Use scrypt for deriving a safer key from the master password + website.
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
/**
|
||||
|
@@ -13,6 +13,6 @@
|
||||
|
||||
@interface MPElementGeneratedEntity : MPElementEntity
|
||||
|
||||
@property (nonatomic, assign) int16_t counter;
|
||||
@property (nonatomic, assign) uint16_t counter;
|
||||
|
||||
@end
|
||||
|
@@ -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) {
|
||||
|
@@ -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:^{
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -12,6 +12,9 @@
|
||||
|
||||
- (void)didSelectType:(MPElementType)type;
|
||||
|
||||
@optional
|
||||
- (MPElementType)selectedType;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPTypeViewController : UITableViewController
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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]];
|
||||
|
@@ -717,4 +717,4 @@ L4m3P4sSw0rD</string>
|
||||
<simulatedOrientationMetrics key="orientation"/>
|
||||
<simulatedScreenMetrics key="destination"/>
|
||||
</simulatedMetricsContainer>
|
||||
</document>
|
||||
</document>
|
@@ -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"
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user