Export to GitHub

crypto-js - issue #96

bug in cipher-core.js


Posted on Sep 21, 2013 by Grumpy Wombat

There is a bug in cipher-core.js that causes decryption to fail (in all "serialized" cases, as far as i can tell).

See line 704:

var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext);

should be:

var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext);

There is no such property "ciphertext.ciphertext".

Comment #1

Posted on Sep 21, 2013 by Happy Horse

"ciphertext" will be a CiperParams object (or at least it's supposed to be; JavaScript doesn't let us enforce types, so it's up to you to pass the right thing), and CipherParams objects have a property "ciphertext".

Comment #2

Posted on Sep 21, 2013 by Grumpy Wombat

All i know is that when I made the change that i described above, the code worked. Before the change, it didn't.

Comment #3

Posted on Sep 21, 2013 by Grumpy Wombat

Comment deleted

Comment #4

Posted on Sep 21, 2013 by Grumpy Wombat

Look at the _parse() function. In the case that a string is passed into decrypt() for the ciphertext param (which is legal according to the decrypt() docs), _parse() returns a word array, not a CipherParams as _parse() is documented to do. Hence the bug. decrypt assumes it always returns a CipherParams object.

Comment #5

Posted on Sep 21, 2013 by Happy Horse

How about you post your code that reproduces the issue.

Comment #6

Posted on Sep 21, 2013 by Grumpy Wombat

I think the code will only work, when passing in a string, if the string is "OpenSSL-compatible"

Comment #7

Posted on Sep 21, 2013 by Grumpy Wombat

Just pass in any string that is not OpenSSL. This is clear just from a cursory look at the _parse() function. But if it helps, here is the call i'm making:

cryptography.decrypt = function (aString, key) {

var ivlen = this.AesOptions.blockSize; // in 32-bit words

var messageWordArray = this.base64Decode(aString);

this.AesOptions.iv = CryptoJS.lib.WordArray.create(messageWordArray.words.slice(0, ivlen));

var message = CryptoJS.lib.WordArray.create(messageWordArray.words.slice(ivlen));

return CryptoJS.AES.decrypt(message, key, this.AesOptions).toString(CryptoJS.enc.Utf8); };

message is a word array.

Comment #8

Posted on Sep 21, 2013 by Grumpy Wombat

OK, more info, as i wasn't correct in my analysis above. My code is passing in a WordArray for the ciphertext param, which _parse() treats as a CipherParams object, even though it is not. So this appears to be the source of the error: I should be passing in either a CypherParams object or a string. The docs are clear about this, so it looks like it is my mistake.

I apologize for wasting your time and being presumptuous!

Comment #9

Posted on Sep 21, 2013 by Grumpy Wombat

OK, I spoke too soon, again. I find that if I pass in a Base64 string (having set the cfg.format to be Base64), the _parse() function returns a wordarray, not a CipherParams object. As I noted above, when passing in a string _parse() will return a CipherParams object only when the string format is OpenSSL.

So this does appear to be a bug in either the decrypt() code or the documentation, as decrypt() is documented to take any string, but in fact will not work unless the string is formatted as OpenSSL.

Comment #10

Posted on Sep 21, 2013 by Grumpy Wombat

Comment deleted

Comment #11

Posted on Sep 21, 2013 by Grumpy Wombat

OK, it seems like the correct fix would be to make format.parse() always return the same type of object.

But I don't have the scope of knowledge to understand the implications of such a change. So until a fix is released I'm going with the following hack:

    _parse: function (ciphertext, format) {
        if (typeof ciphertext == 'string') {
            var retval = format.parse(ciphertext, this);
            if (format != CryptoJS.enc.OpenSSL) {
                retval = CipherParams.create({ ciphertext: retval, salt: null });
            }
            return retval;
        } else {
            return ciphertext;
        }
    }

Comment #12

Posted on Sep 22, 2013 by Happy Horse

Encoders (under the enc namespace) and cipher text formatters (under the format namespace) don't do the same job and are not interchangeable. You can't pass Base64 as a format and expect it to work.

Comment #13

Posted on Sep 22, 2013 by Grumpy Wombat

ok, thanks for that info. So, as documented and coded, decrypt() will take either a CypherParam or a string. Since the only format strategy type I see is OpenSSL, then without writing one's own custom format strategy, the string must be in OpenSSL format. Am I missing other format types or documentation?

Given that my starting point is a WordArray, then my options are:

1) convert the WordArray to a CipherParam, 2) convert the WordArray to an OpenSSL string, 3) convert the WordArray to another sort of string (Base64, for example) and write a custom format strategy that would enable decrypt() to convert the string to a CipherParam.

Converting the WordArray to an OpenSSL string (option 2) presets a problem in that the OpenSSL formatter wants "Base64" to be defined, and it is not clear to me without further research how to make that be defined.

I am reluctant to convert the WordArray to a CipherParam (option 1) because CipherParam seems to potentially overlap with the config options that I'm passing, which makes me uncomfortable. But the only property it seems to care about in this case is ciphertext. So the code to convert WordArray to CipherParam looks like this:

message = CryptoJS.lib.CipherParams.create({ ciphertext: message });

That works. Not all very intuitive nor documented, but i'll take it. Thanks, Jeff, for your help!

Status: New

Labels:
Type-Defect Priority-Medium