javascriptcryptojsbitarrayrncryptorsjcl

SJCL not concatenating bit arrays


I am trying to use RNCryptor-JS which uses SJCL but for some reason, SJCL bit array concatenation does not seem to work.

var SALT_SIZE = 64/8;

var plaintext = "Hello, World!";

var password = "myPassword";

function keyForPassword(password, salt){
    // Using CryptoJS for pbkdf2, aes, sha256, and random word arrays
    var pbkdf2_key = CryptoJS.PBKDF2(
        password,
        salt,
        {
          keySize: 256/32,
          iterations: 1000,
          hasher: CryptoJS.algo.SHA256
        }
      );
    return pbkdf2_key;
}

var encryption_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var encryption_key = keyForPassword(password, encryption_salt);

var hmac_salt = CryptoJS.lib.WordArray.random(SALT_SIZE);
var hmac_key = keyForPassword(password, hmac_salt);

var iv = CryptoJS.lib.WordArray.random(128/8);

var version = sjcl.codec.hex.toBits("03");
var options = sjcl.codec.hex.toBits("01");

var message = sjcl.bitArray.concat(version, iv);
message = sjcl.bitArray.concat(message, encryption_salt);
message = sjcl.bitArray.concat(message, hmac_salt);
message = sjcl.bitArray.concat(message, iv);

// Progressive cipher
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(encryption_key, {iv: iv});
var ciphertext = aesEncryptor.process(plaintext);

message = sjcl.bitArray.concat(message, ciphertext);    

var hmac = new sjcl.misc.hmac(hmac_key).encrypt(message);

var encrypted_data = sjcl.bitArray.concat(message, hmac);
var output = sjcl.codec.hex.fromBits(encrypted_data);

console.log(output);

When I log the output of message after the first set of sjcl.bitArray.concat is done, all that returns is the first concatenation of version and iv. The final hex output is just that first concatenation and hmac concatenated. This reinforces my suspicion that it might be CryptoJS's fault because the output concatenation works and is between two sjcl variables.

I tried using SJCL random bit arrays but had some trouble. SJCL's generator, prng, did not work when using

new sjcl.prng.randomWords(32/4);

or

new sjcl.prng(32/4);

And sjcl.random.randomWords does not seem to work anymore.


Solution

  • CryptoJS (WordArray) and SJCL (bitArray) have different internal representations of data. You can't simply concatenate them.

    The easiest way would be probably to encode it into an intermediate format such as Hex and let the other side decode into its internal format:

    message = sjcl.bitArray.concat(version, sjcl.codec.hex.toBits(iv.toString()));
    

    WordArray#toString() automatically uses Hex encoding. You would have to do this for all lines, but this is a little overkill, since you can concatenate Hex strings as strings:

    message = sjcl.codec.hex.toBits("03" + iv + encryption_salt + hmac_salt + iv);
    

    This should work as expected, because adding a WordArray such as iv to a string automatically calls its toString() function which in turn produces a big-endian hex-encoded string.

    I wonder why you're using iv twice. Perhaps you meant options on one of them.


    What needs to change:

    function convert(wordArray){
        return sjcl.codec.hex.toBits(wordArray.toString());
    }
    var message = "0301" + encryption_salt + hmac_salt + iv;
    
    var ciphertext = CryptoJS.AES.encrypt(plaintext, encryption_key, {iv: iv}).ciphertext;
    
    message += ciphertext;
    message = sjcl.codec.hex.toBits(message);
    
    var hmac = new sjcl.misc.hmac(convert(hmac_key)).encrypt(message);
    
    var encrypted_data = sjcl.bitArray.concat(message, hmac);
    var output = sjcl.codec.hex.fromBits(encrypted_data);
    
    console.log(output);