3

I'm trying to encrypt a string in Swift 3, and my encryption is giving different output every time. Why is that? (I have tried similar encryption in python, and the encrypted output is always the same).

Here is my Swift 3 aesEncrypt function:

func aesEncrypt(key:String, iv:Array<Any>, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = sha256(string:key),
        let data = self.data(using: String.Encoding.utf8),
        let cryptData    = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {

        let keyLength              = size_t(kCCKeySizeAES128)
        let operation: CCOperation = UInt32(kCCEncrypt)
        let algorithm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
        let options:   CCOptions   = UInt32(options)

        var numBytesEncrypted :size_t = 0

        let cryptStatus = CCCrypt(operation,
                                  algorithm,
                                  options,
                                  (keyData as NSData).bytes, keyLength,
                                  iv,
                                  (data as NSData).bytes, data.count,
                                  cryptData.mutableBytes, cryptData.length,
                                  &numBytesEncrypted)
        // ADDED PRINT STATEMENTS 
        print("keyData")
        print(keyData)
        print("\(keyData as NSData)")
        print("iv")
        print(iv)
        var hex_iv = toHexString(arr: iv as! [UInt8])
        print(hex_iv)
        print("data")
        print(data)
        print("\(data as NSData)")

        print("encryption: cryptdata")
        print(cryptData)

        print("encryption: num bytes encrypted")
        print(numBytesEncrypted)

        if UInt32(cryptStatus) == UInt32(kCCSuccess) {
            cryptData.length = Int(numBytesEncrypted)
            let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
            return base64cryptString
        }
        else {
            return nil
        }
    }
    return nil
}

When I try to run the following code with initial_string = "hello", I get different encrypted output strings every time.

let iv [UInt8](repeating: 0, count: 16)
let key = "sample_key"
let initial_string = "hello"

let encryptedString = initial_string.aesEncrypt(key: key, iv: iv)
print("Encrypted string")
print(encryptedString)

sample output from running code with "hello" string first time:

keyData
32 bytes
<d5a78c66 e9b3ed40 b3a92480 c732527f 1a919fdc f68957d2 b7e9218f 6221085d>
iv
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
data
5 bytes
<68656c6c 6f>
encryption: cryptdata
<b17d67fc 26e3f316 6a2bdfbf 9d387c2d 00000000 00>
encryption: num bytes encrypted
16
Encrypted string
Optional("sX1n/Cbj8xZqK9+/nTh8LQ==")

sample output from running code with "hello" string second time:

keyData
32 bytes
<d5a78c66 e9b3ed40 b3a92480 c732527f 1a919fdc f68957d2 b7e9218f 6221085d>
iv
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
data
5 bytes
<68656c6c 6f>
encryption: cryptdata
<01b9f69b 45deb31d eda46c2d dc9ad9e8 00000000 00>
encryption: num bytes encrypted
16
Encrypted string
Optional("Abn2m0Xesx3tpGwt3JrZ6A==")

Can you tell me why the output is different every time for the same key, iv, and string? Thanks!

Maya Smith
  • 33
  • 5
  • Have a look at the code in https://stackoverflow.com/a/37681510/1187415, that should work. – Martin R Dec 08 '17 at 20:23
  • The code is an extension but the extension code is missing. But in answer to your question: because the input is different each time. Print `keyData`, `data` and `iv` just prior to calling and `cryptData` just after calling `CCCrypt` -- in hex. Add that to the question but my guess is you will find the problem there. – zaph Dec 09 '17 at 01:13
  • @zaph i just added print statements, let me know if that helps! – Maya Smith Dec 09 '17 at 18:09

2 Answers2

0

Disclaimer: I can't run the question code. Among other things it is not complete, the extension declaration is missing. Also it seems to be Swift 2 code, this needs to be at least updated to Swift 3.

encryption: cryptdata
<01b9f69b 45deb31d eda46c2d dc9ad9e8 00000000 00>

Is completely wrong, it is even the wrong length Encrypted data will be a multiple of the block size.

With PKCS#7 padding and CBC mode the encrypted result should be: C99A30D8DA44968418E8B66F42790216. See Cyyptomathic AES CALCULATOR. Note the 0b0b0b0b0b0b0b0b0b0b0b is the PKCS#7 padding.

Here is an example in Swift 3, this is not production code, it is missing error handling at a minimum.

func SHA256(string:String) -> Data {
    let data = string.data(using:.utf8)!
    var hashData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))

    _ = hashData.withUnsafeMutableBytes {digestBytes in
        data.withUnsafeBytes {messageBytes in
            CC_SHA256(messageBytes, CC_LONG(data.count), digestBytes)
        }
    }
    return hashData
}

func aesCBCEncrypt(data:Data, keyData:Data, ivData:Data) -> Data {
    let cryptLength = size_t(kCCBlockSizeAES128 + data.count + kCCBlockSizeAES128)
    var cryptData   = Data(count:cryptLength)
    var numBytesEncrypted :size_t = 0

    let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
        data.withUnsafeBytes {dataBytes in
            keyData.withUnsafeBytes {keyBytes in
                ivData.withUnsafeBytes {ivBytes in
                    CCCrypt(CCOperation(kCCEncrypt),
                            CCAlgorithm(kCCAlgorithmAES),
                            CCOptions(kCCOptionPKCS7Padding),
                            keyBytes, keyData.count,
                            ivBytes,
                            dataBytes, data.count,
                            cryptBytes, cryptLength,
                            &numBytesEncrypted)
                }}}}

    cryptData.count = (cryptStatus == kCCSuccess) ? numBytesEncrypted : 0

    return cryptData;
}


let keyString = "sample_key"
let keyData = SHA256(string:keyString)
print("keyString: \(keyString)")
print("keyData:   \(hexEncode(keyData))")

let clearData = hexDecode("68656c6c6f")
// let keyData   = hexDecode("d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d")
let ivData    = hexDecode("00000000000000000000000000000000")

print("clearData: \(hexEncode(clearData))")
print("keyData:   \(hexEncode(keyData))")
print("ivData:    \(hexEncode(ivData))")

let cryptData = aesCBCEncrypt(data:clearData, keyData:keyData, ivData:ivData)
print("cryptData: \(hexEncode(cryptData))")

Output:

keyString: sample_key  
keyData:   d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d

clearData: 68656c6c6f
keyData:   d5a78c66e9b3ed40b3a92480c732527f1a919fdcf68957d2b7e9218f6221085d
ivData:    00000000000000000000000000000000
cryptData: c99a30d8da44968418e8b66f42790216

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Thanks @zaph! But how are you getting the keydata? I wanted to hash the key string "sample_key" with sha256. – Maya Smith Dec 10 '17 at 01:00
  • I just the keydata it from the question. It seems the question was getting different results with the same key. Creating a key from SHA is a separate issue and it not generally a good solution,PBKDF2 is the commonly used key derivation function used. – zaph Dec 10 '17 at 14:00
  • Added SHA256 `keyData` generation. But key derivation should be done with a KDF such as PBKDF2. – zaph Dec 10 '17 at 14:52
  • Thanks @zaph, that was extremely helpful. I have accepted your answer. – Maya Smith Dec 11 '17 at 20:23
-1

you see that IV? that is initialization vector, that functions as a counter to make you encryption different each time, making it more secure and hard to break. so basically your code is working fine, but to decrypt it correctly, the receiver can not act only by having Key , but needs IV also

Danii-Sh
  • 447
  • 1
  • 4
  • 18