Reputation: 1659
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
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
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