This article introduces the concept of key stretching, using code examples to explain the ideas. For code you can use in an app that more closely resembles current practice, see Password checking with CommonCrypto.
There’s been quite the media circus regarding the possibility that Sony was storing authentication credentials for its PlayStation Network credentials in plain text. I was even quoted in a UK national daily paper regarding the subject. But none of this helps you: how should you deal with user passwords?
The best solution is also the easiest: if you can avoid it, don’t store the passwords yourself. On the Mac, you can use the OpenDirectory framework to authenticate both local users and users with accounts on the network (where the Mac is configured to talk to a networked directory service). This is fully covered in Chapter 2 of Professional Cocoa Application Security.
On the iPhone, you’re not so lucky. And maybe on the Mac there’s a reason you can’t use the local account: your app needs to manage its own password. The important point is that you never need to see that password—you need to know that the same password was presented in order to know (or at least have a good idea) that the same user is at the touchscreen, but that’s not the same as seeing the password itself.
That means that we don’t even need to use encryption where we can protect the password and recover it when we must check the password. Instead we can use a cryptographic one-way hash function to store data derived from the password: we can never get the password back, but we can always generate the same hash value when we see the same password.
Shut up Graham. Show me the code.
Here it is. This code is provided under the terms of the WTFPL, and comes without any warranty to the extent permitted by applicable law.
The first thing you’ll need to do is generate a salt. This is a random string of bytes that is combined with the password to hash: the point here is that if two users on the same system have the same password, the fact that the salt is different means that they still have different hashes. So you can’t do any statistical analysis on the hashes to work out what some of the passwords are. Otherwise, you could take your knowledge that, say, 10% of people use “password” as their password, and look for the hash that appears 10% of the time.
It also protects the password against a rainbow tables attack by removing the one-one mapping between a password and its hash value. This mitigation is actually more important in the real world than the one above, which is easier to explain :-).
This function uses Randomization Services, so remember to link Security.framework in your app’s link libraries build phase.
NSString *FZARandomSalt(void) { uint8_t bytes[16] = {0}; int status = SecRandomCopyBytes(kSecRandomDefault, 16, bytes); if (status == -1) { NSLog(@"Error using randomization services: %s", strerror(errno)); return nil; } NSString *salt = [NSString stringWithFormat: @"%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]]; return salt; }
Now you pass this string, and the password, to the next function, which actually calculates the hash. In fact, it runs through the hashing function 5,000 times. That slows things down a little—on an A4-equipped iPad it takes nearly 0.088s to compute the hash—but it also slows down brute-force attacks.
NSData *FZAHashPassword(NSString *password, NSString *salt) { NSCParameterAssert([salt length] >= 32); uint8_t hashBuffer[64] = {0}; NSString *saltedPassword = [[salt substringToIndex: 32] stringByAppendingString: password]; const char *passwordBytes = [saltedPassword cStringUsingEncoding: NSUTF8StringEncoding]; NSUInteger length = [saltedPassword lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; CC_SHA512(passwordBytes, length, hashBuffer); for (NSInteger i = 0; i < 4999; i++) { CC_SHA512(hashBuffer, 64, hashBuffer); } return [NSData dataWithBytes: hashBuffer length: 64]; }
Where do I go now?
You now have two pieces of information: a random salt, like edbfe42b3da2995a159c16c0a7184211, and a hash of the password, like 855fec563d91576db0e66d8745a3a9cb71dbe40d7cb2615a82b1c87958dd2e8e56db02860739422b976f182a7055dd223a3037dd3dcc5e1ca28aaaf0bade8a08. Store both of these on the machine where the password will be tested. In principle there isn’t too much worry about this data being leaked, because it’s super-hard to get the password out of it, but it’s still best practice to restrict access as much as you can so that attackers have to brute-force passwords on your terms.
When you come to verify the user’s password, pass the string presented by the user and the stored salt to FZAHashPassword(). You should get the same hash out that you previously calculated, if the same password was presented.
Anything else?
Yes. The weakest part of this solution is no longer the password storage: it’s the password itself. The salt+hash shown above is actually for the password “password” (try it yourself), and no amount of software is going to change the fact that that’s a questionable choice of password…well, software that finally does away with password authentication will, but that’s a different argument.
If you want to limit a user’s ability to choose a simple password, you have to do this at password registration and change time. Just look at the (plain-text) password the user has given you and decide whether you want to allow its use.
Thanks for the code – if just everybody realized how simple it is to secure the credentials.
I noticed that you are only using the first 8 of 32 characters of your salted string. Is there a particular reason for that? Or is the long salt just to fool any potential password cracker into believing that the full salt string is used?
Ah, that’s a bug, sorry. I increased the length of the salt to match FreeBSD’s choice, and didn’t update the other function. That’s what I get for making the change in the blog editor rather than the test harness ;-)
Pingback: Secure Credentials at Under The Bridge
Hi Graham,
thanks for this article – could you please provide a corrected/updated version regarding the question from Claus Broch to the public. I’m a novice and I don’t like to do an implementation error in my app. BTW your book is a wonderful resource and I’m really looking forward to see what’s next on your blog. Regards, Thomas
Hi, I already addressed the bug in the post.
Why not just use PBKDF2 instead of rolling your own algorithm (which basically does a similar thing)? I think that it’s always better to rely on already tested implementations and write as less of your own security-related code as possible.
Indeed, PBKDF2 and bcrypt are both fine solutions to this problem.