Search code examples
javascriptcryptojs

Encrypt / Decrypt binary data crypto-js


I am failing to successfully encrypt a binary message using Crypto-JS. My data is in an Array Buffer. I'm working in Angular and I've run up a short test which works fine. I'm communicating with a WiFi device. The following test works fine, using simple strings, which proves that the encryption and transport are all working.

var iv_hexString = this.buf2hex([146, 66, 191, 151, 23, 3, 113, 119, 231, 131, 133, 112, 79, 32, 114, 136]);
var key_hexString = this.buf2hex([123, 217, 20, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 115, 222, 209, 241, 24, 175, 144, 175, 53, 196, 29, 24, 23, 17, 218, 131, 226, 53, 209]);
var key = CryptoJS.enc.Hex.parse(key_hexString);
var iv = CryptoJS.enc.Hex.parse(iv_hexString);

console.log('iv  = ' + iv);
console.log('key = ' + key);

let cli_verify = CryptoJS.AES.encrypt("1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff", key, {
  mode: CryptoJS.mode.CTR,
  iv: iv,
  padding: CryptoJS.pad.NoPadding
});

console.log('Cipher Text = ', cli_verify.ciphertext.toString());



// Test by decoding some shit here. 
var decrypted = CryptoJS.AES.decrypt(cli_verify, key, {
  mode: CryptoJS.mode.CTR,
  iv: iv,
  padding: CryptoJS.pad.NoPadding
});

console.log('Decrypted Output = ', decrypted.toString(CryptoJS.enc.Utf8));

This works fine:

[ng] [console.log]: "iv  = 9242bf9717037177e78385704f207288"
[ng] [console.log]: "key = 7bd9140b181a552d72b81ba22573ded1f118af90af35c41d181711da83e235d1"
[ng] [console.log]: "Cipher Text = " "de3cfe57c4d6fc5a8218b9eb801ebd49d3bd6cbf9ecd1b62656be424c09ba1d72b8ea3b6f304752b514ff4dc086a63a5349534e08e110f4855108a8d96804af3"
[ng] [console.log]: "Decrypted Output = " "1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff"

Problem is my data isn't in a string. It's binary, so is difficult to transmit as a string. I do not have any control over the code on the other side, which expects this format. My data may contains any of the 256 codes:

let myMessage = new Uint8Array([0x0a, 0x11, 0x00, 0xff, 0x30, 0x53, 0x22, 0x6a, 0x8f, 0x05, 0x16, 0x15, 0x88, 0xb5]);

Can anyone advice how I can format that such that it will be accepted by crypto-js. In the case of the code 0x30, that is of course '0'. Excellent. That fits in a string nicely. 0x11 and 0x00: not so good! I've tried the following:

var devPublic_hexString = this.buf2hex(this.device_Public_Key);
  let cli_verify = CryptoJS.AES.encrypt(this.hex2a(devPublic_hexString), key, {
    mode: CryptoJS.mode.CTR,
    iv: iv,
    padding: CryptoJS.pad.NoPadding
  });

 hex2a(hexx) {
    var hex = hexx.toString();//force conversion
    var str = '';
    //for (var i = 0; (i < hex.length && hex.substr(i, 2) !== '00'); i += 2)
    for (var i = 0; (i < hex.length); i += 2)
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    return str;
  }

Looking closely at the hex dumps, there are some good clues:

Expected result other side:

5c fa 82 94 4e 79 64 08 9c 0b 9a 4d d8 8f 06 c5 39 ad c0 91 9b c7 cc 6a 0a 86 81 dd 5d ad 0b 03

Actual results other side:

5c c3 ba c2 82 c2 94 4e 79 64 08 c2 9c 0b c2 9a 4d c3 98 c2 8f 06 c3 85 39 c2 ad c3 80 c2 91 c2

Etc. I got bored bolding the matching bits but you can see the pattern. Question is what are all the c2 and c3 codes about? Why is 0xfa confused with oxba? Same for 0x98 and 0xd8? And 0xc5 & 0x85... And 0xC0 & 0x80... Is it something to do with using illegal string characters that are decoded wrong? How can I use this library to get my binary data across?


Solution

  • Your problems stem from the fact that WordArray from crypto-js will handle inputs as UTF-8 strings and codepoint conversions will occur. You'll want to use a single-byte encoding, like Latin-1. Here's how to convert between some representations:

        const arrayToString = arr => arr.reduce((str, code) => str + String.fromCharCode(code), '');
    
        const original = new Uint8Array([0x0a, 0x11, 0x00, 0xff, 0x30, 0x53, 0x22, 0x6a, 0x8f, 0x05, 0x16, 0x15, 0x88, 0xb5]);
        const originalString = arrayToString(original);
    
        // -> Hex 0a1100ff3053226a8f05161588b5
    
        const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Latin1.parse(originalString), 'secret');
        const encryptedArray32 = Uint32Array.from(encrypted.ciphertext.words);
        const encryptedArray8 = new Uint8Array(encryptedArray32.buffer);
    
        // -> Hex f6b58e23281ed5323e80d22e6fb94752
    
        const decryptedString = CryptoJS.AES.decrypt(encrypted, 'secret').toString(CryptoJS.enc.Latin1);
        const decryptedArray8 = Uint8Array.from(Buffer.from(decryptedString, 'latin1'));
    
        // -> Hex 0a1100ff3053226a8f05161588b5