Friday, April 24, 2009

The keymaster is alive :-)

Not a lot of energy to blog right now (probably this Sunday again, maybe Monday), but I just wanted to report that the third stage of the Schlüsselmeister app is complete :-). Following the advice of a commenter on this blog, I used a native Javascript implementation of an encryption algorithm to scramble data that went to the server (other people's passwords are none of my business to read, after all!). GWT's native Javascript integration made it pretty easy, as shown in the following implementation (probably not the most effective way of doing it, but it works):

package com.appenginefan.schluesselmeister.client;

/**
* A thin GWT wrapper around the native javascript BlockTEA
* implementation as described in
* http://www.movable-type.co.uk/scripts/tea-block.html
*/
public class BlockTEAScrambler
extends Scrambler {

@Override
public String scramble(String s) {
return TEAencrypt(s, scramblingSecret);
}

@Override
public String unscramble(String s) {
return TEAdecrypt(s, scramblingSecret);
}

// What follows is the native Javascript library
// =============================================

private static native String TEAencrypt(String plaintext,
String password)/*-{
function strToLongs(s) { // convert string to array of longs, each containing 4 chars
// note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
var l = new Array(Math.ceil(s.length/4));
for (var i=0; i<l.length; i++) {
// note little-endian encoding - endianness is irrelevant as long as
// it is the same in longsToStr()
l[i] = s.charCodeAt(i*4) + (s.charCodeAt(i*4+1)<<8) +
(s.charCodeAt(i*4+2)<<16) + (s.charCodeAt(i*4+3)<<24);
}
return l; // note running off the end of the string generates nulls since
} // bitwise operators treat NaN as 0
function longsToStr(l) { // convert array of longs back to string
var a = new Array(l.length);
for (var i=0; i<l.length; i++) {
a[i] = String.fromCharCode(l[i] & 0xFF, l[i]>>>8 & 0xFF,
l[i]>>>16 & 0xFF, l[i]>>>24 & 0xFF);
}
return a.join(''); // use Array.join() rather than repeated string appends for efficiency
}

if (plaintext.length == 0) return(''); // nothing to encrypt
// 'escape' plaintext so chars outside ISO-8859-1 work in single-byte packing, but keep
// spaces as spaces (not '%20') so encrypted text doesn't grow too long (quick & dirty)
var asciitext = escape(plaintext).replace(/%20/g,' ');
var v = strToLongs(asciitext); // convert string to array of longs
if (v.length <= 1) v[1] = 0; // algorithm doesn't work for n<2 so fudge by adding a null
var k = strToLongs(password.slice(0,16)); // simply convert first 16 chars of password as key
var n = v.length;

var z = v[n-1], y = v[0], delta = 0x9E3779B9;
var mx, e, q = Math.floor(6 + 52/n), sum = 0;

while (q-- > 0) { // 6 + 52/n operations gives between 6 & 32 mixes on each word
sum += delta;
e = sum>>>2 & 3;
for (var p = 0; p < n; p++) {
y = v[(p+1)%n];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
z = v[p] += mx;
}
}

var ciphertext = longsToStr(v);

return ciphertext.replace(/[\0\t\n\v\f\r\xa0'"!]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
}-*/;

private static native String TEAdecrypt(
String ciphertext, String password)/*-{
function strToLongs(s) { // convert string to array of longs, each containing 4 chars
// note chars must be within ISO-8859-1 (with Unicode code-point < 256) to fit 4/long
var l = new Array(Math.ceil(s.length/4));
for (var i=0; i<l.length; i++) {
// note little-endian encoding - endianness is irrelevant as long as
// it is the same in longsToStr()
l[i] = s.charCodeAt(i*4) + (s.charCodeAt(i*4+1)<<8) +
(s.charCodeAt(i*4+2)<<16) + (s.charCodeAt(i*4+3)<<24);
}
return l; // note running off the end of the string generates nulls since
} // bitwise operators treat NaN as 0
function longsToStr(l) { // convert array of longs back to string
var a = new Array(l.length);
for (var i=0; i<l.length; i++) {
a[i] = String.fromCharCode(l[i] & 0xFF, l[i]>>>8 & 0xFF,
l[i]>>>16 & 0xFF, l[i]>>>24 & 0xFF);
}
return a.join(''); // use Array.join() rather than repeated string appends for efficiency
}

if (ciphertext.length == 0) return('');
var v = strToLongs(ciphertext.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); }));
var k = strToLongs(password.slice(0,16));
var n = v.length;

var z = v[n-1], y = v[0], delta = 0x9E3779B9;
var mx, e, q = Math.floor(6 + 52/n), sum = q*delta;

while (sum != 0) {
e = sum>>>2 & 3;
for (var p = n-1; p >= 0; p--) {
z = v[p>0 ? p-1 : n-1];
mx = (z>>>5 ^ y<<2) + (y>>>3 ^ z<<4) ^ (sum^y) + (k[p&3 ^ e] ^ z);
y = v[p] -= mx;
}
sum -= delta;
}

var plaintext = longsToStr(v);

// strip trailing null chars resulting from filling 4-char blocks:
plaintext = plaintext.replace(/\0+$/,'');

return unescape(plaintext);
}-*/;

}

After that, it was mostly a matter of configuring the app to always use https, and then upload it into the cloud. You can look at the latest version at https://3.latest.vinz-clortho.appspot.com/. You need to have a gmail account to login; press the login button to connect to the server.

As you will start using the app, you will notice that there is still quite a lot of cleanup to be done -- which is what I am going to address in the fourth and last iteration. The things I intend to take care of are:


  • Add a new field for the username (how could I forget?)

  • Add a way to select a row with an existing password entry, to show the password, and to edit it

  • A delete button for passwords would be nice.

  • The url entry in the table should be a hyperlink

  • Making the app pretty using CSS (anyone know a good template I can use? Please post to this forum!)

  • Convert the landing page into a CSV that hides the app if no user is logged in. Right now, you have to press the login button, which is not quite intuitive.

  • Maybe put some ads onto the main page ;-)



Anything I forgot? Post it to this blog!

0 comments: