2
0

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.
This commit is contained in:
Maarten Billemont 2012-06-07 00:12:38 +02:00
parent b472c85c9d
commit 679990dc4b
5 changed files with 30 additions and 25 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 3a61ba22afefb23c2f87a3836e8b996d2b5a96fb Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0

View File

@ -62,7 +62,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (![tryPassword length]) if (![tryPassword length])
return NO; return NO;
NSData *tryKey = keyForPassword(tryPassword); NSData *tryKey = keyForPassword(tryPassword, user.name);
NSData *tryKeyID = keyIDForKey(tryKey); NSData *tryKeyID = keyIDForKey(tryKey);
inf(@"Key ID was known? %@.", user.keyID? @"YES": @"NO"); inf(@"Key ID was known? %@.", user.keyID? @"YES": @"NO");
if (user.keyID) { if (user.keyID) {

View File

@ -269,7 +269,7 @@ static NSDateFormatter *rfc3339DateFormatter = nil;
user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
} }
if ([key isEqualToString:@"Key ID"]) { if ([key isEqualToString:@"Key ID"]) {
if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password) encodeHex]]) if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]])
return MPImportResultInvalidPassword; return MPImportResultInvalidPassword;
} }

View File

@ -78,8 +78,8 @@ typedef enum {
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten" #define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed" #define MPNotificationElementUsed @"MPNotificationElementUsed"
NSData *keyForPassword(NSString *password); NSData *keyForPassword(NSString *password, NSString *username);
NSData *keyIDForPassword(NSString *password); NSData *keyIDForPassword(NSString *password, NSString *username);
NSData *keyIDForKey(NSData *key); NSData *keyIDForKey(NSData *key);
NSString *NSStringFromMPElementType(MPElementType type); NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type); NSString *ClassNameFromMPElementType(MPElementType type);

View File

@ -11,24 +11,28 @@
#import "MPElementStoredEntity.h" #import "MPElementStoredEntity.h"
#define MP_salt nil
#define MP_N 131072 #define MP_N 131072
#define MP_r 8 #define MP_r 8
#define MP_p 1 #define MP_p 1
#define MP_dkLen 64 #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] 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; 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) { 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); err(@"Incorrect type (is not MPElementTypeClassGenerated): %d, for: %@", type, name);
return nil; return nil;
} }
if (!name) { if (!name.length) {
err(@"Missing name."); err(@"Missing name.");
return nil; return nil;
} }
if (!key) { if (!key.length) {
err(@"Key not set."); err(@"Missing key.");
return nil; return nil;
} }
uint32_t salt = counter;
if (!counter) if (!counter)
// Counter unset, go into OTP mode. // 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. // 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) if (MPTypes_ciphers == nil)
MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers" MPTypes_ciphers = [NSDictionary dictionaryWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"ciphers"
withExtension:@"plist"]]; withExtension:@"plist"]];
// Determine the seed whose bytes will be used for calculating a password: sha1(name . '\0' . key . '\0' . salt) // Determine the seed whose bytes will be used for calculating a password
uint32_t nsalt = htonl(salt); trc(@"seed from: hmac-sha256(key, 'com.lyndir.masterpassword' | %u | %@ | %u)", key, name.length, name, counter);
trc(@"seed from: sha1(%@, %@, %u)", name, key, nsalt); uint32_t ncounter = htonl(counter), nnameLength = htonl(name.length);
NSData *seed = [[NSData dataByConcatenatingWithDelimitor:'\0' datas: NSData *seed = [[NSData dataByConcatenatingDatas:
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
[NSData dataWithBytes:&nnameLength length:sizeof(nnameLength)],
[name dataUsingEncoding:NSUTF8StringEncoding], [name dataUsingEncoding:NSUTF8StringEncoding],
key, [NSData dataWithBytes:&ncounter length:sizeof(ncounter)],
[NSData dataWithBytes:&nsalt length:sizeof(nsalt)], nil]
nil] hashWith:PearlDigestSHA256]; hmacWith:PearlHashSHA256 key:key];
trc(@"seed is: %@", seed); trc(@"seed is: %@", seed);
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;