I’m working on an iPhone / iPad app that needs to be able to authenticate itself to the server from which it requests data. The users of the app will have an account with the website so they will already have a username / password. There is no SSL functionality, so I needed a way to authenticate over plaintext without giving away the password or even the hash of it.
Let’s talk about what the webserver does in relation to storing the password. It doesn’t actually store the password. It stores a hashed value where the actual password was combined with a salt (random string of a descent length) and that combined string was hashed using SHA256. The result is stored as the “password” for the user and the salt used to compute the hash is also stored for that user. When the user logs in the password they send (using ssl, so it is encrypted between the user and server) is combined with the stored salt and hashed. If the result is the same as the stored hash then the user is authenticated, otherwise they are not.
Here’s a little helper class written in C# for doing the Hash. It’s server side and .net specific.
#region HashingLogic
public class Hash { public Hash() { }
public enum HashType : int { MD5, SHA1, SHA256, SHA512 }
public static string GetHash(string text, HashType hashType) { HashAlgorithm _algorithm; switch (hashType) { case HashType.MD5: _algorithm = MD5.Create(); break; case HashType.SHA1: _algorithm = SHA1.Create(); break; case HashType.SHA256: _algorithm = SHA256.Create(); break; case HashType.SHA512: _algorithm = SHA512.Create(); break; default: throw new ArgumentException("Invalid hash type", "hashType"); }
byte[] bytes = Encoding.Unicode.GetBytes(text); byte[] hash = _algorithm.ComputeHash(bytes);
string hashString = string.Empty; foreach (byte x in hash) { hashString += String.Format("{0:x2}", x); } return hashString; }
public static bool CheckHash(string original, string hashString, HashType hashType) { string originalHash = GetHash(original, hashType); return (originalHash == hashString); }
} #endregion
And here’s the webserver checking the Hash. It should be pretty easy to see how to store it as well when a user sets their password in the first place.
// ran a query that returned the hashed password and salt for the user in question // note: password is the value sent by the user trying to log in
string the_hashed_password = dt.Rows[0]["vcHashedPass"].ToString().Trim(); string the_salt = dt.Rows[0]["vcPassSalt"].ToString().Trim(); string the_salted_password = password.Trim() + the_salt; string hashedvalue = Hash.GetHash(the_salted_password, Hash.HashType.SHA256);
if (hashedvalue == the_hashed_password) loginSuccessful = true;
Now that we have the background, I can talk about what really screwed with me. I was trying to implement part of this on the iOS side. , but I was getting a different value back for my hash. I used a Hash library for iOS from this site. It was easy to use. Just copy the files to your project and import the .h into whatever file you need to do hashing. Before hashing an NSString you must first convert it to an NSData. But this conversion requires you to specify the encoding. To use it in conjunction with the .Net helper class above you have to choose the right encoding or it will has to the wrong value. Code first, then discussion.
NSString *usernameex = [keychainwrapper objectForKey:(id)kSecAttrAccount]; NSString *passwordex = [keychainwrapper objectForKey:(id)kSecValueData]; // I'm hardcoding the salt for this example, now we need to talk to the server to see if it will let us log in with it
NSString *retrievedSalt = [NSString stringWithFormat:@"b3b5c979-3438-4f56-aa1a-1d85bda78b7e"]; NSString *saltedpassword = [NSString stringWithFormat:@"%@%@", passwordex, retrievedSalt];
// Webserver thinks that hashed(password+salt) = // 14083e601f136a42bb17f37a6dedfd03c1f53f2c93f2cc886de5b44ed4a452c1 -- looking for
// but this library was returning various results... here's one // ed87d50a50937361f17f9b3c32c6db04e6b7779e5f3c42dfa20e44cac7bf112e -- getting
// there are lots of example of how the "encoding" can be done and what the hashed value that results is // I included them in this sample code because I had to try a ton to finally find the one that matched the .net
HashValue *hashvalue;
//NSUTF16StringEncoding 762ce0d893b64032f11562e4ee0be3f4ed08ca6b801ab892d319af3942ca788c NSData* datasaltedpassword0a=[saltedpassword dataUsingEncoding:NSUTF16StringEncoding]; hashvalue = [HashValue sha256HashWithData:datasaltedpassword0a]; ASLog(@"hashedvalue0a - %@", hashvalue.description); // password - whatever you entered
NSData* datasaltedpassword0b=[saltedpassword dataUsingEncoding:NSUTF16BigEndianStringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword0b];
ASLog(@"hashedvalue0b - %@", hashvalue.description); // password - whatever you entered
NSData* datasaltedpassword0c=[saltedpassword dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword0c];
ASLog(@"hashedvalue0c - %@", hashvalue.description); // password - whatever you entered
NSData* datasaltedpassword0d=[saltedpassword dataUsingEncoding:NSUTF32StringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword0d];
ASLog(@"hashedvalue0d - %@", hashvalue.description); // password - whatever you entered
NSData* datasaltedpassword0e=[saltedpassword dataUsingEncoding:NSUTF32BigEndianStringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword0e];
ASLog(@"hashedvalue0e - %@", hashvalue.description); // password - whatever you entered
NSData* datasaltedpassword0f=[saltedpassword dataUsingEncoding:NSUTF32LittleEndianStringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword0f];
ASLog(@"hashedvalue0f - %@", hashvalue.description); // password - whatever you entered
//NSUTF8StringEncoding
NSData* datasaltedpassword1=[saltedpassword dataUsingEncoding:NSUTF8StringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword1];
ASLog(@"hashedvalue1 - %@", hashvalue.description); // password - whatever you entered
//[NSString defaultCStringEncoding]
NSData* datasaltedpassword2=[saltedpassword dataUsingEncoding:[NSString defaultCStringEncoding]];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword2];
ASLog(@"hashedvalue2 - %@", hashvalue.description); // password - whatever you entered
//NSUnicodeStringEncoding allowLossyConversion:NO
NSData* datasaltedpassword3=[saltedpassword dataUsingEncoding:NSUnicodeStringEncoding allowLossyConversion:NO];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword3];
ASLog(@"hashedvalue3 - %@", hashvalue.description); // password - whatever you entered
//NSWindowsCP1252StringEncoding
NSData* datasaltedpassword4=[saltedpassword dataUsingEncoding:NSWindowsCP1252StringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword4];
ASLog(@"hashedvalue4 - %@", hashvalue.description); // password - whatever you entered
//NSMacOSRomanStringEncoding
NSData* datasaltedpassword5=[saltedpassword dataUsingEncoding:NSMacOSRomanStringEncoding];
hashvalue = [HashValue sha256HashWithData:datasaltedpassword5];
ASLog(@"hashedvalue5 - %@", hashvalue.description); // password - whatever you entered
ASLog(@"targetvalue - 14083e601f136a42bb17f37a6dedfd03c1f53f2c93f2cc886de5b44ed4a452c1", hashvalue.description); // password - whatever you entered
And the output:
2012-02-05 16:13:38.077 [11082:560f] LoginViewController.m:121 -[LoginViewController workOfDoLogin] password – ocelot
2012-02-05 16:13:38.078 [11082:560f] LoginViewController.m:140 -[LoginViewController workOfDoLogin] salt – b3b5c979-3438-4f56-aa1a-1d85bda78b7e
2012-02-05 16:13:38.078 [11082:560f] LoginViewController.m:144 -[LoginViewController workOfDoLogin] saltedpassword – ocelotb3b5c979-3438-4f56-aa1a-1d85bda78b7e
2012-02-05 16:13:38.080 [11082:560f] LoginViewController.m:152 -[LoginViewController workOfDoLogin] hashedvalue0a – 762ce0d893b64032f11562e4ee0be3f4ed08ca6b801ab892d319af3942ca788c
2012-02-05 16:13:38.080 [11082:560f] LoginViewController.m:156 -[LoginViewController workOfDoLogin] hashedvalue0b – df7e828a1df0664ff42756960ab7f63e83f0504437c59286e217df32147d8815
2012-02-05 16:13:38.081 [11082:560f] LoginViewController.m:160 -[LoginViewController workOfDoLogin] hashedvalue0c – 14083e601f136a42bb17f37a6dedfd03c1f53f2c93f2cc886de5b44ed4a452c1
2012-02-05 16:13:38.082 [11082:560f] LoginViewController.m:164 -[LoginViewController workOfDoLogin] hashedvalue0d – 4276b8d7ff791492f537379bf7353095d8ce40c717f15dc242f2847b376b88de
2012-02-05 16:13:38.083 [11082:560f] LoginViewController.m:168 -[LoginViewController workOfDoLogin] hashedvalue0e – 45838174fdf6cb95b7ed6bdbd7409f065c75f12958e58ae84ec5d28235b6cf0e
2012-02-05 16:13:38.083 [11082:560f] LoginViewController.m:172 -[LoginViewController workOfDoLogin] hashedvalue0f – 492adfab98090cf9319684aa707d368a2689495480a59ffd7a9b2a514ea9362d
2012-02-05 16:13:38.084 [11082:560f] LoginViewController.m:178 -[LoginViewController workOfDoLogin] hashedvalue1 – ed87d50a50937361f17f9b3c32c6db04e6b7779e5f3c42dfa20e44cac7bf112e
2012-02-05 16:13:38.085 [11082:560f] LoginViewController.m:183 -[LoginViewController workOfDoLogin] hashedvalue2 – ed87d50a50937361f17f9b3c32c6db04e6b7779e5f3c42dfa20e44cac7bf112e
2012-02-05 16:13:38.086 [11082:560f] LoginViewController.m:188 -[LoginViewController workOfDoLogin] hashedvalue3 – 762ce0d893b64032f11562e4ee0be3f4ed08ca6b801ab892d319af3942ca788c
2012-02-05 16:13:38.086 [11082:560f] LoginViewController.m:193 -[LoginViewController workOfDoLogin] hashedvalue4 – ed87d50a50937361f17f9b3c32c6db04e6b7779e5f3c42dfa20e44cac7bf112e
2012-02-05 16:13:38.087 [11082:560f] LoginViewController.m:198 -[LoginViewController workOfDoLogin] hashedvalue5 – ed87d50a50937361f17f9b3c32c6db04e6b7779e5f3c42dfa20e44cac7bf112e
2012-02-05 16:13:38.088 [11082:560f] LoginViewController.m:201 -[LoginViewController workOfDoLogin] targetvalue – 14083e601f136a42bb17f37a6dedfd03c1f53f2c93f2cc886de5b44ed4a452c1
The right Encoding to use on the iOS side was “NSUTF16LittleEndianStringEncoding”. I had to do all of those tests to finally find which one matched the values being produced on the web side (.Net)… so just keep in mind that if your getting two different hashes then you might need to use a different Encoding during the conversion from String to Byte / Data in order to make them sync up.
If someone also copied and pasted some of these codes (like the following) and see odd characters like “Äì”.. that’s actually the hyphen character (some copy-and-paste black magic happening there)!!
ASLog(@”hashedvalue0b – %@”, hashvalue.description); // password – whatever you entered
But thanks for your example it’s good!