Home > OS >  Swift CryptoKit AES Encryption and Javascript AES Decryption
Swift CryptoKit AES Encryption and Javascript AES Decryption

Time:07-25

I need to encrypt a value in an iOS app using CryptoKit and send it to a server, which will attempt to decrypt that value using a JavaScript Crypto Library (Right now, we're thinking CryptoJS, but open to other examples). There are a lot of questions on this topic, but I haven't come across any clear answers with updated examples, so I thought I'd describe my approach.

AES Encryption Using Swift

let key = "SomePrivateKey"
let dateToEncrypt = Date().toISOFormat().data(using: .utf8)
let val = try CryptoKit.AES.GCM.seal(
    datToEncrypt ?? Data(),
    using: .init(data: Array(key.utf8))
)
return val.ciphertext.base64EncodedString()

AES Decryption Using CryptoJS

var decrypted = CryptoJS.AES.decrypt(encrypted, myPassword);
return decrypted.toString(CryptoJS.enc.Utf8);

Expected Output

The decrypted string is an ISO Formatted Date

Actual Output

The decryption fails due to poorly formatted UTF-8 data, or the decryption succeeds but with a totally random value then what I expected (looks like a hex value of some sort)

CodePudding user response:

CryptoJS uses CBC mode by default, and doesn’t support GCM at all. You shouldn’t use CryptoJS at the best of times (the native and better-designed Web Crypto API is to be preferred), but especially not on the server, where Node.js has always had a native crypto module.

First, include the GCM nonce and tag, which are essential components:

return val.combined!.base64EncodedString()

Then, in Node.js, using the layout as described in the documentation for the combined property:

The data layout of the combined representation is: nonce, ciphertext, then tag.

// where `sealedBox` is a buffer obtained with `Buffer.from(encryptedString, 'base64')`
let nonce = sealedBox.slice(0, 12);
let ciphertext = sealedBox.slice(12, -16);
let tag = sealedBox.slice(-16);
let decipher = crypto.createDecipheriv('aes-128-gcm', key, nonce);
decipher.setAuthTag(tag);
let decrypted =
    Buffer.concat([decipher.update(ciphertext), decipher.final()])
    .toString('utf8');

Once that’s working, don’t forget to fix your key, because .init(data: Array(key.utf8)) is very uncomfortable (your AES keys should not be valid UTF-8).

  • If you’re starting with a password (for a good reason, not just because it seemed convenient), use a PBKDF to get key bytes. (Unfortunately, no PBKDF implementations are built into CryptoKit.)

    … but if the good reason is that it’s a user-provided password and you’re claiming to provide security, please get someone experienced with use of cryptography to review your work.

  • Otherwise, generate a random key safely and decode it from a Base64 or hex string. No UTF-8.

    node -p 'crypto.randomBytes(16).toString("base64")'
    

    And get someone experienced with use of cryptography to review your work anyway.

  • Related