ritter.vg
tech > security > adventures in (in)security > people who shouldn't do crypto episode 1
16 May 2009 22:12:15 EST
This article was published over a year before I was employed in the security industry, and does not reflect anything seen during a client engagement.

So the other day I was at a website's forgot password form, and I happened to look at their source, and I happened to see stuff like the below. I won't say who's website, but suffice to say that it was a very important website, and I'm genuinely concerned about what might happen if I called them out.

function BuildKey()//these are randomly generated keys (but we need to lock them)
{
	var k = new Array();
	k[0] = new Number('638750945226292'); 
	k[1] = new Number('280638632359334');
	k[2] = new Number('155182780519641');
	k[3] = new Number('836535665283384');
	return( k );
}
function Encipher(p1, p2, k)
{
	var temp = new Array();
	temp[0] = 1; 
	temp[1] = new Number(p1);
	temp[2] = new Number(p2);
	
	var sum = 0;
	var delta = 0x9E3779B9;
	var n = 32;
	
	while ( n-- > 0 )
	{
		temp[1] = temp[1] + ( ( temp[2] << 4 ^ temp[2] >> 5 ) + temp[2] ^ sum + k[ ( sum & 3 ) ] );
		sum = sum + delta;
		temp[2] = temp[2] + ( ( temp[1] << 4 ^ temp[1] >> 5 ) + temp[1] ^ sum + k[ ( sum >> 11 & 3 ) ] );
	}
	return( temp );
}
function EncipherText(password)
{
	//bunch of irrelvent stuff
	key = BuildKey();
	// pad the input so that it's a multiple of 8
	while ( inString.length & 0x7 ) 
	inString += '\x20'; 
	var i = 0;
	while ( i < inString.length ) {
	// slam 4 bytes into a dword 
	p1D  = inString.charCodeAt(i++);
	p1D |= inString.charCodeAt(i++) << 8;
	p1D |= inString.charCodeAt(i++) << 16;
	p1D |= inString.charCodeAt(i++) << 24;
	// mask off 32 bits to be safe
	// javascript numbers are 64 bit IEEE double doubles
	p1D &= 0xFFFFFFFF;
	// slam 4 bytes into a dword
	p2D  = inString.charCodeAt(i++);
	p2D |= inString.charCodeAt(i++) << 8;
	p2D |= inString.charCodeAt(i++) << 16;
	p2D |= inString.charCodeAt(i++) << 24;
	// mask off 32 bits to be safe
	// javascript numbers are 64 bit IEEE double doubles
	p2D &= 0xFFFFFFFF;
	// send dwords to be enciphered
	res = Encipher(p1D, p2D, key);
	// check the validity flag
	// convert the results to hex to facilitate deciphering - 16 chars generated per turn
	// append the hex values to the output buffer 
	// do not include any new lines - the form is set to wrap
	// the validity flag defaults to true because I'm not certain what to check for ;-)
	outString += ( res[0] ? '' + DecToHex(res[1]) + DecToHex(res[2]) : errormark );
	// later perhaps the outString should be chunked up
	// along the lines of B64 email attachments
	// although 0-9 and A-F are 6 and 7 bit values respectively
	// it's really a question of post limitations on the http server
	// clear the temporary variables
	p1D = 0; p2D = 0; res = null;
	}
	// return the encrypted string
	return outString;
}
function DecToHex(x) 
{
	var s = '', x_ = !isNaN(Number(x)) ? Number(x) : 0;
	while( Boolean( x_ ) ) { s = '0123456789ABCDEF'.charAt( x_ & 0xf ) + s; x_ >>>= 4; }
	while ( s.length & 0x7 ) { s = '0' + s; } 
	return ( s );
}
function customResetPassword(f){
	if(f.newPassword.value == ""){
		alert('Please enter the password');
		f.newPassword.focus();
		return false;
	}
	if(f.confirmPassword.value == ""){
		alert('Please confirm the password');
		f.confirmPassword.focus();
		return false;
	}
	if(f.newPassword.value != f.confirmPassword.value){
		alert("New password and Confirm password doesn't match");
		f.newPassword.focus();
		return false;
	}
	var pwdcheck = " EncipherText(f.newPassword.value) == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || EncipherText(f.newPassword.value) == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || f.newPassword.value == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || f.newPassword.value == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || f.newPassword.value == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || f.newPassword.value == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	pwdcheck = pwdcheck + " || f.newPassword.value == ";
	pwdcheck = pwdcheck + "'A12D9DAB99664BA5E2328584044535EA'"; 
	try{
		if(eval(pwdcheck)){
			alert('The New password entered matches one of your past 5 passwords, Please try again');
			f.newPassword.value = "";
			f.confirmPassword.value="";
			f.newPassword.focus();
			return false;
		}
	}catch(e){}
	f.encNewPassword.value = EncipherText(f.newPassword.value);
	document.customForgetPasswordForm.action='forgetPassword.do;';
	document.customForgetPasswordForm.submit();
	return false;
}

Were you intrigued by those "encrypted" values too? I certainly was. (And in the example I swapped them out for the real values of course). Their encryption function happened to have a magic number 0x9E3779B9 and a quick google later I was looking at XTEA. It's not a bad algorithm, it's just a horrible idea to do it all in javascript! So I set about cracking it. And here's what I came up with:

javascript: //I made it a handy bookmarklet!
function ascii_to_hex(x)
{
	h="0123456789ABCDEF";
	a=' !"#$%&' + "'" + '()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[' + '\\' + ']^_`abcdefghijklmnopqrstuvwxyz{|}';
	r="";
	for(i=0;i<x.length;i++)
	{
		y=x.charAt(2*i);
		z=x.charAt(2*i+1);
		v=h.indexOf(y)*16+h.indexOf(z);
		r+=a.charAt(v-32)
	}
	return r;
}
function decrypt(n)
{
	var o='';
	var k=BuildKey();//How do I get the key?  Well they send it back to the browser and let you get it from a function, helpfully
	for(var i=0; i<n.length; i+=16)//loop over every 16 digits
	{
		p1=parseInt('0x' + n.slice(i,i+16).slice(0,8));//get the hex as an integer.  
		p2=parseInt('0x' + n.slice(i,i+16).slice(8,16));//But we need to split each number into the two numbers we work with
		s= 0x9E3779B9 * 32; //delta x 32 rounds.  
		r=32;//32 rounds
		while(r-->0)
		{
			p2-=(((p1<<4)^(p1>>5))+p1)^(s+k[(s>>11)&3]); //the crypto
			s-=0x9E3779B9; //subtract delta
			p1-=(((p2<<4)^(p2>>5))+p2)^(s+k[s&3]) //the other half of the crypto
		}
		o += ascii_to_hex(DecToHex(p1)).reverse() + ascii_to_hex(DecToHex(p2)).reverse();
		//change the decimal to hex, and the hex to ascii, and then reverse it.  then put the two halves together
	}
	return o; //that's your password
}
String.prototype.reverse=function() //just for cross-browser compatibility
{
	a=this.split("");//make array
	b=a.reverse();//reverse array
	s=b.join("");//join array back into string
	return s
};
var m=(customResetPassword + '').split(/pwdcheck = pwdcheck \+ "'/); //get lines defining the passwords out of the function 'customResetPassword'
for(j=1;j<m.length-1;j++)
{
	alert(decrypt(m[j].slice(0,32)));// get the first 32 characters (the encrypted password) and then go decrypt it
}

And if you want to demo it all, head over to this example page and try it out. And that's why people who don't understand crypto, shouldn't do it. Crypto is not like bacon. You can put bacon on anything, and it makes it better. You can't put crypto on anything and make it secure.

The postscript is that I notified the right people, who notified the right people, and it got fixed in a week. I glanced at their fix, and it's certainly not this stupid, so it can hold out for a week or two before I try breaking it again.

Comments
Add a comment...
required
required, hidden, gravatared

required, markdown enabled (help)
you type:you see:
*italics*italics
**bold**bold
[stolen from reddit!](http://reddit.com)stolen from reddit!
* item 1
* item 2
* item 3
  • item 1
  • item 2
  • item 3
> quoted text
quoted text
Lines starting with four spaces
are treated like code:

    if 1 * 2 < 3:
        print "hello, world!"
Lines starting with four spaces
are treated like code:
if 1 * 2 < 3:
    print "hello, world!"