Search code examples
swiftencryptionaescryptojscbc-mode

Encryption with Crypto.js and cryptoswift generates different encryption cipher text


I am trying to generate encrypted text in Node using Crypto.js using its AES algorithm. It works in Js for encryption and decryption both. Similarly I tried implementing same in Swift using CryptoSwift and it does encryption and decryption both in swift. But the problem arises when I want to use encrypted cipher text from JS to decrypt in Swift. The problem is that the encrypted text generated in JS is different from what is generated in Swift. The mode is CBC and padding is PKCS7.

Swift Code:

 func aes256CBC(key: String, input: String) -> String? {
    guard let keyData = key.data(using: .utf8), let inputData = input.data(using: .utf8) else {
        return nil
    }

    let salt = generateSalt(length: 8)
    let iv = generateSalt(length: 16)
    
    let key = try! PKCS5.PBKDF2(password: keyData.bytes, salt: salt.bytes, iterations: 1, keyLength: 32, variant: .md5).calculate()
        
    let aes = try! AES(key: key, blockMode: CBC(iv: iv.bytes), padding: .pkcs7)

    let encrypted = try! aes.encrypt(inputData.bytes)

    let result = Data(salt + iv + encrypted)

    return result.base64EncodedString()
}

func generateSalt(length: Int) -> Data {
    var salt = Data(count: length)
    let result = salt.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!)
    }
    if result != errSecSuccess {
        fatalError("Failed to generate salt. SecRandomCopyBytes status: \(result)")
    }
    return salt
}

For checking, I am using this site, as output generated by my js is same as this url generates.

input = "test" key = "password"

JS Output: encryptedString = "U2FsdGVkX18087rLuHfWYc9JmmhZL4jJD/Pu8IGuL/E="

Swift Output: RtWc9mSXPY7/6A6x/9OTE7rhBJqEWxqEYkKe33VCpnesN1Q4wujpww==


Solution

  • Below code is solution for my issue: I done this my using CommonCrypto package and calculate pbkdf2 using required key size and iteration.

    func encrypt(_ plainText: String, keyData: Data? = nil, ivData: Data? = nil) -> String? {
            var encryptedData = Data(count: plainText.count + kCCBlockSizeAES128)
            var encryptedLength: size_t = 0
            
            let iv = ivData ?? (Data(hex: Constant.textIV.onlyReveal()) ?? Data())
            let key = keyData ?? (pbkdf2() ?? Data())
            
            let status = key.withUnsafeBytes { keyBytes in
                iv.withUnsafeBytes { ivBytes in
                    plainText.withCString { plainTextBytes in
                        CCCrypt(
                            CCOperation(kCCEncrypt),
                            CCAlgorithm(kCCAlgorithmAES),
                            CCOptions(kCCOptionPKCS7Padding),
                            keyBytes.baseAddress,
                            key.count,
                            ivBytes.baseAddress,
                            plainTextBytes,
                            plainText.count,
                            encryptedData.withUnsafeMutableBytes { $0.baseAddress },
                            encryptedData.count,
                            &encryptedLength
                        )
                    }
                }
            }
            
            if status == kCCSuccess {
                encryptedData.removeSubrange(encryptedLength..<encryptedData.count)
                return encryptedData.base64EncodedString()
            } else {
                return nil
            }
        }