2
0

Use scrypt for deriving a safer key from the master password + website.

This commit is contained in:
Maarten Billemont
2012-02-13 10:27:08 +01:00
parent 360f71c4fa
commit ad9c52896d
43 changed files with 1193 additions and 71 deletions

View File

@@ -13,9 +13,9 @@
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (strong, nonatomic) NSString *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex;
@property (readonly, strong, nonatomic) NSData *keyPhrase;
@property (readonly, strong, nonatomic) NSData *keyPhraseHash;
@property (readonly, strong, nonatomic) NSString *keyPhraseHashHex;
+ (MPAppDelegate *)get;
+ (NSManagedObjectModel *)managedObjectModel;
@@ -26,5 +26,6 @@
- (void)showGuide;
- (void)loadKeyPhrase;
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength;
@end

View File

@@ -13,6 +13,10 @@
@interface MPAppDelegate ()
@property (strong, nonatomic) NSData *keyPhrase;
@property (strong, nonatomic) NSData *keyPhraseHash;
@property (strong, nonatomic) NSString *keyPhraseHashHex;
+ (NSDictionary *)keyPhraseQuery;
+ (NSDictionary *)keyPhraseHashQuery;
@@ -67,11 +71,16 @@
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#ifndef PRODUCTION
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], @"logToConsole",
nil]];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
@try {
[TestFlight takeOff:@"bd44885deee7adce0645ce8e5498d80a_NDQ5NDQyMDExLTEyLTAyIDExOjM1OjQ4LjQ2NjM4NA"];
[TestFlight setOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], @"logToConsole",
nil]];
[TestFlight passCheckpoint:MPTestFlightCheckpointLaunched];
}
@catch (NSException *exception) {
err(@"TestFlight: %@", exception);
}
#endif
UIImage *navBarImage = [[UIImage imageNamed:@"ui_navbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5)];
@@ -146,7 +155,7 @@
[self showGuide];
else
[self loadKeyPhrase];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
#endif
@@ -196,7 +205,7 @@
}
[self loadKeyPhrase];
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
#endif
@@ -211,11 +220,8 @@
if ([[MPConfig get].storeKeyPhrase boolValue]) {
// Key phrase is stored in keychain. Load it.
dbg(@"Loading master key phrase from key chain.");
NSData *keyPhraseData = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
dbg(@" -> Master key phrase %@.", keyPhraseData? @"found": @"NOT found");
self.keyPhrase = keyPhraseData? [[NSString alloc] initWithBytes:keyPhraseData.bytes length:keyPhraseData.length
encoding:NSUTF8StringEncoding]: nil;
self.keyPhrase = [KeyChain dataOfItemForQuery:[MPAppDelegate keyPhraseQuery]];
dbg(@" -> Master key phrase %@.", self.keyPhrase? @"found": @"NOT found");
} else {
// Key phrase should not be stored in keychain. Delete it.
dbg(@"Deleting master key phrase from key chain.");
@@ -252,7 +258,8 @@
} cancelTitle:@"Quit" otherTitles:nil];
}
NSData *answerHash = [answer hashWith:PearlDigestSHA512];
NSData *answerKeyPhrase = keyPhraseForPassword(answer);
NSData *answerHash = keyPhraseHashForKeyPhrase(answerKeyPhrase);
if (keyPhraseHash)
// A key phrase hash is known -> a key phrase is set.
// Make sure the user's entered key phrase matches it.
@@ -279,8 +286,8 @@
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
#endif
self.keyPhrase = answer;
self.keyPhrase = answerKeyPhrase;
} cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
});
}
@@ -291,7 +298,7 @@
if (![[MPConfig get].rememberKeyPhrase boolValue])
self.keyPhrase = nil;
#ifndef PRODUCTION
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
#endif
@@ -330,12 +337,12 @@
}];
}
- (void)setKeyPhrase:(NSString *)keyPhrase {
- (void)setKeyPhrase:(NSData *)keyPhrase {
_keyPhrase = keyPhrase;
if (keyPhrase) {
self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
self.keyPhraseHash = keyPhraseHashForKeyPhrase(keyPhrase);
self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
@@ -348,7 +355,7 @@
dbg(@"Storing master key phrase in key chain.");
[KeyChain addOrUpdateItemForQuery:[MPAppDelegate keyPhraseQuery]
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[keyPhrase dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecValueData,
keyPhrase, (__bridge id)kSecValueData,
kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecAttrAccessible,
nil]];
}
@@ -359,6 +366,11 @@
}
}
- (NSData *)keyPhraseWithLength:(NSUInteger)keyLength {
return [self.keyPhrase subdataWithRange:NSMakeRange(0, MIN(keyLength, self.keyPhrase.length))];
}
#pragma mark - Core Data stack
/**

View File

@@ -13,6 +13,6 @@
@interface MPElementGeneratedEntity : MPElementEntity
@property (nonatomic, assign) int16_t counter;
@property (nonatomic, assign) uint16_t counter;
@end

View File

@@ -39,16 +39,14 @@
else
encryptedContent = self.contentObject;
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase
dataUsingEncoding:NSUTF8StringEncoding]
NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyPhraseWithLength:kCipherKeySize]
usePadding:YES];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (void)setContent:(id)content {
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get].keyPhrase
dataUsingEncoding:NSUTF8StringEncoding]
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[MPAppDelegate get].keyPhrase
usePadding:YES];
if (self.type == MPElementTypeStoredDevicePrivate) {

View File

@@ -114,8 +114,6 @@
}
}];
[self closeAlert];
[super viewDidLoad];
}
@@ -216,8 +214,11 @@
[NSURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"#%@", chapter] relativeToURL:
[[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]];
[self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setClass('%@');",
ClassNameFromMPElementType(self.activeElement.type)]];
if (error.length)
err(@"setClass: %@", error);
}
- (void)showContentTip:(NSString *)message withIcon:(UIImageView *)icon {
@@ -228,11 +229,6 @@
[UIView animateWithDuration:0.2f animations:^{
self.contentTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (!finished) {
icon.hidden = YES;
return;
}
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5.0f * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView animateWithDuration:0.2f animations:^{
@@ -375,6 +371,11 @@
nil];
}
- (MPElementType)selectedType {
return self.activeElement.type;
}
- (void)didSelectType:(MPElementType)type {
[self updateElement:^{

View File

@@ -205,6 +205,8 @@
message:l(@"Do you want to create a new site named:\n%@", siteName)
viewStyle:UIAlertViewStyleDefault
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (buttonIndex == [alert cancelButtonIndex])
return;

View File

@@ -12,6 +12,9 @@
- (void)didSelectType:(MPElementType)type;
@optional
- (MPElementType)selectedType;
@end
@interface MPTypeViewController : UITableViewController

View File

@@ -8,6 +8,13 @@
#import "MPTypeViewController.h"
@interface MPTypeViewController ()
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end
@implementation MPTypeViewController
@synthesize delegate;
@@ -16,7 +23,7 @@
- (void)viewDidLoad {
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
[super viewDidLoad];
}
@@ -25,30 +32,51 @@
return YES;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([delegate respondsToSelector:@selector(selectedType)])
if ([delegate selectedType] == [self typeAtIndexPath:indexPath])
[cell iterateSubviewsContinueAfter:^BOOL(UIView *subview) {
if ([subview isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = ((UIImageView *)subview);
if (!imageView.highlightedImage)
imageView.highlightedImage = [imageView.image highlightedImage];
imageView.highlighted = YES;
return NO;
}
return YES;
}];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
assert(self.navigationController.topViewController == self);
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
[self.navigationController popViewControllerAnimated:YES];
}
MPElementType type;
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0: {
// Calculated
switch (indexPath.row) {
case 0:
type = MPElementTypeCalculatedLong;
break;
return MPElementTypeCalculatedLong;
case 1:
type = MPElementTypeCalculatedMedium;
break;
return MPElementTypeCalculatedMedium;
case 2:
type = MPElementTypeCalculatedShort;
break;
return MPElementTypeCalculatedShort;
case 3:
type = MPElementTypeCalculatedBasic;
break;
return MPElementTypeCalculatedBasic;
case 4:
type = MPElementTypeCalculatedPIN;
break;
return MPElementTypeCalculatedPIN;
default:
[NSException raise:NSInternalInconsistencyException
@@ -61,11 +89,9 @@
// Stored
switch (indexPath.row) {
case 0:
type = MPElementTypeStoredPersonal;
break;
return MPElementTypeStoredPersonal;
case 1:
type = MPElementTypeStoredDevicePrivate;
break;
return MPElementTypeStoredDevicePrivate;
default:
[NSException raise:NSInternalInconsistencyException
@@ -79,8 +105,7 @@
format:@"Unsupported section: %d, when selecting element type.", indexPath.section];
}
[delegate didSelectType:type];
[self.navigationController popViewControllerAnimated:YES];
@throw nil;
}
@end

View File

@@ -57,7 +57,10 @@ typedef enum {
#define MPTestFlightCheckpointSetKeyphraseLength @"MPTestFlightCheckpointSetKeyphraseLength_%d"
#endif
NSData *keyPhraseForPassword(NSString *password);
NSData *keyPhraseHashForPassword(NSString *password);
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase);
NSString *NSStringFromMPElementType(MPElementType type);
NSString *ClassNameFromMPElementType(MPElementType type);
Class ClassFromMPElementType(MPElementType type);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter);
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter);

View File

@@ -10,9 +10,27 @@
#import "MPElementGeneratedEntity.h"
#import "MPElementStoredEntity.h"
#define MP_salt nil
#define MP_N 16384
#define MP_r 8
#define MP_p 1
#define MP_hash PearlDigestSHA256
NSData *keyPhraseForPassword(NSString *password) {
return [SCrypt deriveKeyWithLength:64 fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
usingSalt:MP_salt N:MP_N r:MP_r p:MP_p];
}
NSData *keyPhraseHashForPassword(NSString *password) {
return keyPhraseHashForKeyPhrase(keyPhraseForPassword(password));
}
NSData *keyPhraseHashForKeyPhrase(NSData *keyPhrase) {
return [keyPhrase hashWith:MP_hash];
}
NSString *NSStringFromMPElementType(MPElementType type) {
if (!type)
return nil;
@@ -81,7 +99,7 @@ NSString *ClassNameFromMPElementType(MPElementType type) {
}
static NSDictionary *MPTypes_ciphers = nil;
NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPhrase, int counter) {
NSString *MPCalculateContent(MPElementType type, NSString *name, NSData *keyPhrase, uint16_t counter) {
assert(type & MPElementTypeClassCalculated);
@@ -91,15 +109,19 @@ NSString *MPCalculateContent(MPElementType type, NSString *name, NSString *keyPh
// Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase)
assert(name && keyPhrase);
NSData *keyHash = [[NSString stringWithFormat:@"%@-%@-%d", name, keyPhrase, counter] hashWith:PearlDigestSHA1];
NSData *keyHash = [[NSData dataByConcatenatingWithDelimitor:'-' datas:
[name dataUsingEncoding:NSUTF8StringEncoding],
keyPhrase,
htonl(counter),
nil] hashWith:PearlDigestSHA1];
const char *keyBytes = keyHash.bytes;
// Determine the cipher from the first hash byte.
assert([keyHash length]);
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:ClassNameFromMPElementType(type)]
valueForKey:NSStringFromMPElementType(type)];
valueForKey:NSStringFromMPElementType(type)];
NSString *cipher = [typeCiphers objectAtIndex:keyBytes[0] % [typeCiphers count]];
// Encode the content, character by character, using subsequent hash bytes and the cipher.
assert([keyHash length] >= [cipher length] + 1);
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];

View File

@@ -717,4 +717,4 @@ L4m3P4sSw0rD</string>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination"/>
</simulatedMetricsContainer>
</document>
</document>

View File

@@ -18,13 +18,7 @@
#endif
#endif
#define PEARL
#define PEARL_CRYPTO
#define PEARL_UIKIT
#import "Pearl.h"
#import "Pearl-Crypto.h"
#import "Pearl-UIKit.h"
#import "Pearl-Prefix.pch"
#import "MPTypes.h"
#import "MPConfig.h"

View File

@@ -52,6 +52,9 @@
<script type="text/javascript">
function setClass(activeClass) {
$(".Class").css("display", "none");
if (!$(".Class." + activeClass).length)
return "Not found: " + activeClass;
$(".Class." + activeClass).css("display", "block");
}
</script>