问题
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