How Is Commoncrypto Used in Swift3

Swift 3 import CommonCrypto

I know there are similar questions on stackoverflow about this but I looked at them and still had problems so I wanted to share my experiences.

Easiest way to import the Obj-C CommonCrypto library to an existing Swift XCode Project (Swift 3, Xcode 8.3.3):

  1. Add a new file of type "Objective-C file". It doesn't matter what you call it, you will delete it in a moment.
  2. After you add that file Xcode should prompt you if you want to create a bridging header. Check the appropriate targets for you project and allow Xcode to create the bridging header for you.
  3. Add #import < CommonCrypto/CommonCrypto.h > to the bridging header(s) it creates.
  4. Delete the Objective-C file you created in step 1.

I tried to create my own Objective-C bridging file and it wasn't working. I spent about an hour looking for solutions until I tried this. I wanted to share to hopefully spare other developers the issue I had.

Decrypting DES with CommonCrypto in Swift 3

When you encrypt or decrypt from String to String, you need 3 steps, as modern encryption algorithms work on only binary data.

Encoding:

[original String]
↓(encode in UTF-8)
[original binary]
↓(encrypt)
[encrypted binary]
↓(encode in base64)
[encrypted String]

(I guess you have a base64 encoded String as your == in the encrypted String is suggesting.)

So, when decoding, you need all these steps in reverse.

Decoding:

[encrypted String]
↓(decode in base64)
[encrypted binary]
↓(decrypt)
[original binary]
↓(decode in UTF-8)
[original String]

You are doing the first step of decoding in a wrong way. (See #1 of the code below.)


One more, when you want to receive data into mutable (var) Data, set count (not only capacity) of the Data. (#2 and #3)


UPDATED
And, as told by zaph, you need to specify IV for CBC mode (default) or use ECB mode (#4).

Your code should be something like this:

if
let key = "12345678".data(using: .utf8),
let data = Data(base64Encoded: encrypted, options: .ignoreUnknownCharacters) //<-#1
{
var numBytesDecrypted: size_t = 0
var result = Data(count: data.count) //<-#2

let err = result.withUnsafeMutableBytes {resultBytes in
data.withUnsafeBytes {dataBytes in
key.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCDecrypt), CCAlgorithm(kCCAlgorithmDES), CCOptions(kCCOptionPKCS7Padding|kCCModeECB), keyBytes, kCCKeySizeDES, nil, dataBytes, data.count, resultBytes, result.count, &numBytesDecrypted) //<-#4
}
}
}

if err != CCCryptorStatus(kCCSuccess) {
NSLog("Decryption failed! Error: \(err.description)")
}

print(numBytesDecrypted)
result.count = numBytesDecrypted //<-#3
print(result as NSData) //`as NSData` is good for debugging.

return String(data: result, encoding: .utf8) ?? "???"
}
return "???"

common crypto in swift 2.3 and Xcode8.1

If you are looking for a complete secure solution that integrate rather easily use RNCryptor.

If you are looking for a partial solution, that is just AES encryption without password derivation or authentication look at this example implementation:

Example from sunsetted documentation section:

AES encryption in CBC mode with a random IV (Swift 3+)

The iv is prefixed to the encrypted data

aesCBC128Encrypt will create a random IV and prefixed to the encrypted code.
aesCBC128Decrypt will use the prefixed IV during decryption.

Inputs are the data and key are Data objects. If an encoded form such as Base64 if required convert to and/or from in the calling method.

The key should be exactly 128-bits (16-bytes). For other key sizes see the Swift 3.0 example.

PKCS#7 padding is set by default.

This example requires Common Crypto
It is necessary to have a bridging header to the project:

import

Add the Security.framework to the project.

See Swift 3 example for notes.

This is example, not production code.

func aesCBC128Encrypt(data data:[UInt8], keyData:[UInt8]) -> [UInt8]? {
let keyLength = size_t(kCCKeySizeAES128)
let ivLength = size_t(kCCBlockSizeAES128)
let cryptDataLength = size_t(data.count + kCCBlockSizeAES128)
var cryptData = [UInt8](count:ivLength + cryptDataLength, repeatedValue:0)

let status = SecRandomCopyBytes(kSecRandomDefault, Int(ivLength), UnsafeMutablePointer<UInt8>(cryptData));
if (status != 0) {
print("IV Error, errno: \(status)")
return nil
}

var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES128),
CCOptions(kCCOptionPKCS7Padding),
keyData, keyLength,
cryptData,
data, data.count,
&cryptData + ivLength, cryptDataLength,
&numBytesEncrypted)

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.removeRange(numBytesEncrypted+ivLength..<cryptData.count)
}
else {
print("Error: \(cryptStatus)")
return nil;
}

return cryptData;
}

func aesCBC128Decrypt(data data:[UInt8], keyData:[UInt8]) -> [UInt8]? {
let clearLength = size_t(data.count)
var clearData = [UInt8](count:clearLength, repeatedValue:0)

let keyLength = size_t(kCCKeySizeAES128)
let ivLength = size_t(kCCBlockSizeAES128)

var numBytesDecrypted :size_t = 0
let cryptStatus = CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES128),
CCOptions(kCCOptionPKCS7Padding),
keyData, keyLength,
data,
UnsafePointer<UInt8>(data) + ivLength, data.count - ivLength,
&clearData, clearLength,
&numBytesDecrypted)

if UInt32(cryptStatus) == UInt32(kCCSuccess) {
clearData.removeRange(numBytesDecrypted..<clearLength)

} else {
print("Error: \(cryptStatus)")
return nil;
}

return clearData;
}

Example usage:

let clearData = toData("clearData0123456")
let keyData = toData("keyData890123456")

print("clearData: \(toHex(clearData))")
print("keyData: \(toHex(keyData))")
let cryptData = aesCBC128Encrypt(data:clearData, keyData:keyData)!
print("cryptData: \(toHex(cryptData))")
let decryptData = aesCBC128Decrypt(data:cryptData, keyData:keyData)!
print("decryptData: \(toHex(decryptData))")

Example Output:

clearData:   <636c6561 72446174 61303132 33343536>
keyData: <6b657944 61746138 39303132 33343536>
cryptData: <9fce4323 830e3734 93dd93bf e464f72a a653a3a5 2c40d5ea e90c1017 958750a7 ff094c53 6a81b458 b1fbd6d4 1f583298>
decryptData: <636c6561 72446174 61303132 33343536>

Issue decrypting with CommonCrypto in Swift

The difference is that NSData+AESCrypt.m is using CBC mode (the default) with an iv of NULL. The code in the question is using ECB mode.

Best practice is to use CBC mode with a random iv. The iv is typically pre-pended to the encrypted data so the decryption can separate the iv and data prior to decryption.

Do not use NSData+AESCrypt.m, it has not been maintained, is a category on NSData, does not support ARC. Consider RNCryptor for Objective-C, it is actively maintained.

This is the change I made in "NSData+AESCrypt.m", method AES256EncryptWithKey: kCCOptionPKCS7Padding + kCCOptionECBMode. I added kCCOptionECBMode, that's all.

Here is the call I made:
NSString *keyString = @"12345678901234567890123456789012";

NSString *message = @"Don´t try to read this text. Top Secret Stuff";
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];

NSData *crypData = [data AES256EncryptWithKey:keyString];
NSLog(@"crypData: %@", crypData);

Output:

crypData: <118a32dc c23f7caa 883abc3c 1c7f0770 e200016b 2737acfa 17bb96fb a02b02a7 c147603b 06acd863 94bb8ff2 6cb14515>

Which is the same as from the above code (the same as the previous question):

cryptData = <118a32dc c23f7caa 883abc3c 1c7f0770 e200016b 2737acfa 17bb96fb a02b02a7 c147603b 06acd863 94bb8ff2 6cb14515>

It is just a matter of getting all of the inputs the same: operation, algorithm, options, keyBytes, keyLength, dataBytes, dataLength and iv if non ECB mode. CCCrypt is just a function call, that's all. Put in the same input, get the same output.

Put in NSLog() statements, preferably hex dumps for data and strings. Compare and fix as needed.

Believe it or not, this is the easy part of electronic security.

DES/ECB/NoPadding in swift 3.1 using Common Crypto

So it seems like there's no NoPadding options available, in the end I just removed '+ kCCBlockSizeDES' from 'cryptData' initialization and added '0' characters to my string before encrypting it to reach a right size (for example a length multiple of 8) and then after decryption I just removed the zeros to get the original string.

How to use CommonCrypto for PBKDF2 in Swift 2 & 3

func pbkdf2(hash :CCPBKDFAlgorithm, password: String, salt: [UInt8], keyCount: Int, rounds: UInt32!) -> [UInt8]! {
let derivedKey = [UInt8](count:keyCount, repeatedValue:0)
let passwordData = password.dataUsingEncoding(NSUTF8StringEncoding)!

let derivationStatus = CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
UnsafePointer<Int8>(passwordData.bytes), passwordData.length,
UnsafePointer<UInt8>(salt), salt.count,
CCPseudoRandomAlgorithm(hash),
rounds,
UnsafeMutablePointer<UInt8>(derivedKey),
derivedKey.count)

if (derivationStatus != 0) {
print("Error: \(derivationStatus)")
return nil;
}

return derivedKey
}

hash is the hash type such as kCCPRFHmacAlgSHA1, kCCPRFHmacAlgSHA256, kCCPRFHmacAlgSHA512.

Example from sunsetted documentation section:

Password Based Key Derivation 2 (Swift 3+)

Password Based Key Derivation can be used both for deriving an encryption key from password text and saving a password for authentication purposes.

There are several hash algorithms that can be used including SHA1, SHA256, SHA512 which are provided by this example code.

The rounds parameter is used to make the calculation slow so that an attacker will have to spend substantial time on each attempt. Typical delay values fall in the 100ms to 500ms, shorter values can be used if there is unacceptable performance.

This example requires Common Crypto

It is necessary to have a bridging header to the project:

#import <CommonCrypto/CommonCrypto.h>

Add the Security.framework to the project.

Parameters:

password     password String  
salt salt Data
keyByteCount number of key bytes to generate
rounds Iteration rounds

returns Derived key

func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
return pbkdf2(hash:CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
}

func pbkdf2(hash :CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
let passwordData = password.data(using:String.Encoding.utf8)!
var derivedKeyData = Data(repeating:0, count:keyByteCount)

let derivationStatus = derivedKeyData.withUnsafeMutableBytes {derivedKeyBytes in
salt.withUnsafeBytes { saltBytes in

CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password, passwordData.count,
saltBytes, salt.count,
hash,
UInt32(rounds),
derivedKeyBytes, derivedKeyData.count)
}
}
if (derivationStatus != 0) {
print("Error: \(derivationStatus)")
return nil;
}

return derivedKeyData
}

Example usage:

let password     = "password"
//let salt = "saltData".data(using: String.Encoding.utf8)!
let salt = Data(bytes: [0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let keyByteCount = 16
let rounds = 100000

let derivedKey = pbkdf2SHA1(password:password, salt:salt, keyByteCount:keyByteCount, rounds:rounds)
print("derivedKey (SHA1): \(derivedKey! as NSData)")

Example Output:

derivedKey (SHA1): <6b9d4fa3 0385d128 f6d196ee 3f1d6dbf>

Password Based Key Derivation Calibration

This example requires Common Crypto
It is necessary to have a bridging header to the project:

#import <CommonCrypto/CommonCrypto.h>

Add the Security.framework to the project.


Determine the number of PRF rounds to use for a specific delay on the current platform.

Several parameters are defaulted to representative values that should not materially affect the round count.

password Sample password.  
salt Sample salt.
msec Targeted duration we want to achieve for a key derivation.

returns The number of iterations to use for the desired processing time.

Password Based Key Derivation Calibration (Swift 3)

func pbkdf2SHA1Calibrate(password: String, salt: Data, msec: Int) -> UInt32 {
let actualRoundCount: UInt32 = CCCalibratePBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password.utf8.count,
salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
kCCKeySizeAES256,
UInt32(msec));
return actualRoundCount
}

Example usage:

let saltData       = Data(bytes: [0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let passwordString = "password"
let delayMsec = 100

let rounds = pbkdf2SHA1Calibrate(password:passwordString, salt:saltData, msec:delayMsec)
print("For \(delayMsec) msec delay, rounds: \(rounds)")

Example Output:

For 100 msec delay, rounds: 93457

Password Based Key Derivation Calibration (Swift 2.3)

func pbkdf2SHA1Calibrate(password:String, salt:[UInt8], msec:Int) -> UInt32 {
let actualRoundCount: UInt32 = CCCalibratePBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password.utf8.count,
salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
kCCKeySizeAES256,
UInt32(msec));
return actualRoundCount
}

Example usage:

let saltData       = [UInt8]([0x73, 0x61, 0x6c, 0x74, 0x44, 0x61, 0x74, 0x61])
let passwordString = "password"
let delayMsec = 100

let rounds = pbkdf2SHA1Calibrate(passwordString, salt:saltData, msec:delayMsec)
print("For \(delayMsec) msec delay, rounds: \(rounds)")

Is there a way to use Common Crypto in a Swift playground?

A quick and dirty solution:

func SHA1HashString(string: String) -> String {
let task = NSTask()
task.launchPath = "/usr/bin/shasum"
task.arguments = []

let inputPipe = NSPipe()
inputPipe.fileHandleForWriting.writeData(string.dataUsingEncoding(NSUTF8StringEncoding)!)
inputPipe.fileHandleForWriting.closeFile()

let outputPipe = NSPipe()
task.standardOutput = outputPipe
task.standardInput = inputPipe
task.launch()

let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
let hash = String(data: data, encoding: NSUTF8StringEncoding)!
return hash.stringByReplacingOccurrencesOfString(" -\n", withString: "")
}


Related Topics



Leave a reply



Submit