brix / crypto-js

JavaScript library of crypto standards.
Other
15.82k stars 2.39k forks source link

AES encrypt throws error when we pass key as WordArray #439

Open alexandrsek opened 1 year ago

alexandrsek commented 1 year ago

CryptoJS throws error when we pass AES key as WordArray. Here is a sample code to get this error.

const CryptoJS = require("crypto-js");

const data = JSON.stringify({ name: "John Doe" });
const key = CryptoJS.enc.Utf8.parse("Example");

const encrypted = CryptoJS.AES.encrypt(data, key);
const decrypted = CryptoJS.AES.decrypt(encrypted, key);

const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
const decoded = JSON.parse(decryptedString);
console.log(decoded);

And this is what I get

/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:371
                        words[offset + i] ^= block[i];
                                                  ^

TypeError: Cannot read properties of undefined (reading '0')
    at Object.xorBlock (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:371:44)
    at Object.processBlock (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:314:27)
    at Object._doProcessBlock (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:486:25)
    at Object._process (/Users/alex/Desktop/scripts/node_modules/crypto-js/core.js:632:27)
    at Object._doFinalize (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:501:46)
    at Object.finalize (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:163:44)
    at Object.encrypt (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:685:41)
    at Object.encrypt (/Users/alex/Desktop/scripts/node_modules/crypto-js/cipher-core.js:201:59)
    at Object.<anonymous> (/Users/alex/Desktop/scripts/decrypt.js:6:32)
    at Module._compile (node:internal/modules/cjs/loader:1159:14)

Node.js v18.12.0

The simple solution would be to pass key as a string, but in the project I'm working on we can only decrypt when we pass key as CryptoJS.enc.Utf8.parse().

stanbar commented 1 year ago

I have the same problem

alexandrsek commented 1 year ago

I decided to just rewrite the encryption with node-forge and it ended up working correctly...

liudonghua123 commented 4 months ago

I have the similar problem when I try to use binary key of AES.encrypt instead of plain string password.

import crypto from 'crypto-js'

// write a simple ase encryption function test
const encrypt = (text, key) => {
    return crypto.AES.encrypt(text, key).toString() // key is a string, and it works without iv cfg
}

const encryptViaByte = (text, key) => {
    // return crypto.AES.encrypt(text, crypto.enc.Hex.parse(key), { iv: crypto.enc.Hex.parse('') }).toString() // key is a non string, and it works only you set iv cfg
    return crypto.AES.encrypt(text, crypto.enc.Hex.parse(key)).toString() // without iv cfg, TypeError: Cannot read properties of undefined (reading '0') will be thrown and from the final stack frame, it is iv is undefined, why not default iv to empty string?
}

encrypt('hello world', '1234')
encryptViaByte('hello world', '010203')
Liu.D.H  crypto-js-demo   221ms  00:27 > node index.js
D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:371
                        words[offset + i] ^= block[i];
                                                  ^

TypeError: Cannot read properties of undefined (reading '0')
    at Object.xorBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:371:44)
    at Object.processBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:314:27)
    at Object._doProcessBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:486:25)
    at Object._process (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\core.js:632:27)
    at Object._doFinalize (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:501:46)
    at Object.finalize (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:163:44)
    at Object.encrypt (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:685:41)
    at Object.encrypt (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:201:59)
    at encryptViaByte (file:///D:/code/node/playground/crypto-js-demo/index.js:10:23)
    at file:///D:/code/node/playground/crypto-js-demo/index.js:14:1

Node.js v20.11.1

Liu.D.H  crypto-js-demo   164ms  00:34 >

Here is the related code of final stack frame, iv is undefined and the go to else branch and block is undefined too.

                // Choose mixing block
                if (iv) {
                    block = iv;

                    // Remove IV for subsequent blocks
                    this._iv = undefined;
                } else {
                    block = this._prevBlock;
                }

                // XOR blocks
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= block[i];
                }

The simple workaround is update the line to block = this._prevBlock || [0];.

liudonghua123 commented 4 months ago

I have the similar problem when I try to use binary key of AES.encrypt instead of plain string password.

import crypto from 'crypto-js'

// write a simple ase encryption function test
const encrypt = (text, key) => {
    return crypto.AES.encrypt(text, key).toString() // key is a string, and it works without iv cfg
}

const encryptViaByte = (text, key) => {
    // return crypto.AES.encrypt(text, crypto.enc.Hex.parse(key), { iv: crypto.enc.Hex.parse('') }).toString() // key is a non string, and it works only you set iv cfg
    return crypto.AES.encrypt(text, crypto.enc.Hex.parse(key)).toString() // without iv cfg, TypeError: Cannot read properties of undefined (reading '0') will be thrown and from the final stack frame, it is iv is undefined, why not default iv to empty string?
}

encrypt('hello world', '1234')
encryptViaByte('hello world', '010203')
Liu.D.H  crypto-js-demo   221ms  00:27 > node index.js
D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:371
                        words[offset + i] ^= block[i];
                                                  ^

TypeError: Cannot read properties of undefined (reading '0')
    at Object.xorBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:371:44)
    at Object.processBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:314:27)
    at Object._doProcessBlock (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:486:25)
    at Object._process (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\core.js:632:27)
    at Object._doFinalize (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:501:46)
    at Object.finalize (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:163:44)
    at Object.encrypt (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:685:41)
    at Object.encrypt (D:\code\node\playground\crypto-js-demo\node_modules\crypto-js\cipher-core.js:201:59)
    at encryptViaByte (file:///D:/code/node/playground/crypto-js-demo/index.js:10:23)
    at file:///D:/code/node/playground/crypto-js-demo/index.js:14:1

Node.js v20.11.1

Liu.D.H  crypto-js-demo   164ms  00:34 >

Here is the related code of final stack frame, iv is undefined and the go to else branch and block is undefined too.

              // Choose mixing block
              if (iv) {
                  block = iv;

                  // Remove IV for subsequent blocks
                  this._iv = undefined;
              } else {
                  block = this._prevBlock;
              }

              // XOR blocks
              for (var i = 0; i < blockSize; i++) {
                  words[offset + i] ^= block[i];
              }

The simple workaround is update the line to block = this._prevBlock || [0];.

Maybe a more nice solution is set a default cfg option if the third argument is not passed. Change cfg: Base.extend(), to cfg: Base.extend({iv: WordArray.create()}), in

https://github.com/brix/crypto-js/blob/ac34a5a584337b33a2e567f50d96819a96ac44bf/src/cipher-core.js#L25-L31

Then the cfg in encrypt/decrypt will work as expected.

https://github.com/brix/crypto-js/blob/ac34a5a584337b33a2e567f50d96819a96ac44bf/src/cipher-core.js#L664-L670

I tested with the following code, it works for me.

// test1.js
import CryptoJS from 'crypto-js'

// 生成随机密钥和 IV
const key = CryptoJS.lib.WordArray.random(256 / 8); // 256 位密钥
const iv = CryptoJS.lib.WordArray.random(128 / 8);  // 128 位 IV

const plaintext = "This is some plaintext to encrypt.";

// encrypt
const encrypted = CryptoJS.AES.encrypt(plaintext, key);

console.log("encrypted text:", encrypted.toString());

// decrypt
const decrypted = CryptoJS.AES.decrypt(encrypted, key);
const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);

console.log("decrypted text:", decryptedText);

image