From 679990dc4b498235e6676a3229a52909e036b4fa Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Thu, 7 Jun 2012 00:12:38 +0200 Subject: [PATCH] Improved algorithm security. [UPDATED] Algorithm updated to reflect advice from randombit.net cryptography list: - Add in a salt (user name) to defeat rainbow tables. - Add in a fixed string to scope the algorithm and avoid colliding with someone else's similar or identical algorithm (also helps protect against precalculated rainbow tables). - Use HMAC instead of plain SHA to avoid SHA weaknesses. The old implementation wasn't vulnerable to extension attacks or other known weaknesses, but HMAC is a safer choice and will bring up less suspicion. - Prefix strings by length as an extra precautionary measure against possible bugs in hash functions. --- External/Pearl | 2 +- MasterPassword/MPAppDelegate_Key.m | 2 +- MasterPassword/MPAppDelegate_Store.m | 2 +- MasterPassword/MPTypes.h | 4 +-- MasterPassword/MPTypes.m | 45 +++++++++++++++------------- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/External/Pearl b/External/Pearl index 3a61ba22..e55ef687 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit 3a61ba22afefb23c2f87a3836e8b996d2b5a96fb +Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0 diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m index 6c1f43c6..b050f233 100644 --- a/MasterPassword/MPAppDelegate_Key.m +++ b/MasterPassword/MPAppDelegate_Key.m @@ -62,7 +62,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { if (![tryPassword length]) return NO; - NSData *tryKey = keyForPassword(tryPassword); + NSData *tryKey = keyForPassword(tryPassword, user.name); NSData *tryKeyID = keyIDForKey(tryKey); inf(@"Key ID was known? %@.", user.keyID? @"YES": @"NO"); if (user.keyID) { diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index d00f33b5..88332bf6 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -269,7 +269,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; } if ([key isEqualToString:@"Key ID"]) { - if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]]) + if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]]) return MPImportResultInvalidPassword; } diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index f2b9aab2..cee0f018 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -78,8 +78,8 @@ typedef enum { #define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" #define MPNotificationElementUsed @"MPNotificationElementUsed" -NSData *keyForPassword(NSString *password); -NSData *keyIDForPassword(NSString *password); +NSData *keyForPassword(NSString *password, NSString *username); +NSData *keyIDForPassword(NSString *password, NSString *username); NSData *keyIDForKey(NSData *key); NSString *NSStringFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type); diff --git a/MasterPassword/MPTypes.m b/MasterPassword/MPTypes.m index 28153baa..f692022c 100644 --- a/MasterPassword/MPTypes.m +++ b/MasterPassword/MPTypes.m @@ -11,24 +11,28 @@ #import "MPElementStoredEntity.h" -#define MP_salt nil #define MP_N 131072 #define MP_r 8 #define MP_p 1 #define MP_dkLen 64 -#define MP_hash PearlDigestSHA256 +#define MP_hash PearlHashSHA256 -NSData *keyForPassword(NSString *password) { - +NSData *keyForPassword(NSString *password, NSString *username) { + + uint32_t nusernameLength = htonl(username.length); NSData *key = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding] - usingSalt:MP_salt N:MP_N r:MP_r p:MP_p]; + usingSalt:[NSData dataByConcatenatingDatas: + [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], + [NSData dataWithBytes:&nusernameLength length:sizeof(nusernameLength)], + [username dataUsingEncoding:NSUTF8StringEncoding], + nil] N:MP_N r:MP_r p:MP_p]; - trc(@"Password: %@ derives to key ID: %@", password, [keyIDForKey(key) encodeHex]); + trc(@"User: %@, password: %@ derives to key ID: %@", username, password, [keyIDForKey(key) encodeHex]); return key; } -NSData *keyIDForPassword(NSString *password) { +NSData *keyIDForPassword(NSString *password, NSString *username) { - return keyIDForKey(keyForPassword(password)); + return keyIDForKey(keyForPassword(password, username)); } NSData *keyIDForKey(NSData *key) { @@ -116,32 +120,33 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *key, ui err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name); return nil; } - if (!name) { + if (!name.length) { err(@"Missing name."); return nil; } - if (!key) { - err(@"Key not set."); + if (!key.length) { + err(@"Missing key."); return nil; } - uint32_t salt = counter; if (!counter) // Counter unset, go into OTP mode. // Get the UNIX timestamp of the start of the interval of 5 minutes that the current time is in. - salt = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300; + counter = ((uint32_t)([[NSDate date] timeIntervalSince1970] / 300)) * 300; if (MPTypes_ciphers == nil) MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]]; - // Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt) - uint32_t nsalt = htonl(salt); - trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt); - NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas: + // Determine the seed whose bytes will be used for calculating a password + trc(@"seed from: hmac-sha256(key, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter); + uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length); + NSData *seed = [[NSData dataByConcatenatingDatas: + [@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding], + [NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)], [name dataUsingEncoding:NSUTF8StringEncoding], - key, - [NSData dataWithBytes:&nsalt length:sizeof(nsalt)], - nil] hashWith:PearlDigestSHA256]; + [NSData dataWithBytes:&ncounter length:sizeof(ncounter)], + nil] + hmacWith:PearlHashSHA256 key:key]; trc(@"seed is: %@", seed); const char *seedBytes = seed.bytes;