Use scrypt for deriving a safer key from the master password + website.
2
External/Pearl
vendored
@ -1 +1 @@
|
||||
Subproject commit 62421fb92c7421eea8cbf8f784d141425516396a
|
||||
Subproject commit 7263659901f7c5e8135f553e835fdabc7784ef2f
|
@ -11,6 +11,9 @@
|
||||
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
|
||||
DA0A848C14C4DFCB0090EA8E /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0A848B14C4DFCB0090EA8E /* MPElementGeneratedEntity.m */; };
|
||||
DA0B951114C2D69E001D4EB1 /* help.html in Resources */ = {isa = PBXBuildFile; fileRef = DA0B951014C2D69E001D4EB1 /* help.html */; };
|
||||
DA1A144414E4950C00BCFFBE /* libscryptenc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA1A143814E494DE00BCFFBE /* libscryptenc.a */; };
|
||||
DA1A144714E4983200BCFFBE /* SCrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = DA1A144514E4983200BCFFBE /* SCrypt.h */; };
|
||||
DA1A144814E4983200BCFFBE /* SCrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1A144614E4983200BCFFBE /* SCrypt.m */; };
|
||||
DA34DA0D14B1BC7D00F721C1 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA34DA0C14B1BC7D00F721C1 /* MPElementStoredEntity.m */; };
|
||||
DA34DA1114B1BC7E00F721C1 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA34DA1014B1BC7E00F721C1 /* MPElementEntity.m */; };
|
||||
DA41A40B14DB3BF100638533 /* guide_page_0.png in Resources */ = {isa = PBXBuildFile; fileRef = DA41A40914DB3BF100638533 /* guide_page_0.png */; };
|
||||
@ -711,6 +714,27 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
DA1A143714E494DE00BCFFBE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D2AAC07E0554694100DB518D;
|
||||
remoteInfo = scryptenc;
|
||||
};
|
||||
DA1A143914E494DE00BCFFBE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = A0511C5A127770FD00DE46C4;
|
||||
remoteInfo = scryptcrypto;
|
||||
};
|
||||
DA1A143B14E494DE00BCFFBE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = DA67FE5D14E4834300DB7CC9;
|
||||
remoteInfo = util;
|
||||
};
|
||||
DA95D5EE14DF0AB3008D1B94 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
|
||||
@ -748,6 +772,9 @@
|
||||
DA0A848A14C4DFCB0090EA8E /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
|
||||
DA0A848B14C4DFCB0090EA8E /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
|
||||
DA0B951014C2D69E001D4EB1 /* help.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = help.html; path = Resources/help.html; sourceTree = "<group>"; };
|
||||
DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = scrypt.xcodeproj; path = External/Pearl/External/iOSPorts/ports/security/scrypt/scrypt.xcodeproj; sourceTree = "<group>"; };
|
||||
DA1A144514E4983200BCFFBE /* SCrypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SCrypt.h; sourceTree = "<group>"; };
|
||||
DA1A144614E4983200BCFFBE /* SCrypt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SCrypt.m; sourceTree = "<group>"; };
|
||||
DA34DA0B14B1BC7D00F721C1 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
|
||||
DA34DA0C14B1BC7D00F721C1 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
|
||||
DA34DA0F14B1BC7E00F721C1 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
|
||||
@ -1377,7 +1404,6 @@
|
||||
DAC77CDB1482AAD600BCF976 /* WebUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebUtils.m; sourceTree = "<group>"; };
|
||||
DAC77CDD1482AAD600BCF976 /* CryptUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptUtils.h; sourceTree = "<group>"; };
|
||||
DAC77CDE1482AAD600BCF976 /* CryptUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptUtils.m; sourceTree = "<group>"; };
|
||||
DAC77CDF1482AAD600BCF976 /* Dependencies.README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Dependencies.README; sourceTree = "<group>"; };
|
||||
DAC77CE01482AAD600BCF976 /* KeyChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyChain.h; sourceTree = "<group>"; };
|
||||
DAC77CE11482AAD600BCF976 /* KeyChain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeyChain.m; sourceTree = "<group>"; };
|
||||
DAC77CE21482AAD600BCF976 /* Pearl-Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Crypto.h"; sourceTree = "<group>"; };
|
||||
@ -1504,6 +1530,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA1A144414E4950C00BCFFBE /* libscryptenc.a in Frameworks */,
|
||||
DAC63278148680740075AEA5 /* libjrswizzle.a in Frameworks */,
|
||||
DAC63277148680700075AEA5 /* libuicolor-utilities.a in Frameworks */,
|
||||
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */,
|
||||
@ -1863,9 +1890,20 @@
|
||||
path = Resources/Insignia;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA1A142814E494DA00BCFFBE /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA1A143814E494DE00BCFFBE /* libscryptenc.a */,
|
||||
DA1A143A14E494DE00BCFFBE /* libscryptcrypto.a */,
|
||||
DA1A143C14E494DE00BCFFBE /* libutil.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA5BFA39147E415C00F98B1E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */,
|
||||
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */,
|
||||
DA5BFA50147E415C00F98B1E /* MasterPassword */,
|
||||
DAC77CAF148291A600BCF976 /* Pearl */,
|
||||
@ -2250,9 +2288,10 @@
|
||||
DAC77CDC1482AAD600BCF976 /* Pearl-Crypto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA1A144514E4983200BCFFBE /* SCrypt.h */,
|
||||
DA1A144614E4983200BCFFBE /* SCrypt.m */,
|
||||
DAC77CDD1482AAD600BCF976 /* CryptUtils.h */,
|
||||
DAC77CDE1482AAD600BCF976 /* CryptUtils.m */,
|
||||
DAC77CDF1482AAD600BCF976 /* Dependencies.README */,
|
||||
DAC77CE01482AAD600BCF976 /* KeyChain.h */,
|
||||
DAC77CE11482AAD600BCF976 /* KeyChain.m */,
|
||||
DAC77CE21482AAD600BCF976 /* Pearl-Crypto.h */,
|
||||
@ -2529,6 +2568,7 @@
|
||||
DAC781321482AAD800BCF976 /* WebViewController.h in Headers */,
|
||||
DA84808414CB3DBA00A2FA22 /* MessageView.h in Headers */,
|
||||
DA8E8E5514DEB8FC0044257E /* InfoPlist.h in Headers */,
|
||||
DA1A144714E4983200BCFFBE /* SCrypt.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -2646,6 +2686,12 @@
|
||||
mainGroup = DA5BFA39147E415C00F98B1E;
|
||||
productRefGroup = DA5BFA45147E415C00F98B1E /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = DA1A142814E494DA00BCFFBE /* Products */;
|
||||
ProjectRef = DA1A142714E494DA00BCFFBE /* scrypt.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
DA5BFA43147E415C00F98B1E /* MasterPassword */,
|
||||
@ -2657,6 +2703,30 @@
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
DA1A143814E494DE00BCFFBE /* libscryptenc.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libscryptenc.a;
|
||||
remoteRef = DA1A143714E494DE00BCFFBE /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
DA1A143A14E494DE00BCFFBE /* libscryptcrypto.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libscryptcrypto.a;
|
||||
remoteRef = DA1A143914E494DE00BCFFBE /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
DA1A143C14E494DE00BCFFBE /* libutil.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libutil.a;
|
||||
remoteRef = DA1A143B14E494DE00BCFFBE /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DA5BFA42147E415C00F98B1E /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
@ -3347,6 +3417,7 @@
|
||||
DAC781331482AAD800BCF976 /* WebViewController.m in Sources */,
|
||||
DA84808514CB3DBA00A2FA22 /* MessageView.m in Sources */,
|
||||
DA8E8E5614DEB8FC0044257E /* InfoPlist.m in Sources */,
|
||||
DA1A144814E4983200BCFFBE /* SCrypt.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -3545,6 +3616,7 @@
|
||||
DSTROOT = /tmp/Pearl.dst;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = "$(SRCROOT)/External/Pearl/External/iOSPorts/include/**";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -3640,6 +3712,7 @@
|
||||
DSTROOT = /tmp/Pearl.dst;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = "$(SRCROOT)/External/Pearl/External/iOSPorts/include/**";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -3653,6 +3726,7 @@
|
||||
DSTROOT = /tmp/Pearl.dst;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";
|
||||
HEADER_SEARCH_PATHS = "$(SRCROOT)/External/Pearl/External/iOSPorts/include/**";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -3690,6 +3764,7 @@
|
||||
DA95D60E14DF3F3B008D1B94 /* Production */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = {
|
||||
isa = XCConfigurationList;
|
||||
|
@ -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
|
||||
@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)];
|
||||
@ -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.
|
||||
@ -280,7 +287,7 @@
|
||||
[TestFlight passCheckpoint:MPTestFlightCheckpointMPAsked];
|
||||
#endif
|
||||
|
||||
self.keyPhrase = answer;
|
||||
self.keyPhrase = answerKeyPhrase;
|
||||
} cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
MPElementType type;
|
||||
[delegate didSelectType:[self typeAtIndexPath:indexPath]];
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (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,7 +10,25 @@
|
||||
#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)
|
||||
@ -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,7 +109,11 @@ 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.
|
||||
|
@ -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>
|
||||
|
@ -6,6 +6,8 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
#define PEARL_WITH_SCRYPT
|
||||
|
||||
#define PEARL
|
||||
#define PEARL_CRYPTO
|
||||
#define PEARL_UIKIT
|
||||
|
@ -7,7 +7,7 @@ shopt -s extglob
|
||||
## LisuUI
|
||||
#
|
||||
## Submodules that need to be checked out.
|
||||
dependencies=( External/Pearl External/Pearl:External/jrswizzle External/Pearl:External/uicolor-utilities )
|
||||
dependencies=( External/Pearl External/Pearl:External/jrswizzle External/Pearl:External/uicolor-utilities External/Pearl:External/iOSPorts )
|
||||
|
||||
## Custom migration.
|
||||
# None yet.
|
||||
|
209
Site/1/css/screen.css
Normal file
@ -0,0 +1,209 @@
|
||||
html {
|
||||
background: url("../img/back-light.png") center 0;
|
||||
-webkit-box-shadow: inset 0 0 100px #FFF, inset 0 0 100px #FFF;
|
||||
-moz-box-shadow: inset 0 0 100px #FFF, inset 0 0 100px #FFF;
|
||||
box-shadow: inset 0 0 100px #FFF, inset 0 0 100px #FFF;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
color: black;
|
||||
|
||||
font: 120% ExoLight, sans-serif;
|
||||
font-weight: 100;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
text-shadow: #FFF 0 -1px 1px, #AAA 0 0 5px;
|
||||
|
||||
font-family: ExoBold;
|
||||
}
|
||||
strong {
|
||||
font-family: ExoDemiBold;
|
||||
}
|
||||
h1 .sub {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 250%;
|
||||
}
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
}
|
||||
p, blockquote, ul {
|
||||
text-shadow: #FFF 0 -1px 1px, #CCC 0 0 3px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
ul {
|
||||
font-family: ExoDemiBold;
|
||||
font-size: 90%;
|
||||
font-weight: 500;
|
||||
}
|
||||
blockquote {
|
||||
font-family: ExoDemiBold;
|
||||
font-size: 90%;
|
||||
font-weight: bold;
|
||||
}
|
||||
a, .link, :link {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
a:hover, .link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
|
||||
/* Classes */
|
||||
.stripe {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border-width: 1px 0;
|
||||
-webkit-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
-moz-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
|
||||
/* Page */
|
||||
header {
|
||||
position: relative;
|
||||
background: url("../img/back-dark.png") center 0;
|
||||
border-bottom: 1px solid #FFF;
|
||||
-webkit-box-shadow: 0 0 50px #666;
|
||||
-moz-box-shadow: 0 0 50px #666;
|
||||
box-shadow: 0 0 50px #666;
|
||||
|
||||
margin: 0 0 5em;
|
||||
padding: 0 0 1em;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
header h1 {
|
||||
margin: 0;
|
||||
|
||||
color: white;
|
||||
font-size: 350%;
|
||||
}
|
||||
header .logo {
|
||||
width: 200px;
|
||||
}
|
||||
header .divider {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: -90px;
|
||||
}
|
||||
footer {
|
||||
clear: both;
|
||||
padding: 1em 0;
|
||||
|
||||
color: #333;
|
||||
text-shadow: #FFF 0 -1px 1px, #999 0 0 5px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 80%;
|
||||
}
|
||||
section {
|
||||
width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#content section.current {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
hr {
|
||||
background: url("../img/Dividers/Simple.png") center center no-repeat;
|
||||
border: none;
|
||||
height: 5px;
|
||||
}
|
||||
blockquote {
|
||||
margin-left: 5em;
|
||||
}
|
||||
blockquote:before {
|
||||
content: "❝";
|
||||
position: absolute;
|
||||
margin: -0.5ex 0 0 -1em;
|
||||
font-size: 300%;
|
||||
font-family: ExoLight;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.side {
|
||||
float: right;
|
||||
margin: 1em -10em 1em 1em;
|
||||
}
|
||||
.sidebox {
|
||||
background: url("../img/Dividers/Divider_H.png") center bottom no-repeat;
|
||||
position: relative;
|
||||
float: right;
|
||||
margin: 1em;
|
||||
width: 546px;
|
||||
text-align: center;
|
||||
}
|
||||
.sidebox .clip {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: -1;
|
||||
}
|
||||
.frontpage .sidebox {
|
||||
margin-right: -100px;
|
||||
}
|
||||
.frontpage .sidebox .clip {
|
||||
height: 416px;
|
||||
margin-bottom: 84px;
|
||||
}
|
||||
.hoverShow {
|
||||
display: none;
|
||||
}
|
||||
*:hover>.hoverShow {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
-moz-transition: all 0.2s ease-in-out;
|
||||
-ms-transition: all 0.2s ease-in-out;
|
||||
-o-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
section.active {
|
||||
display: block;
|
||||
}
|
||||
a.previous, a.next {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: -2.5em 0 0 0;
|
||||
|
||||
font-size: 150%;
|
||||
font-family: ExoDemiBold;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.previous {
|
||||
margin-left: -3em;
|
||||
}
|
||||
a.previous:before {
|
||||
content: "< ";
|
||||
}
|
||||
a.next {
|
||||
margin-left: 3em;
|
||||
|
||||
text-align: right;
|
||||
}
|
||||
a.next:after {
|
||||
content: " >";
|
||||
}
|
||||
|
||||
#frontpage .sidebox {
|
||||
margin-top: -50px;
|
||||
}
|
BIN
Site/1/img/Dividers/Divider_H.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
Site/1/img/Dividers/Divider_V.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
Site/1/img/Dividers/Simple.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Site/1/img/MasterPassword_1.png
Normal file
After Width: | Height: | Size: 222 KiB |
BIN
Site/1/img/MasterPassword_2.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
Site/1/img/back-dark.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
Site/1/img/back-light.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Site/1/img/iTunesArtwork-Bare.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
Site/1/img/iTunesArtwork-Clipped.png
Normal file
After Width: | Height: | Size: 109 KiB |
200
Site/1/index.html
Normal file
@ -0,0 +1,200 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Master Password — Securing your online life.</title>
|
||||
|
||||
<link rel="icon" href="images/resources/favicon.png" type="image/x-png" />
|
||||
<link rel="shortcut icon" href="images/resources/favicon.png" type="image/x-png" />
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/sprites/glossy-black/sprites.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/sprites/silk/sprites.css" />
|
||||
<link rel="stylesheet" type="text/css" href="fnt/Exo/stylesheet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/screen.css" />
|
||||
|
||||
<script src="js/jquery-1.6.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/functions.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
function navigateTo(section) {
|
||||
$("section").removeClass("active");
|
||||
if (section.length)
|
||||
$("section." + section).addClass("active");
|
||||
if (!$("section.active").length)
|
||||
$("section.frontpage").addClass("active");
|
||||
}
|
||||
$(document).ready(function() {
|
||||
navigateTo(document.location.hash.substring(1));
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<img class="logo" src="img/iTunesArtwork-Bare.png" />
|
||||
<h1>Master Password</h1>
|
||||
<div class="divider">
|
||||
</div>
|
||||
</header>
|
||||
<section class="frontpage">
|
||||
|
||||
<a class="next" href="#about" onClick="navigateTo($(this).attr('href').substring(1));">What is this thing?</a>
|
||||
|
||||
<div class="sidebox">
|
||||
<div class="clip">
|
||||
<img src="img/MasterPassword_1.png" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Stop worrying about <em>passwords</em></h1>
|
||||
|
||||
<h2>Memorising passwords or even saving them in our browser, an application or the cloud just isn't good enough.</h2>
|
||||
|
||||
<p>Master Password is a solution that <strong>voids the need to <em>keep</em> your passwords anywhere</strong>. Not in your head, not on your computer and not in the cloud. Nothing to store means nothing to loose. At the same time it makes sure that your accounts are adequately protected with <em>exclusive</em> passwords.</p>
|
||||
|
||||
<p>With Master Password, you <strong>remember <em>one</em> secure password</strong> and use that with the application to <strong>generate <em>any</em> password you might need</strong>. You could even generate PIN codes with it, if you wanted. Today, it's time to <em>stop worrying</em> about passwords and get on with what we need to get done.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
|
||||
<a class="previous" href="#frontpage" onClick="navigateTo($(this).attr('href').substring(1));">Front page</a>
|
||||
<a class="next" href="#algorithm" onClick="navigateTo($(this).attr('href').substring(1));">How does it work?</a>
|
||||
|
||||
<h1>What is this?</h1>
|
||||
|
||||
<p>
|
||||
Master Password is a revolution in password management.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It aims to secure your online (and offline!) life by changing the way you deal with passwords.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>Revolution? Why would I need that?</h1>
|
||||
|
||||
<p>
|
||||
You already know the problem:<br />
|
||||
Passwords are confidential information between you and a site. They should never be shared with anyone else, definitely not other sites. Yet that's exactly what happens with most of us: Hundereds of online accounts to manage and authenticate, we can't help but reuse one, two or five passwords that we can remember. Maybe we keep a paper stuck to our monitor with a list of passwords on them, because we realize the truth:
|
||||
</p>
|
||||
|
||||
<blockquote>It is impossible to remember a secure password for each of our accounts and still keep those passwords both <em>exclusive</em> and <em>confidential</em>.</blockquote>
|
||||
|
||||
<p>
|
||||
Multiple solutions exist:<br />
|
||||
Sites that realize that passwords aren't the end-all of authentication usually implement some sort of alternative authentication mechanism: OpenID, SAML, some form of mobile authentication, secure tokens, etc.<br />
|
||||
The problem here is that these solutions only work for the select few sites that have chosen to implement them; and then you, the user, are stuck with whatever mechanism the site has chosen for you.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To solve the problem for other sites, there are programs that remember our passwords for us.<br />
|
||||
The problem with these is that they do not actually help us with setting exclusive and confidential passwords for our accounts. They just offload the work of remembering passwords, and at a great expense: If you loose your data, you loose your online identity and are locked out of everything.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>So, I guess you claim to do better?</h1>
|
||||
|
||||
<p>
|
||||
Master Password aims to turn the tables in favor of the user, you.<br />
|
||||
In the end, <em>what we really want</em> is a way of dealing with passwords in an exclusive and confidential way without having to remember them, and without running the risk of losing our online identity to fraudsters.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Master Password does exactly this. You remember a single master password. Make it a long and secure one. Master Password uses this password along with the name of the site that you want to log into and generates a secure but unique password for that site. What's more, it doesn't store this information anywhere. If you loose your phone, the thieves can get none the wiser from it. You kick yourself for losing your phone, pick up any other phone, start the application, enter your master password, and instantly have access to all your passwords again. No sync, no backups, no hassle.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Built with the highest security considerations in mind.</li>
|
||||
<li>Designed with beauty, elegance, simplicity and usability in mind.</li>
|
||||
<li>Different types of passwords can be generated to curb sites with strange password policies.</li>
|
||||
<li>A password counter lets you generate a new password for a site in case it gets compromised.</li>
|
||||
<li>Master password can be either:
|
||||
<ul>
|
||||
<li>Stored securely on the device (so you don't need to enter it anymore).</li>
|
||||
<li>Not stored but remembered between sessions (so you only enter it once after powering on).</li>
|
||||
<li>Not stored or remembered and required for every usage of the application (safest).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For those cases where you cannot change your account's password, the application will encrypt passwords with your master password and store them securely (as explained, stored passwords can get lost).</li>
|
||||
<li>Integrates with iCloud to synchronize and back up your site history and stored passwords.</li>
|
||||
<li>For those that care to know, the password generation algorithm is open and documented within the application.</li>
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>OK, I'm convinced. Where do I get in?</h1>
|
||||
|
||||
<p>
|
||||
Master Password is currently in beta.<br />
|
||||
Anyone interested in joining the beta is invited to <a href="http://bit.ly/vNN5Zi">join the Lyndir TestFlight team</a>. Every so often new pending testers are admitted to the Master Password beta testers team.
|
||||
</p>
|
||||
<p>
|
||||
Participation in the beta is free of charge, but does come with the expectation that you will contribute. Comment constructively, report issues and propose improvements.
|
||||
</p>
|
||||
<p>
|
||||
Post-beta, Master Password is expected to sell for somewhere around 7 USD. The most helpful testers will receive the final version (and all future updates) free of charge.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
<section class="algorithm">
|
||||
|
||||
<a class="previous" href="#about" onClick="navigateTo($(this).attr('href').substring(1));">What is this thing?</a>
|
||||
<a class="next" href="https://github.com/Lyndir/MasterPassword">View the code</a>
|
||||
|
||||
<h1>So how does it work?</h1>
|
||||
|
||||
<p>
|
||||
The theory behind Master Password is simple. The user remembers a single, secure password. The user only ever uses that password to log into the Master Password application. This master password is then used as a seed to generate a different password based on the name of the site to generate a password for.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The result is that each master password generates its own unique sequence of passwords for any site name. Since the only input data is the master password and the site name (along with a password counter, see below), there is no need for any kind of storage to recreate a site's password. All that's needed is the correct master password and the correct algorithm implementation. What that does for you is make it almost impossible to loose your passwords. It also makes it nearly impossible for hackers to steal your online identity.
|
||||
</p>
|
||||
|
||||
<h1>The algorithm</h1>
|
||||
<p>
|
||||
Alright, let's describe the process in detail. This part will likely make sense to you only if you're well versed in computer security jargon. If you're the kind of person who likes to know how the clock ticks before deciding that it can be trusted to keep ticking, read on.
|
||||
</p>
|
||||
<p>
|
||||
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. When the user requests a password be generated for a site, the application composes a string consisting of the site name, the master password, and a password counter, delimited in that order by a dash character, and hashes those <code>UTF-8</code> bytes using the <code>SHA-1</code> algorithm. The bytes resulting from this hashing operation are called the <code>keyBytes</code> in the next steps.
|
||||
</p>
|
||||
<code><pre>
|
||||
keyBytes = sha1( site name "-" master password "-" password counter )
|
||||
</pre></code>
|
||||
<p>
|
||||
Next, we need the password type that the user has chosen to use for the site. Password types determine the
|
||||
<q>cipher</q> that will be used to encrypt <code>keyBytes</code> into a readable password. For
|
||||
instance, the standard password type <q>Long Password</q> activates one of three pre-set ciphers:
|
||||
<code>CvcvCvcvnoCvcv</code>, <code>CvcvnoCvcvCvcv</code> or <code>CvcvCvcvCvcvno</code>. Which of those
|
||||
will be used, depends on the first of the <code>keyBytes</code>. Take the byte value modulo the amount of
|
||||
pre-set ciphers (in this case, three), and the result tells you which of the three ciphers to use.
|
||||
</p>
|
||||
<code><pre>
|
||||
ciphers = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno" ]
|
||||
cipher = ciphers[ keyBytes[0] % count( ciphers ) ]
|
||||
</pre></code>
|
||||
<p>
|
||||
Now that we know what cipher to use for building our final password, all that's left is to iterate the
|
||||
cipher, and produce a character of password output for each step. When you iterate the cipher (<code>i</code>), every
|
||||
character in the cipher represents a set of possible output characters (<code>passChars</code>). For instance, a <code>C</code>
|
||||
character in the cipher indicates that we need to choose a capital consonant character. An <code>o</code>
|
||||
character in the cipher indicates that we need to choose an <q>other</q> (symbol) character. Exactly which
|
||||
character to choose in that set for the password output depends on the next byte from <code>keyBytes</code>.
|
||||
Like before, take the next unused <code>keyByte</code>'s byte value modulo the amount of characters in the
|
||||
set of possible output characters for the cipher iteration and use the result to choose the output
|
||||
character (<code>passChar</code>). Repeat until you've iterated the whole cipher.
|
||||
</p>
|
||||
<code><pre>
|
||||
passChar = passChars[ keyBytes[i + 1] % count( passChars ) ]
|
||||
passWord += passChar
|
||||
</pre></code>
|
||||
|
||||
</section>
|
||||
<footer>
|
||||
Master Password is a security and productivity product by <a href="http://www.lyndir.com">Lyndir</a>, © 2011.
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
61
Site/1/js/functions.js
Normal file
@ -0,0 +1,61 @@
|
||||
// jQuery plugin: PutCursorAtEnd 1.0
|
||||
// http://plugins.jquery.com/project/PutCursorAtEnd
|
||||
// by teedyay
|
||||
//
|
||||
// Puts the cursor at the end of a textbox/ textarea
|
||||
|
||||
// codesnippet: 691e18b1-f4f9-41b4-8fe8-bc8ee51b48d4
|
||||
(function($)
|
||||
{
|
||||
jQuery.fn.putCursorAtEnd = function()
|
||||
{
|
||||
return this.each(function()
|
||||
{
|
||||
$(this).focus()
|
||||
|
||||
// If this function exists...
|
||||
if (this.setSelectionRange)
|
||||
{
|
||||
// ... then use it
|
||||
// (Doesn't work in IE)
|
||||
|
||||
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
|
||||
var len = $(this).val().length * 2;
|
||||
this.setSelectionRange(len, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ... otherwise replace the contents with itself
|
||||
// (Doesn't work in Google Chrome)
|
||||
$(this).val($(this).val());
|
||||
}
|
||||
|
||||
// Scroll to the bottom, in case we're in a tall textarea
|
||||
// (Necessary for Firefox and Google Chrome)
|
||||
this.scrollTop = 999999;
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
|
||||
// Show the content element referenced by the document's hash
|
||||
function updateHash() {
|
||||
var hashContent = document.location.hash.split('/', 1)[0];
|
||||
var foundCurrent = false;
|
||||
var contentElement = $(hashContent + "-content");
|
||||
if (contentElement.size() != 1)
|
||||
contentElement = $("#about-content");
|
||||
|
||||
$("#content section").each(function (i) {
|
||||
if (foundCurrent)
|
||||
this.className = "future";
|
||||
else {
|
||||
if (this.id == contentElement.attr("id")) {
|
||||
foundCurrent = true;
|
||||
this.className = "current";
|
||||
} else
|
||||
this.className = "past";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
18
Site/1/js/jquery-1.6.1.min.js
vendored
Normal file
214
Site/2/css/screen.css
Normal file
@ -0,0 +1,214 @@
|
||||
html {
|
||||
background: url("../img/back-dark.png") center 0;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
color: white;
|
||||
|
||||
font: 120% Exo, sans-serif;
|
||||
font-weight: 100;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
text-shadow: #000 0 1px 1px;
|
||||
color: #999;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
strong {
|
||||
font-weight: 400;
|
||||
}
|
||||
h1 .sub {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 250%;
|
||||
}
|
||||
h2 {
|
||||
color: #666;
|
||||
font-size: inherit;
|
||||
font-weight: 400;
|
||||
}
|
||||
p, blockquote, ul {
|
||||
text-shadow: #FFF 0 -1px 1px, #CCC 0 0 3px;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
ul {
|
||||
font-weight: 600;
|
||||
font-size: 90%;
|
||||
font-weight: 500;
|
||||
}
|
||||
blockquote {
|
||||
font-weight: 600;
|
||||
font-size: 90%;
|
||||
font-weight: bold;
|
||||
}
|
||||
a, .link, :link {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
a:hover, .link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
|
||||
/* Classes */
|
||||
.stripe {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border-width: 1px 0;
|
||||
-webkit-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
-moz-box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5), 2px 2px 6px rgba(200, 200, 200, 0.5);
|
||||
}
|
||||
|
||||
/* Page */
|
||||
header {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 900px;
|
||||
margin: 2em auto 0;
|
||||
}
|
||||
header h1 {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
|
||||
color: inherit;
|
||||
font-size: 150%;
|
||||
}
|
||||
header .logo {
|
||||
height: 3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
header .divider {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: -90px;
|
||||
}
|
||||
footer {
|
||||
clear: both;
|
||||
padding: 1em 0;
|
||||
|
||||
color: #333;
|
||||
text-shadow: #FFF 0 -1px 1px, #999 0 0 5px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 80%;
|
||||
}
|
||||
section {
|
||||
background: url("../img/back-light.png") center 0;
|
||||
-webkit-box-shadow: 2px 2px 2px #000, 0 0 50px #CCC, inset 0 0 50px #FFF;
|
||||
-moz-box-shadow: 2px 2px 2px #000, 0 0 50px #AAA, inset 0 0 50px #FFF;
|
||||
box-shadow: 1px 1px 4px #000, 0 0 200px #666;
|
||||
border-radius: 5px;
|
||||
border: 1px solid white;
|
||||
color: black;
|
||||
|
||||
width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 2em;
|
||||
}
|
||||
#content section.current {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
hr {
|
||||
background: url("../img/Dividers/Simple.png") center center no-repeat;
|
||||
border: none;
|
||||
height: 5px;
|
||||
}
|
||||
blockquote {
|
||||
margin-left: 5em;
|
||||
}
|
||||
blockquote:before {
|
||||
content: "❝";
|
||||
position: absolute;
|
||||
margin: -0.5ex 0 0 -1em;
|
||||
font-size: 300%;
|
||||
font-weight: 200;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.side {
|
||||
float: right;
|
||||
margin: 1em -10em 1em 1em;
|
||||
}
|
||||
.sidebox {
|
||||
background: url("../img/Dividers/Divider_H.png") center bottom no-repeat;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
float: right;
|
||||
margin: 1em;
|
||||
width: 546px;
|
||||
text-align: center;
|
||||
}
|
||||
.sidebox .clip {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
.frontpage .sidebox {
|
||||
margin-right: -100px;
|
||||
}
|
||||
.frontpage .sidebox .clip {
|
||||
height: 416px;
|
||||
margin-bottom: 84px;
|
||||
}
|
||||
.hoverShow {
|
||||
display: none;
|
||||
}
|
||||
*:hover>.hoverShow {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
section {
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
-moz-transition: all 0.2s ease-in-out;
|
||||
-ms-transition: all 0.2s ease-in-out;
|
||||
-o-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
section.active {
|
||||
display: block;
|
||||
}
|
||||
a.previous, a.next {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin: -2.5em 0 0 0;
|
||||
|
||||
font-size: 150%;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.previous {
|
||||
margin-left: -3em;
|
||||
}
|
||||
a.previous:before {
|
||||
content: "< ";
|
||||
}
|
||||
a.next {
|
||||
margin-left: 3em;
|
||||
|
||||
text-align: right;
|
||||
}
|
||||
a.next:after {
|
||||
content: " >";
|
||||
}
|
||||
|
||||
#frontpage .sidebox {
|
||||
margin-top: -50px;
|
||||
}
|
BIN
Site/2/img/Dividers/Divider_H.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
Site/2/img/Dividers/Divider_V.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
Site/2/img/Dividers/Simple.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Site/2/img/MasterPassword_1.png
Normal file
After Width: | Height: | Size: 222 KiB |
BIN
Site/2/img/MasterPassword_2.png
Normal file
After Width: | Height: | Size: 257 KiB |
BIN
Site/2/img/back-dark.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
Site/2/img/back-light.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Site/2/img/iTunesArtwork-Bare.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
Site/2/img/iTunesArtwork-Clipped.png
Normal file
After Width: | Height: | Size: 109 KiB |
200
Site/2/index.html
Normal file
@ -0,0 +1,200 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Master Password — Securing your online life.</title>
|
||||
|
||||
<link rel="icon" href="images/resources/favicon.png" type="image/x-png" />
|
||||
<link rel="shortcut icon" href="images/resources/favicon.png" type="image/x-png" />
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
|
||||
|
||||
<link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Exo:100,200,300,400,500,600,700,800,900,100italic,200italic,300italic,400italic,500italic,600italic,700italic,800italic,900italic' />
|
||||
<link rel="stylesheet" type="text/css" href="css/sprites/glossy-black/sprites.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/sprites/silk/sprites.css" />
|
||||
<link rel="stylesheet" type="text/css" href="css/screen.css" />
|
||||
|
||||
<script src="js/jquery-1.6.1.min.js" type="text/javascript"></script>
|
||||
<script src="js/functions.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
function navigateTo(section) {
|
||||
$("section").removeClass("active");
|
||||
if (section.length)
|
||||
$("section." + section).addClass("active");
|
||||
if (!$("section.active").length)
|
||||
$("section.frontpage").addClass("active");
|
||||
}
|
||||
$(document).ready(function() {
|
||||
navigateTo(document.location.hash.substring(1));
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<img class="logo" src="img/iTunesArtwork-Bare.png" />
|
||||
<h1>Master Password</h1>
|
||||
<div class="divider">
|
||||
</div>
|
||||
</header>
|
||||
<section class="frontpage">
|
||||
|
||||
<a class="next" href="#about" onClick="navigateTo($(this).attr('href').substring(1));">What is this thing?</a>
|
||||
|
||||
<div class="sidebox">
|
||||
<div class="clip">
|
||||
<img src="img/MasterPassword_1.png" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Stop worrying about <em>passwords</em></h1>
|
||||
|
||||
<h2>Memorising passwords or even saving them in our browser, an application or the cloud just isn't good enough.</h2>
|
||||
|
||||
<p>Master Password is a solution that <strong>voids the need to <em>keep</em> your passwords anywhere</strong>. Not in your head, not on your computer and not in the cloud. Nothing to store means nothing to loose. At the same time it makes sure that your accounts are adequately protected with <em>exclusive</em> passwords.</p>
|
||||
|
||||
<p>With Master Password, you <strong>remember <em>one</em> secure password</strong> and use that with the application to <strong>generate <em>any</em> password you might need</strong>. You could even generate PIN codes with it, if you wanted. Today, it's time to <em>stop worrying</em> about passwords and get on with what we need to get done.</p>
|
||||
</section>
|
||||
<section class="about">
|
||||
|
||||
<a class="previous" href="#frontpage" onClick="navigateTo($(this).attr('href').substring(1));">Front page</a>
|
||||
<a class="next" href="#algorithm" onClick="navigateTo($(this).attr('href').substring(1));">How does it work?</a>
|
||||
|
||||
<h1>What is this?</h1>
|
||||
|
||||
<p>
|
||||
Master Password is a revolution in password management.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It aims to secure your online (and offline!) life by changing the way you deal with passwords.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>Revolution? Why would I need that?</h1>
|
||||
|
||||
<p>
|
||||
You already know the problem:<br />
|
||||
Passwords are confidential information between you and a site. They should never be shared with anyone else, definitely not other sites. Yet that's exactly what happens with most of us: Hundereds of online accounts to manage and authenticate, we can't help but reuse one, two or five passwords that we can remember. Maybe we keep a paper stuck to our monitor with a list of passwords on them, because we realize the truth:
|
||||
</p>
|
||||
|
||||
<blockquote>It is impossible to remember a secure password for each of our accounts and still keep those passwords both <em>exclusive</em> and <em>confidential</em>.</blockquote>
|
||||
|
||||
<p>
|
||||
Multiple solutions exist:<br />
|
||||
Sites that realize that passwords aren't the end-all of authentication usually implement some sort of alternative authentication mechanism: OpenID, SAML, some form of mobile authentication, secure tokens, etc.<br />
|
||||
The problem here is that these solutions only work for the select few sites that have chosen to implement them; and then you, the user, are stuck with whatever mechanism the site has chosen for you.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To solve the problem for other sites, there are programs that remember our passwords for us.<br />
|
||||
The problem with these is that they do not actually help us with setting exclusive and confidential passwords for our accounts. They just offload the work of remembering passwords, and at a great expense: If you loose your data, you loose your online identity and are locked out of everything.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>So, I guess you claim to do better?</h1>
|
||||
|
||||
<p>
|
||||
Master Password aims to turn the tables in favor of the user, you.<br />
|
||||
In the end, <em>what we really want</em> is a way of dealing with passwords in an exclusive and confidential way without having to remember them, and without running the risk of losing our online identity to fraudsters.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Master Password does exactly this. You remember a single master password. Make it a long and secure one. Master Password uses this password along with the name of the site that you want to log into and generates a secure but unique password for that site. What's more, it doesn't store this information anywhere. If you loose your phone, the thieves can get none the wiser from it. You kick yourself for losing your phone, pick up any other phone, start the application, enter your master password, and instantly have access to all your passwords again. No sync, no backups, no hassle.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Built with the highest security considerations in mind.</li>
|
||||
<li>Designed with beauty, elegance, simplicity and usability in mind.</li>
|
||||
<li>Different types of passwords can be generated to curb sites with strange password policies.</li>
|
||||
<li>A password counter lets you generate a new password for a site in case it gets compromised.</li>
|
||||
<li>Master password can be either:
|
||||
<ul>
|
||||
<li>Stored securely on the device (so you don't need to enter it anymore).</li>
|
||||
<li>Not stored but remembered between sessions (so you only enter it once after powering on).</li>
|
||||
<li>Not stored or remembered and required for every usage of the application (safest).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For those cases where you cannot change your account's password, the application will encrypt passwords with your master password and store them securely (as explained, stored passwords can get lost).</li>
|
||||
<li>Integrates with iCloud to synchronize and back up your site history and stored passwords.</li>
|
||||
<li>For those that care to know, the password generation algorithm is open and documented within the application.</li>
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
|
||||
<h1>OK, I'm convinced. Where do I get in?</h1>
|
||||
|
||||
<p>
|
||||
Master Password is currently in beta.<br />
|
||||
Anyone interested in joining the beta is invited to <a href="http://bit.ly/vNN5Zi">join the Lyndir TestFlight team</a>. Every so often new pending testers are admitted to the Master Password beta testers team.
|
||||
</p>
|
||||
<p>
|
||||
Participation in the beta is free of charge, but does come with the expectation that you will contribute. Comment constructively, report issues and propose improvements.
|
||||
</p>
|
||||
<p>
|
||||
Post-beta, Master Password is expected to sell for somewhere around 7 USD. The most helpful testers will receive the final version (and all future updates) free of charge.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
<section class="algorithm">
|
||||
|
||||
<a class="previous" href="#about" onClick="navigateTo($(this).attr('href').substring(1));">What is this thing?</a>
|
||||
<a class="next" href="https://github.com/Lyndir/MasterPassword">View the code</a>
|
||||
|
||||
<h1>So how does it work?</h1>
|
||||
|
||||
<p>
|
||||
The theory behind Master Password is simple. The user remembers a single, secure password. The user only ever uses that password to log into the Master Password application. This master password is then used as a seed to generate a different password based on the name of the site to generate a password for.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The result is that each master password generates its own unique sequence of passwords for any site name. Since the only input data is the master password and the site name (along with a password counter, see below), there is no need for any kind of storage to recreate a site's password. All that's needed is the correct master password and the correct algorithm implementation. What that does for you is make it almost impossible to loose your passwords. It also makes it nearly impossible for hackers to steal your online identity.
|
||||
</p>
|
||||
|
||||
<h1>The algorithm</h1>
|
||||
<p>
|
||||
Alright, let's describe the process in detail. This part will likely make sense to you only if you're well versed in computer security jargon. If you're the kind of person who likes to know how the clock ticks before deciding that it can be trusted to keep ticking, read on.
|
||||
</p>
|
||||
<p>
|
||||
The user chooses a single master password, preferably sufficiently long to harden against brute-force attacks. When the user requests a password be generated for a site, the application composes a string consisting of the site name, the master password, and a password counter, delimited in that order by a dash character, and hashes those <code>UTF-8</code> bytes using the <code>SHA-1</code> algorithm. The bytes resulting from this hashing operation are called the <code>keyBytes</code> in the next steps.
|
||||
</p>
|
||||
<code><pre>
|
||||
keyBytes = sha1( site name "-" master password "-" password counter )
|
||||
</pre></code>
|
||||
<p>
|
||||
Next, we need the password type that the user has chosen to use for the site. Password types determine the
|
||||
<q>cipher</q> that will be used to encrypt <code>keyBytes</code> into a readable password. For
|
||||
instance, the standard password type <q>Long Password</q> activates one of three pre-set ciphers:
|
||||
<code>CvcvCvcvnoCvcv</code>, <code>CvcvnoCvcvCvcv</code> or <code>CvcvCvcvCvcvno</code>. Which of those
|
||||
will be used, depends on the first of the <code>keyBytes</code>. Take the byte value modulo the amount of
|
||||
pre-set ciphers (in this case, three), and the result tells you which of the three ciphers to use.
|
||||
</p>
|
||||
<code><pre>
|
||||
ciphers = [ "CvcvCvcvnoCvcv", "CvcvnoCvcvCvcv", "CvcvCvcvCvcvno" ]
|
||||
cipher = ciphers[ keyBytes[0] % count( ciphers ) ]
|
||||
</pre></code>
|
||||
<p>
|
||||
Now that we know what cipher to use for building our final password, all that's left is to iterate the
|
||||
cipher, and produce a character of password output for each step. When you iterate the cipher (<code>i</code>), every
|
||||
character in the cipher represents a set of possible output characters (<code>passChars</code>). For instance, a <code>C</code>
|
||||
character in the cipher indicates that we need to choose a capital consonant character. An <code>o</code>
|
||||
character in the cipher indicates that we need to choose an <q>other</q> (symbol) character. Exactly which
|
||||
character to choose in that set for the password output depends on the next byte from <code>keyBytes</code>.
|
||||
Like before, take the next unused <code>keyByte</code>'s byte value modulo the amount of characters in the
|
||||
set of possible output characters for the cipher iteration and use the result to choose the output
|
||||
character (<code>passChar</code>). Repeat until you've iterated the whole cipher.
|
||||
</p>
|
||||
<code><pre>
|
||||
passChar = passChars[ keyBytes[i + 1] % count( passChars ) ]
|
||||
passWord += passChar
|
||||
</pre></code>
|
||||
|
||||
</section>
|
||||
<footer>
|
||||
Master Password is a security and productivity product by <a href="http://www.lyndir.com">Lyndir</a>, © 2011.
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
61
Site/2/js/functions.js
Normal file
@ -0,0 +1,61 @@
|
||||
// jQuery plugin: PutCursorAtEnd 1.0
|
||||
// http://plugins.jquery.com/project/PutCursorAtEnd
|
||||
// by teedyay
|
||||
//
|
||||
// Puts the cursor at the end of a textbox/ textarea
|
||||
|
||||
// codesnippet: 691e18b1-f4f9-41b4-8fe8-bc8ee51b48d4
|
||||
(function($)
|
||||
{
|
||||
jQuery.fn.putCursorAtEnd = function()
|
||||
{
|
||||
return this.each(function()
|
||||
{
|
||||
$(this).focus()
|
||||
|
||||
// If this function exists...
|
||||
if (this.setSelectionRange)
|
||||
{
|
||||
// ... then use it
|
||||
// (Doesn't work in IE)
|
||||
|
||||
// Double the length because Opera is inconsistent about whether a carriage return is one character or two. Sigh.
|
||||
var len = $(this).val().length * 2;
|
||||
this.setSelectionRange(len, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ... otherwise replace the contents with itself
|
||||
// (Doesn't work in Google Chrome)
|
||||
$(this).val($(this).val());
|
||||
}
|
||||
|
||||
// Scroll to the bottom, in case we're in a tall textarea
|
||||
// (Necessary for Firefox and Google Chrome)
|
||||
this.scrollTop = 999999;
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
|
||||
// Show the content element referenced by the document's hash
|
||||
function updateHash() {
|
||||
var hashContent = document.location.hash.split('/', 1)[0];
|
||||
var foundCurrent = false;
|
||||
var contentElement = $(hashContent + "-content");
|
||||
if (contentElement.size() != 1)
|
||||
contentElement = $("#about-content");
|
||||
|
||||
$("#content section").each(function (i) {
|
||||
if (foundCurrent)
|
||||
this.className = "future";
|
||||
else {
|
||||
if (this.id == contentElement.attr("id")) {
|
||||
foundCurrent = true;
|
||||
this.className = "current";
|
||||
} else
|
||||
this.className = "past";
|
||||
}
|
||||
});
|
||||
}
|
||||
|