skip to Main Content

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?

2

Answers


  1. To ensure that your Swift implementation of AES/GCM/NoPadding encryption matches the behavior of your Java implementation, it’s essential to verify several aspects, including the correct setup of the key, IV, and encryption parameters. Both languages should produce the same ciphertext length when given the same input, provided that the AES/GCM setup is identical.

    Here’s a detailed step-by-step comparison and setup to help you achieve consistent encryption results between Java and Swift:

    Java Code

    Let’s first review your Java encryption setup to ensure all parameters are clearly defined:

    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.ByteBuffer;
    import java.security.SecureRandom;
    
    public class AesGcmEncryption {
        private static final int GCM_IV_LENGTH = 12;
        private static final int GCM_TAG_LENGTH = 16;
        private static final int AES_KEY_SIZE = 256;
    
        public static void main(String[] args) throws Exception {
            // Generate AES key
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(AES_KEY_SIZE);
            SecretKey key = keyGen.generateKey();
    
            // Generate IV
            byte[] iv = new byte[GCM_IV_LENGTH];
            SecureRandom random = new SecureRandom();
            random.nextBytes(iv);
    
            // Message to encrypt
            byte[] messageBytes = "Hello, World!".getBytes();
    
            // Encrypt
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, spec);
            byte[] encryptedMessageByte = cipher.doFinal(messageBytes);
    
            // Combine IV, salt (if any), and ciphertext
            byte[] salt = new byte[16]; // Assuming a 16-byte salt for example
            random.nextBytes(salt);
    
            byte[] cipherByte = ByteBuffer.allocate(iv.length + salt.length + encryptedMessageByte.length)
                    .put(iv)
                    .put(salt)
                    .put(encryptedMessageByte)
                    .array();
    
            // Print lengths
            System.out.println("IV Length: " + iv.length);
            System.out.println("Salt Length: " + salt.length);
            System.out.println("Encrypted Message Byte Length: " + encryptedMessageByte.length);
            System.out.println("Cipher Byte Length: " + cipherByte.length);
        }
    }
    

    Swift Code

    Now, let’s ensure your Swift code matches the Java setup:

    1. Ensure AES/GCM Configuration: The key, IV, and GCM tag lengths should be consistent.
    2. Combine IV, salt, and encrypted message: Make sure the final concatenated data structure is the same.

    Here’s the revised Swift code:

    import Foundation
    import CryptoSwift
    
    // Constants
    let gcmTagLength = 16
    let ivLength = 12
    let saltLength = 16
    
    // Generate key (256 bits)
    let key = Data((0..<32).map { _ in UInt8.random(in: 0...255) })
    
    // Generate IV
    let iv = Data((0..<ivLength).map { _ in UInt8.random(in: 0...255) })
    
    // Message to encrypt
    let message = "Hello, World!".data(using: .utf8)!
    
    // Generate salt
    let salt = Data((0..<saltLength).map { _ in UInt8.random(in: 0...255) })
    
    do {
        // Setup GCM
        let gcm = GCM(iv: iv.bytes, additionalAuthenticatedData: nil, tagLength: gcmTagLength)
        let aes = try AES(key: key.bytes, blockMode: gcm, padding: .noPadding)
        
        // Encrypt
        let encryptedMessage = try aes.encrypt(message.bytes)
        
        // Combine IV, salt, and encrypted message
        let cipherData = iv + salt + Data(encryptedMessage)
        
        // Print lengths
        print("IV Length: (iv.count)")
        print("Salt Length: (salt.count)")
        print("Encrypted Message Length: (encryptedMessage.count)")
        print("Cipher Data Length: (cipherData.count)")
        
        // Output cipher data for debugging
        print("Cipher Data: (cipherData.base64EncodedString())")
        
    } catch {
        print("Encryption failed: (error)")
    }
    

    Key Points to Verify

    1. Key Length: Ensure the key length is 256 bits (32 bytes) in both implementations.
    2. IV Length: Ensure the IV length is 12 bytes.
    3. Tag Length: The GCM tag length should be 16 bytes.
    4. Message Encoding: Verify that the message encoding is the same (e.g., UTF-8).

    Troubleshooting

    • Ensure Consistent Libraries: Verify that the Swift CryptoSwift library is being used correctly and supports the required AES/GCM functionality.
    • Compare Outputs: Print the base64-encoded output of the encrypted message in both Java and Swift to verify they produce the same results for the same inputs.
    • Debug Steps: If the lengths differ, break down the process to compare intermediate steps such as the encrypted message before concatenating IV and salt.

    By closely aligning the parameters and verifying each step, you should be able to achieve consistent encryption results between your Java and Swift implementations.

    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search