kiecodes
kiecodes

Reputation: 1659

Wrong amount of significant bytes when decrypting AES CFB with CryptoJS

I am trying to decrypt AES-256-encrypted Base64-encoded data. This part of my JS code:

var data = "Ic9OcXxn2MnpgFwH4SHkxSY3laYB+kkevevwOPeQjLEeUsAVcHzLdBJZ1liWK5d94I/uNwyzbk+/l6QH/WsU0mzxuXcqBYl4iRIA7UIfchYJTsoaWAnSIjsioFUBAfc8YCODID0HW4AY7nK6Bb0mTP55HxlWstE92w1uJVMmBmJRscrAxySNlAFzVVGxuiiCc3sJimfbMNajXOUeFgvSzw==";

var base64data = CryptoJS.enc.Base64.parse(data);

var encrypted = new CryptoJS.lib.WordArray.init(base64data.words.slice(4));
var iv = new CryptoJS.lib.WordArray.init(base64data.words.slice(0, 4));
var key = CryptoJS.enc.Utf8.parse("secure%password!secure%password!");

var cipher = CryptoJS.lib.CipherParams.create({
  ciphertext: encrypted
});

var decrypted = CryptoJS.AES.decrypt(cipher, key, {
  iv: iv,
  mode: CryptoJS.mode.CFB
});

var result = decrypted.toString(CryptoJS.enc.Utf8);
console.log(decrypted.toString(CryptoJS.enc.Utf8)); 
// Wrong Output: {"first_name": "Han

console.log(decrypted.sigBytes);

decrypted.sigBytes = 144

console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Correct
// Correct Output: {"first_name": "Hans-J\u00fcrgen", "last_name": "M\u00fcller", "city": "Hamburg", "number": "20a", "zip": "20456", "street": "Ladenstra\u00dfe"}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/mode-cfb.js"></script>

The first output of the WordBuffer results in only part of the decrypted data, because the significant bytes are set to 19 instead of 144. After correcting this, the output is wrong.

Why do I have to correct the sigBytes manual? Any ideas? Thank you!

Upvotes: 0

Views: 1644

Answers (2)

Andrew Shpagin
Andrew Shpagin

Reputation: 1

I also got this problem. The reason that string length was exactly N*chunk_size. So zero at the end was not included in the data. After including zero into the initial data it works as expected.

In the example below the string "0123456789ABCDEF0123456789ABCDEF" encoded with AES-128, ECB, used key "16_characterskey". In the first example the string took216 bytes, in the second - 316. And the first gives incorrect output, the second one is correct.

//incorrect
const base64string = "BuP9rAcekMxp6TV9XmRz5wbj/awHHpDMaek1fV5kc+c=";
const value = CryptoJS.enc.Base64.parse(base64string);
const key = CryptoJS.enc.Utf8.parse("16_characterskey");
const dec = CryptoJS.AES.decrypt({ ciphertext: value }, key, { keySize: 16, mode : CryptoJS.mode.ECB });
console.log(dec);
console.log(dec.toString(CryptoJS.enc.Utf8));

//correct
const base64string1 = "BuP9rAcekMxp6TV9XmRz5wbj/awHHpDMaek1fV5kc+fs4G6ppB/DwfVXaeSnk4yu";
const value1 = CryptoJS.enc.Base64.parse(base64string1);
const dec1 = CryptoJS.AES.decrypt({ ciphertext: value1 }, key, { keySize: 16, mode : CryptoJS.mode.ECB });
console.log(dec1);
console.log(dec1.toString(CryptoJS.enc.Utf8));

Output:

{
10:04:51 AM web.1 |    words: [
10:04:51 AM web.1 |      808530483,  875902519,
10:04:51 AM web.1 |      943276354, 1128547654,
10:04:51 AM web.1 |      808530483,  875902519,
10:04:51 AM web.1 |      943276354, 1128547654
10:04:51 AM web.1 |    ],
10:04:51 AM web.1 |    sigBytes: -38
10:04:51 AM web.1 |  }
// there should be string output, but sigBytes is incorrect, so no output
10:04:51 AM web.1 |  {
10:04:51 AM web.1 |    words: [
10:04:51 AM web.1 |      808530483,  875902519,
10:04:51 AM web.1 |      943276354, 1128547654,
10:04:51 AM web.1 |      808530483,  875902519,
10:04:51 AM web.1 |      943276354, 1128547654,
10:04:51 AM web.1 |              0,          0,
10:04:51 AM web.1 |              0,          0
10:04:51 AM web.1 |    ],
10:04:51 AM web.1 |    sigBytes: 48
10:04:51 AM web.1 |  }
10:04:51 AM web.1 |  0123456789ABCDEF0123456789ABCDEF
// now decoding is correct because we included additional padding

Pay attention, data words are exactly the same except zeros at the end. IMO it looks like a bug of CryptoJS.AES.decrypt. Anyway, additional padding helps. Look the https://github.com/AndrewShpagin/aes_string_pass for more details. It may be useful if you need to pass data securely from the c++ code to js.

Upvotes: 0

kiecodes
kiecodes

Reputation: 1659

I found the answer myself. The problem is actually not in the javascript code. It was in the Python code, which encrypted the data.

When setting the segment size to 128 in pycrpyto using AES CFB you have to pad your date to crypt it to be a multiple of 16 bytes.

Here is my full python encryption code in which data refers to some byte string and key 32-byte long encryption key.

length = 16 - (len(data) % 16)
data += bytes([length]) * length

iv = Random.new().read(AES.block_size)
key = options.encrypt_key.encode()
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)

crypted = cipher.encrypt(data)
entry = iv + crypted
entry = base64.b64encode(entry)

Entry is send to the client which decrypts the data again with the following code in which data is the base64 encoded crypted data from the python code and key again is the same 32-byte long key:

var base64data = CryptoJS.enc.Base64.parse(data);

var encrypted = new CryptoJS.lib.WordArray.init(base64data.words.slice(4));
var iv = new CryptoJS.lib.WordArray.init(base64data.words.slice(0, 4));

var cipher = CryptoJS.lib.CipherParams.create({ ciphertext: encrypted });

var decrypted = CryptoJS.AES.decrypt(cipher, key, {iv: iv, mode: CryptoJS.mode.CFB});

This works good and in every case.

Upvotes: 1

Related Questions