1

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
}
}
Nah
  • 1,690
  • 2
  • 26
  • 46
  • 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? – cristallo Aug 25 '21 at 13:17

2 Answers2

2

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.

Afshin
  • 8,839
  • 1
  • 18
  • 53
  • Not *similar* but *the same* initialization vector. – Sulthan Aug 25 '21 at 14:34
  • @afshin as I mentioned the server-side decryption reference link (posted in a separate question) is decrypting without getting any pre-saved `IV` so the same should be manageable in Swift. – Nah Aug 25 '21 at 17:19
  • @Nah if you look at that post carefully, you see something like this: `let final_encrypted = iv.toString('hex') + ':' + encrypted.toString('hex');` he practically sends `iv` to decryption side by adding it to encrypted data. But you don't do something similar. – Afshin Aug 25 '21 at 17:27
  • Yes you got it right, it is being passed with every encrypted string. I am not familiar to Swift so really appreciate if you can share correct version and implementation in swift so it can work well with server side. – Nah Aug 26 '21 at 06:38
  • @Nah I updated answer, but because I'm not experienced in Swift, it will probably be buggy. But you can use the idea behind it. – Afshin Aug 26 '21 at 07:59
1

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)
                }
            }
        }
    }
cristallo
  • 1,951
  • 2
  • 25
  • 42
  • yes the issue is with decrypting the information coming from the server-side. There is no point of having static IV as that is not even being used in server-side implementation (reference link available in question). Yes, the encrypted string (in swift) is being correctly decrypted on server side so that the encryption part is perfect. – Nah Aug 25 '21 at 17:22