Search code examples
iosencryptionaes

Decrypting AES/CBC/PKCS5Padding in iOS


I have a file that has been encrypted on Android using this code:

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESUtils {
    private static final String IV_STRING = "123456789876543";
    private String key = "mysecretkey12345";

    public static byte[] encryptData(String key, byte[] byteContent) {
        byte[] encryptedBytes = null;
        try {
            byte[] enCodeFormat = key.getBytes();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
            byte[] initParam = IV_STRING.getBytes();
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
            encryptedBytes = cipher.doFinal(byteContent);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedBytes;
    }
    public static byte[] decryptData(String key, byte[] encryptedBytes) {
        byte[] result = null ;
        try {
            byte[] sEnCodeFormat = key.getBytes();
            SecretKeySpec secretKey = new SecretKeySpec(sEnCodeFormat, "AES");
            byte[] initParam = IV_STRING.getBytes();
            IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
            result = cipher.doFinal(encryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

I tried to reverse engineer the decryption in Swift using CommonCrypto like this:

import CommonCrypto

let keyStr:String = "mysecretkey12345"
let ivStr:String = "123456789876543"    

func aesDecrypt(data:NSData) -> Data? {
        let k:NSData = keyStr.data(using: .utf8)! as NSData
        let dbytes = data.bytes
        let kbytes=k.bytes
    
        if let keyData = keyStr.data(using: .utf8),
           let cryptData    = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {
                
                let keyLength              = size_t(kCCKeySizeAES128)
                let operation: CCOperation = UInt32(kCCDecrypt)
                let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
                let options:   CCOptions   = UInt32(kCCOptionPKCS7Padding)
                
                var numBytesEncrypted :size_t = 0
                
                let cryptStatus = CCCrypt(operation,
                    algoritm,
                    options,
                    kbytes, keyLength,
                    ivStr,
                    dbytes, data.length,
                    cryptData.mutableBytes, cryptData.length,
                    &numBytesEncrypted)
                
                if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                    cryptData.length = Int(numBytesEncrypted)
                    return (cryptData.copy() as! Data)
                }
                else {
                    return nil
                }
        }
        return nil
    }

I am quite new to encryption, but from my research I found that CC uses CBC by default and PKCS7Padding is basically identical to PKCS5Padding. However, the decryption does not deliver the results that I expect! The swift code was frankensteined together from various sources, including many solutions suggested here on stackoverflow. Main problem is that most examples use key and iv as Data, whereas I have strings - not sure my conversion causes problems. Secondly, many simply convert string messages, whereas I convert data (from files) directly - should not affect it too much, actually makes the code simpler avoiding data->string conversion. But since it doesn't work, what did I miss?


Solution

  • Okay, I think I figured the problem. Obviously, there is a problem with NSData.bytes and one should work withUnsafeBytes. Furthermore, my issue may was that the IV was not part of the data, as many examples assumed, so I missed 16 bytes when decrypting. The following code works for me, hope it will help someone!

    func decrypt(data: Data) -> Data {
        let key: Data = keyStr.data(using: .utf8) ?? Data()
        let iv: Data = ivStr.data(using: .utf8) ?? Data()
            
        if(keyStr.count == kCCKeySizeAES128){print("Key OKAY")} else {print("Key NOT okay")}
        if(ivStr.count == kCCBlockSizeAES128){print("IV OKAY")} else {print("IV NOT okay")}
        
        var buffer = Data(count: data.count)
    
        var numberBytesDecrypted: Int = 0
    
        let cryptStatus: CCCryptorStatus = key.withUnsafeBytes {keyBytes in
            data.withUnsafeBytes {dataBytes in
                buffer.withUnsafeMutableBytes {bufferBytes in iv.withUnsafeBytes {ivBytes in
                    CCCrypt(         // Stateless, one-shot encrypt operation
                        CCOperation(kCCDecrypt),                        // op: CCOperation
                        CCAlgorithm(kCCAlgorithmAES128),                // alg: CCAlgorithm
                        CCOptions(kCCOptionPKCS7Padding),                                        // options: CCOptions
                        keyBytes.baseAddress,                           // key: the "password"
                        key.count,                                      // keyLength: the "password" size
                        ivBytes.baseAddress,                          // iv: Initialization Vector
                        dataBytes.baseAddress!,    // dataIn: Data to decrypt bytes
                        data.count,                                     // dataInLength: Data to decrypt size
                        bufferBytes.baseAddress,                        // dataOut: decrypted Data buffer
                        data.count,                                     // dataOutAvailable: decrypted Data buffer size
                        &numberBytesDecrypted                           // dataOutMoved: the number of bytes written
                    )}
                }
            }
        }
        
        if(cryptStatus == CCCryptorStatus(kCCSuccess)){
            return buffer[..<numberBytesDecrypted]
        } else {
            print("Decryption failed")
            return data
        }
    }