Nah
Nah

Reputation: 1768

CommonCrypto Encryption Decryption missmatch

Does anyone see what is going wrong in the below code, as the code below works well when encrypting and sending to the server (which means my encryption function is fine), however there seems some problem with my decryption logic which is not decrypting the information correctly.

Note: The server side enc/dec logic works well with other languages e.g. Java

Server-side implementation sample: Nodejs Crypto to Swift commonCrypto

MyEncDec.swift

import Foundation
import CommonCrypto
struct AES256 {
private var key: Data
private var iv: Data
public init(key: Data, iv: Data) throws {
    guard key.count == kCCKeySizeAES256 else {
        throw Error.badKeyLength
    }
    guard iv.count == kCCBlockSizeAES128 else {
        throw Error.badInputVectorLength
    }
    self.key = key
    self.iv = iv
}
enum Error: Swift.Error {
    case keyGeneration(status: Int)
    case cryptoFailed(status: CCCryptorStatus)
    case badKeyLength
    case badInputVectorLength
}
func encrypt(_ digest: Data) throws -> Data {
    return try crypt(input: digest, operation: CCOperation(kCCEncrypt))
}
func decrypt(_ encrypted: Data) throws -> Data {
    return try crypt(input: encrypted, operation: CCOperation(kCCDecrypt))
}
private func crypt(input: Data, operation: CCOperation) throws -> Data {
    var outLength = Int(0)
    var outBytes = [UInt8](repeating: 0, count: input.count + kCCBlockSizeAES128)
    var status: CCCryptorStatus = CCCryptorStatus(kCCSuccess)
    input.withUnsafeBytes { rawBufferPointer in
        let encryptedBytes = rawBufferPointer.baseAddress!
        iv.withUnsafeBytes { rawBufferPointer in
            let ivBytes = rawBufferPointer.baseAddress!
            key.withUnsafeBytes { rawBufferPointer in
                let keyBytes = rawBufferPointer.baseAddress!
                status = CCCrypt(operation,
                                 CCAlgorithm(kCCAlgorithmAES128),            // algorithm
                                 CCOptions(kCCOptionPKCS7Padding),           // options
                                 keyBytes,                                   // key
                                 key.count,                                  // keylength
                                 ivBytes,                                    // iv
                                 encryptedBytes,                             // dataIn
                                 input.count,                                // dataInLength
                                 &outBytes,                                  // dataOut
                                 outBytes.count,                             // dataOutAvailable
                                 &outLength)                                 // dataOutMoved
            }
        }
    }
    guard status == kCCSuccess else {
        throw Error.cryptoFailed(status: status)
    }
    return Data(bytes: &outBytes, count: outLength)
}
static func createKey(password: Data, salt: Data) throws -> Data {
    let length = kCCKeySizeAES256
    var status = Int32(0)
    var derivedBytes = [UInt8](repeating: 0, count: length)
    password.withUnsafeBytes { rawBufferPointer in
        let passwordRawBytes = rawBufferPointer.baseAddress!
        let passwordBytes = passwordRawBytes.assumingMemoryBound(to: Int8.self)
        salt.withUnsafeBytes { rawBufferPointer in
            let saltRawBytes = rawBufferPointer.baseAddress!
            let saltBytes = saltRawBytes.assumingMemoryBound(to: UInt8.self)
            status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),                  // algorithm
                                          passwordBytes,                                // password
                                          password.count,                               // passwordLen
                                          saltBytes,                                    // salt
                                          salt.count,                                   // saltLen
                                          CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),   // prf
                                          10000,                                        // rounds
                                          &derivedBytes,                                // derivedKey
                                          length)                                       // derivedKeyLen
        }
    }
    guard status == 0 else {
        throw Error.keyGeneration(status: Int(status))
    }
    return Data(bytes: &derivedBytes, count: length)
}
static func randomIv() -> Data {
    return randomData(length: kCCBlockSizeAES128)
}
static func iV() -> Data {
    let arr: [UInt8] = [0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1]
    return Data(arr)
}
static func randomSalt() -> Data {
    return randomData(length: 8)
}
static func randomData(length: Int) -> Data {
    var data = Data(count: length)
    var mutableBytes: UnsafeMutableRawPointer!
    data.withUnsafeMutableBytes { rawBufferPointer in
        mutableBytes = rawBufferPointer.baseAddress!
    }
    let status = SecRandomCopyBytes(kSecRandomDefault, length, mutableBytes)
    assert(status == Int32(0))
    return data
}
}

Upvotes: 1

Views: 507

Answers (2)

cristallo
cristallo

Reputation: 2089

I've just try to run your client side code encrypting and decrypting a sample data and it seems to work properly. Is the issue present only when you are trying to decrypt a data buffer coming from the server?

I've tested your code in the following way:

    let keyData = Data("KEY01234567890123456789012345678".utf8)
    let data = Data("TEST123".utf8)
    
    let iv = AES256.randomIv()
    if let aes = try? AES256.init(key: keyData, iv: iv), let aes2 = try? AES256.init(key: keyData, iv: iv) {
        if let encriptedData = try? aes.encrypt(data) {
            if let decryptedData = try? aes2.decrypt(encriptedData) {
                let decryptedString = String(decoding: decryptedData, as: UTF8.self)
                print(decryptedString)
            }
        }
    }

    

I am not sure about how you are using the AES256 class but taking a look to the server side code:

The encryption function generates a string composition of the iv and the data using ":" as separator.

let final_encrypted = iv.toString('hex') + ':' + encrypted.toString('hex');

So the string coming from the server has to be parsed before decrypting it in order to retrieve iv and data.

func parseAndDecrypt(encryptedString: String) {
        let keyData = Data("KEY01234567890123456789012345678".utf8)
        let substrings = encryptedString.split(separator: ":")
        if let ivString = substrings.first, let dataString = substrings.last {
            let iv = Data(ivString.utf8)
            let encryptedData = Data(dataString.utf8)
            if let aes = try? AES256.init(key: keyData, iv: iv) {
                if let decryptedData = try? aes.decrypt(encryptedData) {
                    let decryptedString = String(decoding: decryptedData, as: UTF8.self)
                    print(decryptedString)
                }
            }
        }
    }

Upvotes: 1

Afshin
Afshin

Reputation: 9173

I need to take a guess since you have not provided the code that runs encrypt and decrypt. But I think that you provide iv using randomIV() on both encryption and decryption side and this will be the problem.

You need to provide same iv for decryption and encryption side. It means that you need to send your random iv from encryption side to decryption side and use that iv in decryption process.

I'm not experienced in Swift, But I think you need to use your code like this in order to works correctly:

// Encryption side
    let keyData = Data("KEY01234567890123456789012345678".utf8)
    let data = Data("TEST123".utf8)
    
    let iv = AES256.randomIv()
    if let aes = try? AES256.init(key: keyData, iv: iv) {
        if let encryptedData = try? aes.encrypt(data) {
            var resultData = iv
            resultData.append(encryptedData)
            // use resultData here
        }
    }

// Decryption side
    let keyData = Data("KEY01234567890123456789012345678".utf8)
    let data = resultData
    
    let iv = data.subdata(in: ..<kCCBlockSizeAES128)
    let encryptedPart = data.subdata(in: kCCBlockSizeAES128...)
    if let aes = try? AES256.init(key: keyData, iv: iv) {
        if let decryptedData = try? aes.decrypt(encryptedPart) {
            // use decryptedData here
        }
    }

I used cristallo code as base for this code.

Upvotes: 2

Related Questions