tripRev
tripRev

Reputation: 902

Decrypt openssl AES 256 CBC in browser/CryptoJS

I want to decrypt a string that has been encrypted with openssl on the server like this:

openssl enc -e -aes-256-cbc -pbkdf2 -a -S 0123456789ABCDEF -A -k mypassword

Note this is done providing only a salt and password, and openssl should handle key and IV automatically. Am I too optimistic that this can happen when the browser decrypts too? If at all possible, I want to do it with only those encryption settings, or the bare minimum of increased complexity. In the browser, I'm trying to decrypt with CryptoJS like this:

import * as CryptoJS from 'crypto-js'

const encrypted = <ENCRYPTED_STRING_FROM_SERVER>
const password = 'mypassword'
const salt = '0123456789ABCDEF'
const key = CryptoJS.PBKDF2(password, salt) // Generate key

const bytes = CryptoJS.AES.decrypt(encrypted, key)
const decrypted = bytes.toString(CryptoJS.enc.Utf8)
console.log(decrypted)

But the call to CryptoJS.AES.decrypt errors with Cannot read property '0' of undefined, crypto-js/cipher-core.js:371. The docs for CryptoJS.AES.decrypt are quite thin, and any settings I am aware of to change when calling that func seem to give the same error. Thanks to anyone who can shine light!

Upvotes: 1

Views: 5095

Answers (2)

orel
orel

Reputation: 7

try this lib in browser https://www.npmjs.com/package/cryptojs2 More details can be found in the documentation.

Upvotes: -2

Topaco
Topaco

Reputation: 49141

In the OpenSSL statement, the iteration count and digest are not specified, so the default values 10000 and SHA256 are used. This is relevant because CryptoJS uses different default values (1 and SHA1).

CryptoJS applies the OpenSSL format for the ciphertext, i.e. the encrypted data starts with the ASCII encoding of Salted__ followed by the salt and then the ciphertext. Therefore the beginning of the Base64 encoded ciphertext starts always with U2FsdGVkX1.

CryptoJS uses the WordArray data type, which encapsulates an array of words. A word consists of 4 bytes.

During decryption, ciphertext and salt must first be separated. Then, key and IV must be determined using PBKDF2. Due to the different default values, iteration count and digest must be specified explicitly. Finally it can be decrypted:

// 1. Separate ciphertext and salt
var encrypted = "U2FsdGVkX18BI0VniavN78vlhR6fryIan0VvUrdIr+YeLkDYhO2xyA+/oVXJj/c35swVVkCqHPh9VdRbNQG6NQ=="
var encryptedWA = CryptoJS.enc.Base64.parse(encrypted);
var prefixWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(0, 8/4));                             // Salted__ prefix
var saltWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(8/4, 16/4));                            // 8 bytes salt: 0x0123456789ABCDEF
var ciphertextWA = CryptoJS.lib.WordArray.create(encryptedWA.words.slice(16/4, encryptedWA.words.length)); // ciphertext        

// 2. Determine key and IV using PBKDF2
var password = 'mypassword'
var keyIvWA = CryptoJS.PBKDF2(
    password, 
    saltWA, 
    {
        keySize: (32+16)/4,          // key and IV
        iterations: 10000,
        hasher: CryptoJS.algo.SHA256
    }
);
var keyWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(0, 32/4));
var ivWA = CryptoJS.lib.WordArray.create(keyIvWA.words.slice(32/4, (32+16)/4));

// 3. Decrypt
var decryptedWA = CryptoJS.AES.decrypt(
    {ciphertext: ciphertextWA}, 
    keyWA, 
    {iv: ivWA}
);
var decrypted = decryptedWA.toString(CryptoJS.enc.Utf8)
console.log(decrypted)
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

More details can be found in the CryptoJS documentation.

Upvotes: 4

Related Questions