最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - CryptoJS AES encryption with ECB mode produces different results with the same params - Stack Overflow

programmeradmin4浏览0评论

As mentioned in this answer, I can use ECB mode to reverse a transformed value back into plaintext and not just pare it to another hashed value.

However, with the below code snippet:

const x = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()
const y = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()

console.log(x, y, x === y)
<script src=".0.0/crypto-js.min.js"></script>

As mentioned in this answer, I can use ECB mode to reverse a transformed value back into plaintext and not just pare it to another hashed value.

However, with the below code snippet:

const x = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()
const y = CryptoJS.AES.encrypt('abc', '123', { mode: CryptoJS.mode.ECB }).toString()

console.log(x, y, x === y)
<script src="https://cdnjs.cloudflare./ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

I get:

U2FsdGVkX19blKXDRXfdgXyviCrZtouB0cPcJPoR/cQ= U2FsdGVkX1+1AwWqKWntLVkh7DtiZxPDYCDNsjmc8LM= false

Am I doing something wrong? Is there a way to achieve the intended results?

Share Improve this question asked Apr 5, 2020 at 16:47 Kenny KiKenny Ki 3,4302 gold badges27 silver badges30 bronze badges 3
  • Read the CryptoJS documentation. The result of the encryption is an object. You're just converting the whole object to a string, which doesn't really make sense. – Pointy Commented Apr 5, 2020 at 17:05
  • @Pointy what do you mean? I'm calling toString method on the object as mentioned in the library's readme – Kenny Ki Commented Apr 6, 2020 at 2:27
  • You can certainly do that, but the object details contain the answer to your question. The encryption process involves random numbers. – Pointy Commented Apr 6, 2020 at 13:36
Add a ment  | 

2 Answers 2

Reset to default 14

First of all: For the same plaintext and the same key always the same ciphertext is generated in ECB mode!

If a WordArray is used as second parameter, then CryptoJS.AES.encrypt performs an encryption with a key and the resulting ciphertexts are identical as expected (here):

function encryptWithKey(plaintext, key){
    var encrypted = CryptoJS.AES.encrypt(plaintext, key, { mode: CryptoJS.mode.ECB });
    console.log("Ciphertext (Base64):\n" + encrypted.toString());        // Ciphertext
    var decrypted = CryptoJS.AES.decrypt(encrypted.toString(), key, { mode: CryptoJS.mode.ECB });
    console.log("Decrypted:\n" + decrypted.toString(CryptoJS.enc.Utf8)); // Plaintext
}

var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
encryptWithKey('abc', key);
encryptWithKey('abc', key);
<script src="https://cdnjs.cloudflare./ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

But if a string is used as the second parameter, CryptoJS.AES.encrypt performs an encryption with a passphrase and the resulting ciphertexts are different (here). Nevertheless, the decryption of course returns the original plaintext:

function encryptWithPassphrase(plaintext, passphrase){
    var encrypted = CryptoJS.AES.encrypt(plaintext, passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Ciphertext (OpenSSL):\n" + encrypted.toString());       // Salt and actual ciphertext in OpenSSL format
    var decrypted = CryptoJS.AES.decrypt(encrypted.toString(), passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Decrypted:\n" + decrypted.toString(CryptoJS.enc.Utf8)); // Plaintext
}

encryptWithPassphrase('abc', '123'); 
encryptWithPassphrase('abc', '123');
<script src="https://cdnjs.cloudflare./ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

Explanation:
During the encryption with a passphrase a random 8 bytes salt is generated from which together with the passphrase the actual key (32 bytes, AES-256) is generated.
The salt is intended to make the use of rainbow tables infeasible. Since the salt is generated randomly each time, the resulting keys are different and thus also the ciphertexts.
CryptoJS.AES.encrypt returns a CipherParams object which encapsulates the relevant parameters like salt and actual ciphertext.
toString() converts this object into the OpenSSL format which consists of the ASCII encoding of Salted__, followed by the 8 bytes salt, followed by the actual ciphertext, all together Base64 encoded. For this reason, all ciphertexts begin with U2FsdGVkX1.

function encryptWithPassphraseParams(plaintext, passphrase){
    var encrypted = CryptoJS.AES.encrypt(plaintext, passphrase, { mode: CryptoJS.mode.ECB });
    console.log("Salt (hex):\n" + encrypted.salt);                 // Salt (hex)
    console.log("Key (hex):\n" + encrypted.key);                   // Key (hex)
    console.log("Ciphertext (hex):\n" + encrypted.ciphertext);     // Actual ciphertext (hex)
    console.log("Ciphertext (OpenSSL):\n" + encrypted.toString()); // Salt and actual ciphertext, Base64 encoded, in OpenSSL format
    console.log("\n");
}

encryptWithPassphraseParams('abc', '123'); 
encryptWithPassphraseParams('abc', '123');
<script src="https://cdnjs.cloudflare./ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

Details:
CryptoJS uses the OpenSSL functionality EVB_BytesToKey with the digest MD5 and an iteration count of 1 when deriving the key, which is not very secure. More secure is the use of reliable KDFs such as PBKDF2 and the subsequent encryption with the resulting key.
Apart from security, it should be noted that EVB_BytesToKey does not implement a standard, so this functionality must first be implemented (or copied from the Internet) in libraries where it is not available.

Note: ECB is an insecure mode and should not be used (here), better is authenticated encryption like GCM. More details about CryptoJS can be found in its documentation (here).

This works using AES and ECB mode to encrypt strings deterministically:

const encrypt = (text: string, key: string) => {
  const hash = CryptoJS.SHA256(key);
  const ciphertext = CryptoJS.AES.encrypt(text, hash, {
    mode: CryptoJS.mode.ECB,
  });
  return ciphertext.toString();
};
const decrypt = (ciphertext: string, key: string) => {
  const hash = CryptoJS.SHA256(key);
  const bytes = CryptoJS.AES.decrypt(ciphertext, hash, {
    mode: CryptoJS.mode.ECB,
  });
  return bytes.toString(CryptoJS.enc.Utf8);
};

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论