Jorge Guerra Pires
Jorge Guerra Pires

Reputation: 632

Encrypt and decrypt by parts do not seem to work

Consider the code:

const CryptoJS = require("crypto-js");

var key = CryptoJS.enc.Hex.parse("000102030405060708090a0b0c0d0e0f");
var iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");

// encrypt
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });

var ciphertextPart1 = aesEncryptor.process("Message Part 1");
var ciphertextPart2 = aesEncryptor.process("Message Part 2");
var ciphertextPart3 = aesEncryptor.process("Message Part 3");
var ciphertextPart4 = aesEncryptor.finalize();

// decrypt
var aesDecryptor = CryptoJS.algo.AES.createDecryptor(key, { iv: iv });

var plaintextPart1 = aesDecryptor.process(ciphertextPart1);
var plaintextPart2 = aesDecryptor.process(ciphertextPart2);
var plaintextPart3 = aesDecryptor.process(ciphertextPart3);
var plaintextPart4 = aesDecryptor.process(ciphertextPart4);
var plaintextPart5 = aesDecryptor.finalize();

console.log(plaintextPart5.toString());

Source: https://cryptojs.gitbook.io/docs/#ciphers

Maybe I am wrong, but I expected the messages to be decrypted.

The output is actually:

61676520506172742033

I have no idea what that output means and where that comes from.

If I print out another part, the same issue:

console.log(plaintextPart4.toString());

Output:

7373616765205061727420324d657373

Discussion

One comment says that I am wrong on my assumption regarding how the method works, a nice answer could nicely correct me as well! I know the difference, from my studies, between one-way encryption and by parts: it makes no sense to me the way it is actually working, it seems a bug. My ideia is that you could keep adding message to be encrypted, as we can do with hashing, in the case of hashing (from the same abovementioned source), it seems to work, why not for encryption as well?

Upvotes: 1

Views: 743

Answers (2)

x00
x00

Reputation: 13823

I honestly expected this example to work since it is from the official documentation, and they do not mention anything.

Comprehensive documentation is a rare beast.

it makes no sense to me the way it is actually working, it seems a bug

It works like this:

const CryptoJS = require("crypto-js");

const original_message = " ...0, ...1, ...2, ...3, ...4, ...5, ...6, ...7, ...8, ...9, ...The End!"

var key = CryptoJS.enc.Hex.parse("000102030405060708090a0b0c0d0e0f");
var iv  = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");

const aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });
const aesDecryptor = CryptoJS.algo.AES.createDecryptor(key, { iv: iv });

const fragment_size = 25
const e_acc = []
for(let i = 0 ; i*fragment_size < original_message.length ; ++i ) {
  const slice = original_message.slice(i*fragment_size, i*fragment_size + fragment_size)
  console.log("slice to encrypt", slice)
  e_acc.push(aesEncryptor.process(slice));
}
e_acc.push(aesEncryptor.finalize());

let message = ""
for(let i = 0 ; i !== e_acc.length ; ++i ) {
  message += aesDecryptor.process(e_acc[i]).toString(CryptoJS.enc.Utf8)
  console.log("intermidiate message", message) 
}
message += aesDecryptor.finalize().toString(CryptoJS.enc.Utf8)
console.log("full message", message) 

[...] it supposes to be a trick that you can add information by parts
[...] My idea is that you could keep adding message to be encrypted

And you can, the only thing is - you can't decipher parts of messages that do not fill a block. You can only get as much as fits into a chain of complete blocks.

Consider the code

const sum1 = solver.process("1+22+3")
const sum2 = solver.process("03")
const sum3 = solver.finalize()

Here you can get the intermediate sum1 - sum of 1 and 22. But you can't add 3 right away, because, as you can see later on, it is not actually 3 but 303. Same thing with sum2 - solver do not know yet if the 303 should be added or maybe it would become 3031234.... So the only thing the solver can do is to remember the 3 and then the 303, but not use it. Until you call finalize.

aesEncryptor is like the solver but much more complicated.

Let's get back to the aesDecryptor and why all of plaintextPart* contain only the parts of the message... Why do you need "Progressive Ciphering" in the first place? Probably because you are sending fragments over the net? The key word is fragments - they purposely do not contain all the previous parts of the message as they could weight gigabytes!

in the case of hashing [...], it seems to work, why not for encryption as well?

Hashing is a quite different thing. You can not expect that AES has all the same properties as hashing, as you can not expect that hashing has all the same properties as, for example, summation.

Upvotes: 1

ottomeister
ottomeister

Reputation: 5808

The .process method returns any new chunks of ciphertext that were generated by processing the plaintext given as the argument to .process along with any plaintext that was left over from earlier encryptor activity.

In this case the cipher algorithm is AES. Specifically it's AES-128, because the key used to create the encryptor is 128 bits long. AES-128 consumes plaintext in 128-bit (16-byte) blocks and emits 128 bits (16 bytes) of ciphertext for each plaintext block. This block-based processing is what produces the results that you didn't understand.

What happens with your program is:

var ciphertextPart1 = aesEncryptor.process("Message Part 1");

The encryptor is given 14 bytes of input. That's not enough to allow it to generate a ciphertext block, so .process returns an empty ciphertext result which you store in ciphertextPart1. The encryptor stores the unprocessed 14 bytes internally.

var ciphertextPart2 = aesEncryptor.process("Message Part 2");

This gives the encryptor a further 14 bytes. It appends those to the 14 bytes left over from the previous call, so it now has a total of 28 unprocessed bytes. It processes as many of those bytes as it can. That is, it processes the first 16 of those bytes ("Message Part1Me") and returns the ciphertext block for those 16 bytes, which you store in cipherText2. The encryptor now contains 12 unprocessed bytes.

var ciphertextPart3 = aesEncryptor.process("Message Part 3");

This gives the encryptor another 14 bytes. It now has 26 unprocessed bytes. It processes the first 16 of those bytes ("ssage Part2Mess") and returns the ciphertext block for those 16 bytes, which you store in cipherText3. The encryptor now contains 10 unprocessed bytes.

var ciphertextPart4 = aesEncryptor.finalize();

This forces the encryptor to process any unprocessed bytes. It can only work on 16-byte blocks, so it adds 6 bytes of padding to the remaining 10 unprocessed plaintext bytes ("age Part 3"), encrypts that block and returns the ciphertext for that block. You store that ciphertext block as ciphertextPart4.

And now you decrypt the ciphertext blocks.

var plaintextPart1 = aesDecryptor.process(ciphertextPart1);

cipherTextPart1 was an empty block, so plaintextPart1 will be empty and obviously the decryptor will retain no unprocessed ciphertext.

var plaintextPart2 = aesDecryptor.process(ciphertextPart2);

cipherTextPart2 contained the encrypted version of the first 16 bytes of the plaintext, so plaintextPart2 will contain "Message Part 1Me". The ciphertext input was exactly 16 bytes long, therefore the decryptor contains no unprocessed ciphertext.

var plaintextPart3 = aesDecryptor.process(ciphertextPart3);

cipherTextPart3 contained the encrypted version of the next 16 bytes of the plaintext, so plaintextPart3 will contain "ssage Part 2Mess". Again the decryptor is holding no unprocessed ciphertext.

var plaintextPart4 = aesDecryptor.process(ciphertextPart4);

cipherTextPart4 contained the encrypted version of the final 10 bytes of the plaintext, so plaintextPart3 will contain "age Part 3". No unprocessed ciphertext remains in the decryptor.

var plaintextPart5 = aesDecryptor.finalize();

The is no unprocessed ciphertext remaining in the decryptor, so finalize has no work to do and plaintextPart5 will be empty.

Upvotes: 1

Related Questions