Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: How to call CCKeyDerivationPBKDF from Swift

I'm trying to call CCKeyDerivationPBKDF from Swift.

I've imported the required header in my Project-Bridging-Header.h:

#import <CommonCrypto/CommonKeyDerivation.h>

(Btw, the bridging header appears to be working correctly for importing other Objective C code in my project).

In Xcode I can jump from my .swift file to the definition shown here:

int 
CCKeyDerivationPBKDF( CCPBKDFAlgorithm algorithm, const char *password, size_t passwordLen,
                      const uint8_t *salt, size_t saltLen,
                      CCPseudoRandomAlgorithm prf, uint rounds, 
                      uint8_t *derivedKey, size_t derivedKeyLen)

Finally, when I attempt to call the function like so:

let result = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), NSString(password).UTF8String, size_t(passwordLength), UnsafePointer<UInt8>(salt.bytes), size_t(salt.length), CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), uint(actualRoundCount), UnsafeMutablePointer<UInt8>(derivedKey.mutableBytes), size_t(derivedKey.length));

...I get this compiler error:

Cannot invoke 'init' with an argument list of type '(CCPBKDFAlgorithm, UnsafePointer, size_t, UnsafePointer, size_t, CCPseudoRandomAlgorithm, uint, UnsafeMutablePointer, size_t)'

I believe all the casts are correct (actually the compiler errors helped me identify each problem with each parameter) - which makes me think the compiler understands my intent to call CCKeyDerivationPBKDF.

However, after all the other casting errors went away, the compiler is confused and believes I am trying to construct a class with an initializer.

Hoping someone can show me the error of my ways.

(Xcode 6 beta 7)

As requested, full code in context:

class func generateAesKeyForPassword(password: String, salt: NSData, roundCount: UInt?, error: NSErrorPointer) -> (key: NSData, actualRoundCount: UInt)?
    {
        let derivedKey = NSMutableData(length: kCCKeySizeAES256)

        let passwordLength = size_t(password.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))

        var actualRoundCount: UInt

        if roundCount != nil
        {
            actualRoundCount = roundCount!
        }
        else
        {
            actualRoundCount = UInt(CCCalibratePBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordLength, UInt(salt.length), CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), UInt(derivedKey.length), UInt32(300) /* milliseconds */));
        }

        let result = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), NSString(password).UTF8String, size_t(passwordLength), UnsafePointer<UInt8>(salt.bytes), size_t(salt.length), CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), uint(actualRoundCount), UnsafeMutablePointer<UInt8>(derivedKey.mutableBytes), size_t(derivedKey.length));
        if result != 0
        {
            let errorDescription = "CCKeyDerivationPBKDF failed with error: '\(result)'"

            error.memory = MyError(domain: ClientErrorType.errorDomain, code: Int(result), descriptionText: errorDescription)

            return nil
        }

        return (NSData(data: derivedKey), actualRoundCount)
    }
like image 536
Daniel Avatar asked Sep 05 '14 18:09

Daniel


2 Answers

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>

Swift 2.x:

Minor changes of argument type and class to instance method for testing.

func generateAesKeyForPassword(password: String, salt: NSData, roundCount: Int?, error: NSErrorPointer) -> (key: NSData, actualRoundCount: UInt32)?
{
    let nsDerivedKey = NSMutableData(length: kCCKeySizeAES256)
    var actualRoundCount: UInt32

    // Create Swift intermediates for clarity in function calls
    let algorithm: CCPBKDFAlgorithm        = CCPBKDFAlgorithm(kCCPBKDF2)
    let prf:       CCPseudoRandomAlgorithm = CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
    let saltBytes  = UnsafePointer<UInt8>(salt.bytes)
    let saltLength = size_t(salt.length)
    let nsPassword        = password as NSString
    let nsPasswordPointer = UnsafePointer<Int8>(nsPassword.cStringUsingEncoding(NSUTF8StringEncoding))
    let nsPasswordLength  = size_t(nsPassword.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    var nsDerivedKeyPointer = UnsafeMutablePointer<UInt8>(nsDerivedKey.mutableBytes)
    let nsDerivedKeyLength = size_t(nsDerivedKey.length)
    let msec: UInt32 = 300

    if roundCount != nil {
        actualRoundCount = UInt32(roundCount!)
    }
    else {
        actualRoundCount = CCCalibratePBKDF(
            algorithm,
            nsPasswordLength,
            saltLength,
            prf,
            nsDerivedKeyLength,
            msec);
    }

    let result = CCKeyDerivationPBKDF(
        algorithm,
        nsPasswordPointer,   nsPasswordLength,
        saltBytes,           saltLength,
        prf,                 actualRoundCount,
        nsDerivedKeyPointer, nsDerivedKeyLength)

    if result != 0 {
        let errorDescription = "CCKeyDerivationPBKDF failed with error: '\(result)'"
        // error.memory = MyError(domain: ClientErrorType.errorDomain, code: Int(result), descriptionText: errorDescription)
        return nil
    }

    return (nsDerivedKey, actualRoundCount)
}

// Added bonus:

func salt(#length:UInt) -> NSData {
    let salt        = NSMutableData(length: Int(length))
    var saltPointer = UnsafeMutablePointer<UInt8>(salt.mutableBytes)
    SecRandomCopyBytes(kSecRandomDefault, length, saltPointer);
    return salt
}

// Test call:

let password   = "test pass"
let salt       = self.salt(length:32)
let roundCount = 300
var error: NSError?

let result = self.generateAesKeyForPassword(password, salt:salt, roundCount:roundCount, error:&error)
println("result: \(result)")

Output:

result: Optional((<d279ab8d 8ace67b7 abec844c b9979d20 f2bb0a7f 5af70502 085bf1e4 1016b20c>, 300))
like image 50
zaph Avatar answered Oct 12 '22 12:10

zaph


So I believe this works:

let result = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), NSString(string: password).UTF8String, size_t(passwordLength), UnsafePointer<UInt8>(salt.bytes), size_t(salt.length), CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), uint(actualRoundCount), UnsafeMutablePointer<UInt8>(derivedKey.mutableBytes), size_t(derivedKey.length));

And in Swift 1.2+, the size_t casts are no longer necessary:

let result = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), NSString(string: password).UTF8String, passwordLength, UnsafePointer<UInt8>(salt.bytes), salt.length, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512), uint(actualRoundCount), UnsafeMutablePointer<UInt8>(derivedKey.mutableBytes), derivedKey.length)

Note that this was wrong:

NSString(password).UTF8String

It should have been:

NSString(string: password).UTF8String

Compiler error was definitely misleading (still beta)

like image 29
Daniel Avatar answered Oct 12 '22 12:10

Daniel