Search code examples
javascriptobjective-cencryptioncryptographycryptojs

Encrypt with CryptoJS and decrypt from Objective-C


In communication between two applications, I'd like to encrypt a piece of information in JavaScript and decrypt the message from an Objective-C client using a fixed key (just for basic security).

Encryption works well:

var command = "mjallo";
var crypto_key = CryptoJS.enc.Base64.parse('280f8bb8c43d532f389ef0e2a5321220');
var crypto_iv  = CryptoJS.enc.Base64.parse("CC0A69779E15780A");
// Encrypt and encode
var encrypted = CryptoJS.AES.encrypt(command, crypto_key, {iv: crypto_iv}).toString();
var encrypted_and_encoded = btoa(encrypted);
// encrypted_and_encoded => 'dFBQVDZZS3dGSktoa0J3Y1NQOElpZz09'

// Confirms that decrypt works with CryptoJS:
// Decode and decrypt
var decrypted = CryptoJS.AES.decrypt(atob(encrypted_and_encoded), crypto_key, {iv: crypto_iv});
// decrypted => 'mjallo'

How would you go about decoding and decrypting the message in Objective-c after it was encrypted by CryptoJS?

I've attempted to decrypt using CocoaSecurity, but with no luck. Following is RubyMotion syntax:

  begin
    res = CocoaSecurity.aesDecryptWithBase64('dFBQVDZZS3dGSktoa0J3Y1NQOElpZz09', hexKey: '280f8bb8c43d532f389ef0e2a5321220', hexIv: 'CC0A69779E15780A')
  rescue NSException => e
    p e.reason # => "Length of iv is wrong. Length of iv should be 16(128bits)"
  end

Solution

  • AES supports a block size of 128 bit and key sizes of 128, 192 and 256 bit. The IV for CBC mode (which is the default) should be 128 bit.

    Your encoded key consists of 32 characters. In CryptoJS you're parsing it as Base64 which results in a 192 bit key, but in CocoaSecurity you're assuming that it is Hex encoded. Since it only contains digits and the letters a to f, it's likely Hex encoded and not Base64 encoded. If one would assume that it is Hex encoded, then one would get a valid AES key size of 128 bit:

    var crypto_key = CryptoJS.enc.Hex.parse('280f8bb8c43d532f389ef0e2a5321220');
    

    Your IV on the other hand doesn't have a valid size under the same assumption. An IV should be 16 bytes long for AES in CBC mode. Additionally, an IV should never be fixed at a static value. You would need to generate a random IV for every encryption. Since the IV doesn't have to be secret, you can send it along with the ciphertext.

    var crypto_iv  = CryptoJS.lib.WordArray.random(128/8);
    console.log("IV: " + crypto_iv.toString()); // hex encoded
    

    The result of CryptoJS.<Cipher>.encrypt() is a special formattable object. If you call toString() on that object, you will get a Base64 encoded ciphertext (optionally with a salt when password-based encryption was used). But then you're encoding it again with Base64 by calling btoa(). You don't need to encode it twice.

    var encrypted = CryptoJS.AES.encrypt(command, crypto_key, {iv: crypto_iv}).toString();
    console.log("Ciphertext (Base64): " + encrypted.toString());
    console.log("Ciphertext (Hex): " + encrypted.ciphertext.toString());
    

    As far I can judge, your RubyMotion code looks fine.


    If you can only change the CocoaSecurity code, then you will need to

    • re-encode the key by decoding it as Base64 and encoding it as Hex,
    • append 16 "0" characters to the IV hex string, because CryptoJS fills the IV up to the next valid IV with 0x00 bytes,
    • decode the ciphertext once from Base64.

    You should always authenticate the ciphertexts. This can either be done with an authenticated mode like GCM or with an HMAC over the ciphertext.