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:
parent
b472c85c9d
commit
679990dc4b
2
External/Pearl
vendored
2
External/Pearl
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 3a61ba22afefb23c2f87a3836e8b996d2b5a96fb
|
Subproject commit e55ef6876ee26f61a7cd2c075fc1e7a942016de0
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user