Cryptanalysis with a Known-Plaintext Attack

Cryptanalysis with a Known-Plaintext Attack

In order to make this demonstration a bit more easy, the encryption algorithm is known and very simple.

    In order to make this attack more difficult...
  1. Unkown Key Length
  2. Key is longer than known-plaintext sample
  3. Location of plaintext unkown
  4. Ciphertext will be relatively long in length

So we know that somewhere in the plaintext is the word "radical" - that's what makes this a known plaintext attack.

function encrypt(key, msg) {
	var ciphertext = '';
	for (var i = 0; i < msg.length; i++) {
		ciphertext += toHex(msg.charCodeAt(i) ^ key.charCodeAt(i % key.length));
	}
	return ciphertext;
}

Cipher Text:

Hex representation of "radical" = 7261646963616C
Cipher-text: 212F4C1122566D | 00100001 00101111 01001100 00010001 00100010 01010110 01101101
Plain-text:  7261646963616C | 01110010 01100001 01100100 01101001 01100011 01100001 01101100
XOR:         534E2878413701 | 01010011 01001110 00101000 01111000 01000001 00110111 00000001

Hex representation of "radical" = 7261646963616C
Cipher-text: 2F4C1122566D19 | 00101111 01001100 00010001 00100010 01010110 01101101 00011001
Plain-text:  7261646963616C | 01110010 01100001 01100100 01101001 01100011 01100001 01101100
XOR:         5D2D754B350C75 | 01011101 00101101 01110101 01001011 00110101 00001100 01110101

Decryption Algorithm

function decrypt(hexkey, msg) {
	var plaintext = '';
	for (var i = 0; i < msg.length; i+=2) {
		plaintext += String.fromCharCode(parseInt(msg.slice(i, i+2), 16) ^ parseInt(hexkey.slice(i%hexkey.length, (i%hexkey.length)+2), 16));
	}
	return plaintext;
}
function genKey(knowntext, ciphertext, index, padding) {
	var key = '';
	var len = knowntext.length;

	var ciphersample = ciphertext.slice(index, len+index);
	for (var i = 0; i < len; i+=2) {
		key += toHex(parseInt(ciphersample.slice(i, i+2), 16) ^ parseInt(knowntext.slice(i, i+2), 16));
	}
	for (var i = 0; i < padding; i++) {
		key = key.concat('00');
	}
	// The key is now a hex string that represents what it takes to turn the ciphertext to the plaintext
	// We need to shuffle it around according to what the index is
	len += (padding*2);
	if (index % len != 0) {
		key = key.slice(-(index % len)) + key.slice(0, len - (index % len));
	}
	return key;
}

Index:

Padding:

At this point, our best key is 68416C654A334D000000 at index 180 and padding 3. Because we're still using padding, the decrypted message is only partially clear. Our hex key is 20 characters long with padding, meaning the key used was 10 characters. We were able to determine 7 of them because our known-plaintext "radical" was 7 characters long.

So what we try to do next is to infer what the remaining "bad" characters are. If we can guess three from a word we can complete our key. Unfortunately because our output was generated by javascript and displayed in html, unprintable characters were excluded from the output. Normally you'd know what value these unprintable characters had. The first "clearest" output I want to look more closely at is Sub�"@uently,N3Ye websi�"�for theN(Cganizat�(_ was ha�,Td, temp�5Prily re. Looking specifically at the words theN(Cganizat�(_ was I think it might be "the organization was."

So we need to figure out what value turns N(C into  or

N(C: 4E2843 | 01001110 00101000 01000011
 or: 206F72 | 00100000 01101111 01110010
key: 6E4731 | 01101110 01000111 00110001

So our complete key is 68416C654A334D6E4731.

decrypt('68416C654A334D6E4731', ciphertext);

And if you care, the ascii representation of the key is hAleJ3MnG1