swiftaescommoncrypto

Swift AES Common crypto - First 16 characters during AES decryption gets omitted


Trying to perform encryption and decryption using AES ( common crypto ) in swift, For some reason while decrypting the first 16 characters are getting trimmed off. Can anyone please look at the below playground code and suggest what could be going wrong here ?. The decodedString variable trims off the intial 16 characters of the original payloadString during decryption.

playground code

import UIKit
import CommonCrypto

class AESNew {

    enum MyError: LocalizedError {
        case first(message: String)
        case second(message: String)

        var errorDescription: String? { return "Some description here!" }
    }
    
    func encrypt(plainText: String, keyData:Data, iv: Data, options:Int = kCCOptionPKCS7Padding) -> Result<Data, Error> {
        let plainData = Data(plainText.utf8)
            
        if let cryptData = NSMutableData(length: Int((plainData.count)) + kCCBlockSizeAES128) {

            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)
            
            var numBytesEncrypted :size_t = 0
            
            //let value = iv.withUnsafeBytes { $0.load(as: UInt32.self) }
            
            let cryptResult = iv.withUnsafeBytes { (dataBytes : UnsafePointer<UInt8>) -> Result<Data, Error> in
                
                print(dataBytes)
                
                print(UnsafeRawPointer(dataBytes))
                
                let cryptStatus = CCCrypt(operation,
                                          algoritm,
                                          options,
                                          (keyData as NSData).bytes,
                                          keyLength,
                                          UnsafeRawPointer(dataBytes),
                                          (plainData as NSData).bytes, plainData.count,
                                          cryptData.mutableBytes, cryptData.length,
                                          &numBytesEncrypted)
                
                if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                    cryptData.length = Int(numBytesEncrypted)
                    let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
                    print("base64cryptString: \(base64cryptString)")
                    return .success(cryptData as Data)
                } else {
                    print("failure")
                    return .failure(MyError.first(message: "crypt failed"))
                }
            }
            
            return cryptResult
        
        }
        return .failure(MyError.second(message: "no value failure"))
    }

    // The iv is prefixed to the encrypted data
    func decrypt(data: Data, keyData: Data) throws -> Data? {
        let keyLength = keyData.count
        let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
        if validKeyLengths.contains(keyLength) == false {
            print("validKeyLengths does not match")
        }

        let ivSize = kCCBlockSizeAES128
        let clearLength = size_t(data.count - ivSize)
        var clearData = Data(count: clearLength)

        var numBytesDecrypted: size_t = 0
        let options   = CCOptions(kCCOptionPKCS7Padding)

        let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
            data.withUnsafeBytes {dataBytes in
                keyData.withUnsafeBytes {keyBytes in
                    CCCrypt(CCOperation(kCCDecrypt),
                            CCAlgorithm(kCCAlgorithmAES128),
                            options,
                            keyBytes,
                            keyLength,
                            dataBytes,
                            dataBytes+kCCBlockSizeAES128,
                            clearLength,
                            cryptBytes,
                            clearLength,
                            &numBytesDecrypted)
                }
            }
        }
        
        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            clearData.count = numBytesDecrypted
        } else {
            print("Decryption failed")
        }
        return clearData
    }
}

func randomGenerateBytes(count: Int) -> Data? {
    let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
    defer { bytes.deallocate() }
    let status = CCRandomGenerateBytes(bytes, count)
    guard status == kCCSuccess else { return nil }
    return Data(bytes: bytes, count: count)
}

var ivBytesNew = Data()

if let ivBytes = randomGenerateBytes(count: 16) {
    ivBytesNew = ivBytes
} else {
    print("randomGenerateBytes failed")
}

let keyString   = "keyData890123456"
let keyData   = "keyData890123456".data(using:String.Encoding.utf8)!

let payloadString = "asdflkasfdkaslfd12345"

let aesObject = AESNew()

let encrytedObject = aesObject.encrypt(plainText: payloadString, keyData: keyData, iv: ivBytesNew, options: kCCOptionPKCS7Padding)

var encryptedData = Data()

switch encrytedObject {
case .success(let encData):
    print(encData)
    encryptedData = encData
case .failure(let error):
    print(error)
default:
    print("enc failed")
}

if let decrypedData = try? aesObject.decrypt(data: encryptedData, keyData: keyData) {
    if let decodedString = String(data: decrypedData, encoding: .utf8) {
        print(decodedString) // 1234, first 16 characters are omitted. 
    } else {
        print("conversion from data to string failed")
    }
} else {
    print("decryption failed")
}

Solution

  • Seems you have picked up two different kinds of encrypt and decrypt.

    Do you see your decrypt does not have a parameter iv which is needed for AES decryption?

    Your decrypt expects first 16 bytes of data as iv and the rest as encrypted data. But your encrypt returns only encrypted data.

    Please try changing the line calling decrypt as follows:

    if let decrypedData = try? aesObject.decrypt(data: ivBytesNew + encryptedData, keyData: keyData) {
    

    Generally, your encrypt uses NSMutableData and handling bytes inappropriately. That is fragile and it might crash on some different context. I strongly recommend you not to use your current encrypt.

    Anyway, you should make encrypt and decrypt consistent, or sort of symmetric, currently they are not.