Search code examples
iosswiftencryptionaes-gcmcryptoswift

AES/GCM/NoPadding encryption in Swift iOS (min deployment 12.1) doesn't produce expected ciphertext length


I'm implementing AES/GCM/NoPadding encryption in Swift for iOS with a minimum deployment target of 12.1. I'm trying to achieve similar functionality as my Java code below:

Cipher cipherAes = initCipher(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptedMessageByte = cipherAes.doFinal(messageBytes);

byte[] cipherByte = ByteBuffer.allocate(iv.length + salt.length + encryptedMessageByte.length)
        .put(iv)
        .put(salt)
        .put(encryptedMessageByte)
        .array();

In Swift, I've written the following code:

let gcm = GCM(iv: iv.bytes, additionalAuthenticatedData: nil, tagLength: tagLength)
let aes = try AES(key: key.bytes, blockMode: gcm, padding: .noPadding)
let encryptedMessage = try aes.encrypt(message.bytes)
let cipherData = Data(iv) + Data(salt) + Data(encryptedMessage)

However, the length of encryptedMessage in Swift differs from encryptedMessageByte in Java. I expected both to produce ciphertext of similar lengths.

Assurance: I'm sure that the lengths of key, iv, and salt are the same in both Java and Swift implementations.

Question: Are there any additional configurations or parameters needed for AES/GCM/NoPadding encryption in Swift to match the behavior in Java?


Solution

  • In the Java code, ciphertext and GCM authentication tag are concatenated by default; in the CryptoSwift code, both are handled detached by default.

    To make the CryptoSwift code compatible with Java code, either ciphertext and tag can be explicitly concatenated:

    ...
    let gcm = GCM(iv: iv.bytes, additionalAuthenticatedData: nil, tagLength: tagLength, mode: .detached) // detached is the default
    let aes = try! AES(key: key.bytes, blockMode: gcm, padding: .noPadding)
    let encryptedMessage = try! aes.encrypt(message.bytes)
    let tag = gcm.authenticationTag!
    let cipherData = Data(iv) + Data(salt) + Data(encryptedMessage) + Data(tag) // concatenate explicitly
    ...
    

    or encryption is done in combined mode, whereby ciphertext and tag are implicitly concatenated as well:

    ...
    let gcm = GCM(iv: iv.bytes, additionalAuthenticatedData: nil, tagLength: tagLength, mode: .combined) // apply combined mode
    let aes = try! AES(key: key.bytes, blockMode: gcm, padding: .noPadding)
    let encryptedMessageTag = try! aes.encrypt(message.bytes)
    let cipherData = Data(iv) + Data(salt) + Data(encryptedMessageTag)
    ...
    

    See also the AES-GCM examples in the documentation