Incorrect decrypted string implemented using AES/ECB/NoPadding and base 64 with crypto-js library

大兔子大兔子 提交于 2021-02-08 19:55:19

问题


I am trying to encrypt/decrypt the below data using crypto-js and getting unexpected results.

Library: https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js

function encryptByAESECB(message, key) {
    var keyHex = CryptoJS.enc.Utf8.parse(key);
    var encrypted = CryptoJS.AES.encrypt(message, keyHex, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.NoPadding
    });
    return encrypted.toString();
}
function decryptByAESECB(ciphertext, key) {
    var keyHex = CryptoJS.enc.Utf8.parse(key);

    // direct decrypt ciphertext
    var decrypted = CryptoJS.AES.decrypt({
        ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
    }, keyHex, {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.NoPadding
    });

    return decrypted.toString(CryptoJS.enc.Utf8);
}

var message = 'Message';
var key = '5401cae4-2c89-49';

var ciphertext = encryptByAESECB(message, key);
// ciphertext: 8dKft9vkZ4I=
console.info('ciphertext:', ciphertext);
var plaintext = decryptByAESECB(ciphertext, key);
// plaintext : Message
console.info('plaintext :', plaintext);

Expected output: "Message"

Current output: Error Message "Malformed UTF-8 data"

Code debug link: https://stackblitz.com/edit/aes-ecb-bhardwaj

NOTE: If I am using other paddings like Pkcs7 and ZeroPadding it seems to be working fine.


回答1:


Actually, a plaintext whose length is not an integer multiple of the blocksize cannot be encrypted with AES-ECB if padding is disabled. Therefore, (at least) one of these conditions must be changed implicitly so that encryption is possible at all.

CryptoJS uses the WordArray data type that encapsulates a word array (words) and the number of significant bytes (sigBytes) in that array. It turns out that the encryption performed under the described conditions produces the same ciphertext in the word array as a plaintext explicitly padded with 0-values or as the builtin Zero padding. However, the 0-values used for encryption are not taken into account in sigBytes for the first case, whereas this happens for the last two cases:

function encryptAndPrint(message, padding){
    var encrypted = CryptoJS.AES.encrypt(message, keyHex, {
        mode: CryptoJS.mode.ECB,
        padding: padding
    });
    console.log("words: " + encrypted.ciphertext.words);
    console.log("sigBytes: " + encrypted.ciphertext.sigBytes); 
}

var keyHex = CryptoJS.enc.Utf8.parse('5401cae4-2c89-49');

// Implicit padding (not considered in sigBytes)
var message = 'Message';
encryptAndPrint(message, CryptoJS.pad.NoPadding);

// Explicit padding (considered in sigBytes)
var message = 'Message\0\0\0\0\0\0\0\0\0';
encryptAndPrint(message, CryptoJS.pad.NoPadding);

// built-in Zero padding (considered in sigBytes)
var message = 'Message';
encryptAndPrint(message, CryptoJS.pad.ZeroPadding);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

However, during decryption only the significant bytes (more precise ceil(sigBytes/4) words) are taken into account, so that effectively a different ciphertext is decrypted than the original one. In the above example the ciphertext contained in the word array is [1216989226, 1168217134, 91026943, 1588012560], but because of the not considered 0-values in sigBytes [1216989226, 1168217134, 0, 0] is effectively decrypted, so that the decrypted plaintext does not match the original plaintext:

function decryptAndPrint(ciphertext){
    var decrypted = CryptoJS.AES.decrypt(
        {ciphertext: ciphertext}, 
        keyHex, 
        {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.NoPadding}
    );
    console.log("Decrypted (hex): " + decrypted.toString(CryptoJS.enc.Hex));
}

var keyHex = CryptoJS.enc.Utf8.parse('5401cae4-2c89-49');

// Decryption: The last two words are ignored 
var ciphertext = CryptoJS.lib.WordArray.create([1216989226, 1168217134, 91026943, 1588012560]);
ciphertext.sigBytes = 7; 
decryptAndPrint(ciphertext);

// Proof: The result is identical if the last two words are explicitly set to an arbitrary value, e.g. 0. 
var ciphertext = CryptoJS.lib.WordArray.create([1216989226, 1168217134, 0, 0]);
ciphertext.sigBytes = 7; 
decryptAndPrint(ciphertext);

// The correct result, if padding is considered in sigBytes
var ciphertext = CryptoJS.lib.WordArray.create([1216989226, 1168217134, 91026943, 1588012560]);
ciphertext.sigBytes = 16; 
decryptAndPrint(ciphertext);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

This is probably a bug in CryptoJS. For a block cipher with padding disabled, sigBytes should be an integer multiple of the blocksize (if no stream cipher mode is used). If this condition is not met, it should be handled as an error, e.g. with an exception. This applies to both, the plaintext for encryption and the ciphertext for decryption.



来源:https://stackoverflow.com/questions/61717485/incorrect-decrypted-string-implemented-using-aes-ecb-nopadding-and-base-64-with

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!