I previously described a system for storing and checking credentials on Mac OS and iOS based on using many rounds of a hashing function to generate a key from the password. Time has moved on, and Apple has extended the CommonCrypto library to provide a simple, standard and supported way of doing this. If this is still a problem you need to solve, you should look at doing it this way instead of following the earlier post.
We’re going to use a key-stretching function called PBKDF2 to make an encryption key of a standard length. This key probably will be much longer than the user’s password. However, it can’t possibly be any more random: it must be deterministically derived from that password so we’re stuck with the same amount of randomness that this password contains. A good key-stretching function should “smooth out” the randomness from the initial password, so that you can’t guess anything about the password given the function’s output.
We’re then going to use this key to calculate an HMAC of some known data. An HMAC is a bit like a digital signature, in that it depends both on the key used and the input data. We’ll store the HMAC but not the password and not the key derived from the password. Whenever the password is needed in the future, it must be provided by a user and cannot be derived from any of the data in the app.
The idea, then, is that when you set a new password, the app calculates this key, uses that to calculate an HMAC and stores the HMAC. When you try to use the app, you present a password, from which the app generates a key and an HMAC of the same data. If this HMAC matches the one that was previously calculated, the same keys were used, which (hopefully) means that the same password was supplied.
The faster you scream, the slower we go.
One of the problems that a key-stretching function must address is that computers are really, really fast. Normally computers being really, really fast is a benefit, but the faster it is to compute all of the above stuff the more guesses an attacker can try at the password in some amount of time. We therefore want to make this function slow enough that brute force attacks are limited, but not so slow that people get frustrated with the app’s performance.
PBKDF2 has a tuneable parameter – the number of rounds of a hashing function it uses internally to stretch the key. With CommonCrypto you can ask for a number of rounds that will result in the function taking (approximately) a certain amount of time to work on a password of a certain length.
Requirements for the rounds parameter
- The number of rounds used when checking a password should be the same as the number used when generating the stored HMAC, otherwise the keys generated won’t match.
- The above means that you need to choose a single value to use across app installs and hardware – unless you’re happy with losing access to all user data when a customer upgrades their iPad.
- You will want to revise this parameter upwards as faster hardware becomes available. This conflicts with the first requirement: you’ll need a fallback mechanism to try the same password with different “versions” of your rounds parameter so that you can upgrade users’ credentials with your app.
With those in mind, you can construct an algorithm to choose a tuning parameter based on this call:
const uint32_t oneSecond = 1000; rounds = CCCalibratePBKDF(kCCPBKDF2, predictedPasswordLength, predictedSaltLength, kCCPRFHmacAlgSHA256, kCCKeySizeAES128, oneSecond);
You can probably know what length of salt you’ll use: salt should be a block of random data that you supply, that is used as additional input to the key-stretching function. If two different users supply the same password, the salt stops the function from generating the same output. You probably can’t know what length of password users will use, but you can guess based on experience, data and knowledge of any password strength rules incorporated into your app.
Notice that you should derive this rounds property on the target hardware. The number of rounds that take a second to run through on your brand new iMac will take significantly longer on your customer’s iPhone 3GS.
Generating the HMAC data
Both storing and checking passwords use the same internal function:
- (NSData *)authenticationDataForPassword: (NSString *)password salt: (NSData *)salt rounds: (uint) rounds { const NSString *plainData = @"Fuzzy Aliens"; uint8_t key[kCCKeySizeAES128] = {0}; int keyDerivationResult = CCKeyDerivationPBKDF(kCCPBKDF2, [password UTF8String], [password lengthOfBytesUsingEncoding: NSUTF8StringEncoding], [salt bytes], [salt length], kCCPRFHmacAlgSHA256, rounds, key, kCCKeySizeAES128); if (keyDerivationResult == kCCParamError) { //you shouldn't get here with the parameters as above return nil; } uint8_t hmac[CC_SHA256_DIGEST_LENGTH] = {0}; CCHmac(kCCHmacAlgSHA256, key, kCCKeySizeAES128, [plainData UTF8String], [plainData lengthOfBytesUsingEncoding: NSUTF8StringEncoding], hmac); NSData *hmacData = [NSData dataWithBytes: hmac length: CC_SHA256_DIGEST_LENGTH]; return hmacData; }
Storing credentials for a new password simply involves generating a salt, computing the authentication data then writing it somewhere:
- (void)setPassword: (NSString *)password { //generate a random salt… //do any checking (on complexity, or whether the passwords entered in two fields match)… NSData *hmacData = [self authenticationDataForPassword: password salt: salt rounds: [self roundsForKeyDerivation]]; //store the HMAC and the salt, perhaps by concatenating them and putting them in the keychain… }
Then testing the credentials involves applying the generation function to the password guess, and comparing the result with what you previously stored:
- (BOOL)checkPassword: (NSString *)password { //recover the HMAC and the salt from wherever you stored them… NSData *guessedHmac = [self authenticationDataForPassword: password salt: salt rounds: [self roundsForKeyDerivation]]; return [guessedHmac isEqualToData: hmacData]; }
Conclusion
If you can avoid storing a password in your app, even in the keychain, you should; there’s then a much reduced chance that the password can be recovered from the app by an attacker. Deriving a key from the password using PBKDF2 then testing whether you can use that key to obtain a known cryptographic result obviates the need to store the password itself. Mac OS X and iOS provide an easy way to use PBKDF2 in the CommonCrypto library.
The key derived from the password could even be used to protect the content in the app, so none of the documents are available without the password being presented. Doing this offers additional confidentiality over simply using the password for access control. Building a useful protocol around this key requires key wrapping, the subject of a future post.
I prefer
NSData *utf8Data = [password dataUsingEncoding:NSUTF8StringEncoding];
then using [utf8Data bytes] and [utf8Data length] rather than what you wrote. The way it’s written right now, it might have to convert to UTF-8 twice.
Similarly for plainData, though I would have written that as a C string since it’s never used as an object — it’s really just an array of bytes.