From ed0a57a6072394b5645148e6e1bdbae3aba1112c Mon Sep 17 00:00:00 2001 From: Michael Starke Date: Thu, 31 Jan 2019 13:39:56 +0100 Subject: [PATCH] Password generator now allows to require a character form each character group --- MacPass/Base.lproj/PasswordCreatorView.xib | 2 +- MacPass/MPPasswordCreatorViewController.m | 37 ++++------ MacPass/NSString+MPPasswordCreation.h | 3 +- MacPass/NSString+MPPasswordCreation.m | 78 ++++++++++++++++++---- 4 files changed, 80 insertions(+), 40 deletions(-) diff --git a/MacPass/Base.lproj/PasswordCreatorView.xib b/MacPass/Base.lproj/PasswordCreatorView.xib index 15a3bfce..9cf417fc 100644 --- a/MacPass/Base.lproj/PasswordCreatorView.xib +++ b/MacPass/Base.lproj/PasswordCreatorView.xib @@ -10,7 +10,7 @@ - + diff --git a/MacPass/MPPasswordCreatorViewController.m b/MacPass/MPPasswordCreatorViewController.m index 99ddd4ce..5c23850e 100644 --- a/MacPass/MPPasswordCreatorViewController.m +++ b/MacPass/MPPasswordCreatorViewController.m @@ -68,7 +68,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { @property (strong) IBOutlet NSButton *numbersButton; @property (strong) IBOutlet NSButton *symbolsButton; @property (strong) IBOutlet NSButton *customButton; -@property (strong) IBOutlet NSButton *ensureCharacterFromEachGroupButton; +@property (strong) IBOutlet NSButton *ensureOccuranceButton; @property (strong) IBOutlet NSButton *setDefaultButton; @property (strong) IBOutlet NSTextField *entropyTextField; @property (strong) IBOutlet NSLevelIndicator *entropyIndicator; @@ -76,7 +76,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { @property (nonatomic, copy) NSString *customString; @property (nonatomic, assign) BOOL useCustomString; -@property (nonatomic, assign) BOOL useCharacterFromEachGroup; +@property (nonatomic, assign) BOOL ensureOccurance; @property (nonatomic, assign) NSUInteger passwordLength; @property (nonatomic, assign) CGFloat entropy; @@ -98,7 +98,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { _entropy = 0.0; _useEntryDefaults = NO; _allowsEntryDefaults = NO; - _useCharacterFromEachGroup = NO; + _ensureOccurance = NO; [self _setupDefaults]; } return self; @@ -128,7 +128,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { self.customCharactersTextField.delegate = self; [self.customButton bind:NSValueBinding toObject:self withKeyPath:NSStringFromSelector(@selector(useCustomString)) options:nil]; - [self.ensureCharacterFromEachGroupButton bind:NSValueBinding toObject:self withKeyPath:NSStringFromSelector(@selector(useCharacterFromEachGroup)) options:nil]; + [self.ensureOccuranceButton bind:NSValueBinding toObject:self withKeyPath:NSStringFromSelector(@selector(ensureOccurance)) options:nil]; NSString *copyToPasteBoardKeyPath = [MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyCopyGeneratedPasswordToClipboard]; NSUserDefaultsController *defaultsController = NSUserDefaultsController.sharedUserDefaultsController; @@ -171,7 +171,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { - (IBAction)_generatePassword:(id)sender { self.password = [NSString passwordWithCharactersets:self.characterFlags withCustomCharacters:self._customCharacters - ensureOccurence:self.useCharacterFromEachGroup + ensureOccurence:self.ensureOccurance length:self.passwordLength]; } @@ -228,7 +228,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { entryDefaults[kMPSettingsKeyPasswordCharacterFlags] = @(self.characterFlags); entryDefaults[kMPSettingsKeyPasswordUseCustomString] = @(self.useCustomString); entryDefaults[kMPSettingsKeyPasswordCustomString] = self.customCharactersTextField.stringValue; - entryDefaults[kMPSettingsKeyPasswordEnsureOccurance] = @(self.useCharacterFromEachGroup); + entryDefaults[kMPSettingsKeyPasswordEnsureOccurance] = @(self.ensureOccurance); NSMutableDictionary *availableDefaults = [[self _availableEntryDefaults] mutableCopy]; if(!availableDefaults) { availableDefaults = [[NSMutableDictionary alloc] initWithCapacity:1]; @@ -284,7 +284,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { if(![_password isEqualToString:password]) { _password = [password copy]; NSString *customString = self.useCustomString ? self.customCharactersTextField.stringValue : nil; - self.entropy = [password entropyWhithPossibleCharacterSet:self.characterFlags andCustomCharacters:customString]; + self.entropy = [password entropyWhithCharacterSet:self.characterFlags customCharacters:customString ensureOccurance:self.ensureOccurance]; } } @@ -305,9 +305,9 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { } } -- (void)setUseCharacterFromEachGroup:(BOOL)useCharacterFromEachGroup { - if(self.useCharacterFromEachGroup != useCharacterFromEachGroup) { - _useCharacterFromEachGroup = useCharacterFromEachGroup; +- (void)setEnsureOccurance:(BOOL)useCharacterFromEachGroup { + if(self.ensureOccurance != useCharacterFromEachGroup) { + _ensureOccurance = useCharacterFromEachGroup; [self _resetCharacters]; [self _generatePassword:nil]; } @@ -356,14 +356,14 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { self.characterFlags = [entryDefaults[kMPSettingsKeyPasswordCharacterFlags] integerValue]; self.useCustomString = [entryDefaults[kMPSettingsKeyPasswordUseCustomString] boolValue]; self.customString = entryDefaults[kMPSettingsKeyPasswordCustomString]; - self.useCharacterFromEachGroup = [entryDefaults[kMPSettingsKeyPasswordEnsureOccurance] boolValue]; + self.ensureOccurance = [entryDefaults[kMPSettingsKeyPasswordEnsureOccurance] boolValue]; } else { self.passwordLength = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyDefaultPasswordLength]; self.characterFlags = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyPasswordCharacterFlags]; self.useCustomString = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyPasswordUseCustomString]; self.customString = [NSUserDefaults.standardUserDefaults stringForKey:kMPSettingsKeyPasswordCustomString]; - self.useCharacterFromEachGroup = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyPasswordEnsureOccurance]; + self.ensureOccurance = [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyPasswordEnsureOccurance]; } } @@ -393,19 +393,8 @@ typedef NS_ENUM(NSUInteger, MPPasswordRating) { self.symbolsButton.state = (useSymbols ? NSOnState : NSOffState); // ensure minimum character lenght - if(self.useCharacterFromEachGroup) { - NSUInteger minimumPasswordLength = 0; - NSUInteger activeFlags = self.characterFlags; - while(activeFlags > 0) { - if(activeFlags & 1) { - minimumPasswordLength++; - } - activeFlags >>= 1; - } + if(self.ensureOccurance) { - if(self.passwordLength < minimumPasswordLength) { - self.passwordLength = minimumPasswordLength; - } } } diff --git a/MacPass/NSString+MPPasswordCreation.h b/MacPass/NSString+MPPasswordCreation.h index 1bde17d6..1f279426 100644 --- a/MacPass/NSString+MPPasswordCreation.h +++ b/MacPass/NSString+MPPasswordCreation.h @@ -56,6 +56,7 @@ typedef NS_OPTIONS(NSUInteger, MPPasswordCharacterFlags) { + (NSString *)passwordWithDefaultSettings; ++ (NSUInteger)minimumPasswordLengthWithCharacterSet:(MPPasswordCharacterFlags)characterSet customCharacters:(NSString *)customCharacter ensureOccurance:(BOOL)ensureOccurance; /** * @return returns a random character from the string */ @@ -84,6 +85,6 @@ typedef NS_OPTIONS(NSUInteger, MPPasswordCharacterFlags) { * * @return entropy of the receiver as bits */ -- (CGFloat)entropyWhithPossibleCharacterSet:(MPPasswordCharacterFlags)allowedCharacters andCustomCharacters:(NSString *)customCharacters; +- (CGFloat)entropyWhithCharacterSet:(MPPasswordCharacterFlags)characterSet customCharacters:(NSString *)customCharacters ensureOccurance:(BOOL)ensureOccurance; @end diff --git a/MacPass/NSString+MPPasswordCreation.m b/MacPass/NSString+MPPasswordCreation.m index 3e3bd0b4..17b96cd7 100644 --- a/MacPass/NSString+MPPasswordCreation.m +++ b/MacPass/NSString+MPPasswordCreation.m @@ -26,23 +26,32 @@ #import "NSString+MPComposedCharacterAdditions.h" #import "MPSettingsHelper.h" -NSString *const kMPLowercaseLetterCharacters = @"abcdefghijklmnopqrstuvwxyz"; -NSString *const kMPNumberCharacters = @"1234567890"; -NSString *const kMPSymbolCharacters = @"!$%&\\|/<>(){}[]=?*'+#-_.:,;"; +static NSDictionary *characterClassMap () { + static dispatch_once_t onceToken; + static NSDictionary *characterClassMap; + dispatch_once(&onceToken, ^{ + characterClassMap = @{ @(MPPasswordCharactersLowerCase) : @"abcdefghijklmnopqrstuvwxyz", + @(MPPasswordCharactersUpperCase) : @"abcdefghijklmnopqrstuvwxyz".uppercaseString, + @(MPPasswordCharactersNumbers) : @"1234567890", + @(MPPasswordCharactersSymbols) : @"!$%&\\|/<>(){}[]=?*'+#-_.:,;" + }; + }); + return characterClassMap; +} static NSString *allowedCharactersString(MPPasswordCharacterFlags flags) { NSMutableString *characterString = [NSMutableString stringWithCapacity:30]; if(flags & MPPasswordCharactersLowerCase) { - [characterString appendString:kMPLowercaseLetterCharacters]; + [characterString appendString:characterClassMap()[@(MPPasswordCharactersLowerCase)]]; } if(flags & MPPasswordCharactersUpperCase) { - [characterString appendString:kMPLowercaseLetterCharacters.uppercaseString]; + [characterString appendString:characterClassMap()[@(MPPasswordCharactersUpperCase)]]; } if(flags & MPPasswordCharactersNumbers) { - [characterString appendString:kMPNumberCharacters]; + [characterString appendString:characterClassMap()[@(MPPasswordCharactersNumbers)]]; } if(flags & MPPasswordCharactersSymbols){ - [characterString appendString:kMPSymbolCharacters]; + [characterString appendString:characterClassMap()[@(MPPasswordCharactersSymbols)]]; } return characterString; } @@ -74,10 +83,30 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust withCustomCharacters:(NSString *)customCharacters ensureOccurence:(BOOL)ensureOccurence length:(NSUInteger)length { + if(ensureOccurence) { + length = MAX(length, [NSString minimumPasswordLengthWithCharacterSet:allowedCharacters customCharacters:customCharacters ensureOccurance:ensureOccurence]); + } NSMutableString *password = [NSMutableString stringWithCapacity:length]; NSString *characters = mergeWithoutDuplicates( allowedCharactersString(allowedCharacters), customCharacters); + if(ensureOccurence) { + if(allowedCharacters & MPPasswordCharactersLowerCase) { + [password appendString:characterClassMap()[@(MPPasswordCharactersLowerCase)].randomCharacter]; + } + if(allowedCharacters & MPPasswordCharactersUpperCase) { + [password appendString:characterClassMap()[@(MPPasswordCharactersUpperCase)].randomCharacter]; + } + if(allowedCharacters & MPPasswordCharactersNumbers) { + [password appendString:characterClassMap()[@(MPPasswordCharactersNumbers)].randomCharacter]; + } + if(allowedCharacters & MPPasswordCharactersSymbols){ + [password appendString:characterClassMap()[@(MPPasswordCharactersSymbols)].randomCharacter]; + } + if(customCharacters.length > 0) { + [password appendString:customCharacters.randomCharacter]; + } + } while(password.composedCharacterLength < length) { NSString *randomCharacter = characters.randomCharacter; if(randomCharacter.length > 0) { @@ -87,7 +116,7 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust break; } } - return password; + return ensureOccurence ? password.shuffledString : password; } + (NSString *)passwordWithDefaultSettings { @@ -103,6 +132,21 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust return [NSString passwordWithCharactersets:characterFlags withCustomCharacters:@"" ensureOccurence:NO length:passwordLength]; } ++ (NSUInteger)minimumPasswordLengthWithCharacterSet:(MPPasswordCharacterFlags)characterSet customCharacters:(NSString *)customCharacter ensureOccurance:(BOOL)ensureOccurance { + NSUInteger minimumPasswordLength = 0; + NSUInteger activeFlags = characterSet; + while(activeFlags > 0) { + if(activeFlags & 1) { + minimumPasswordLength++; + } + activeFlags >>= 1; + } + if(customCharacter.length > 0) { + minimumPasswordLength++; + } + return minimumPasswordLength; +} + - (NSString *)passwordWithLength:(NSUInteger)length { return [NSString passwordFromString:self length:length]; } @@ -114,12 +158,18 @@ static NSString *mergeWithoutDuplicates(NSString* baseCharacters, NSString* cust return [self composedCharacterAtIndex:arc4random_uniform((int)self.composedCharacterLength)]; } -- (CGFloat)entropyWhithPossibleCharacterSet:(MPPasswordCharacterFlags)allowedCharacters andCustomCharacters:(NSString *)customCharacters { - NSString *characters = nil; - characters = mergeWithoutDuplicates(allowedCharactersString(allowedCharacters), customCharacters); - CGFloat alphabetCount = characters.composedCharacterLength; - CGFloat passwordLength = self.composedCharacterLength; - return passwordLength * ( log10(alphabetCount) / log10(2) ); + +- (CGFloat)entropyWhithCharacterSet:(MPPasswordCharacterFlags)characterSet customCharacters:(NSString *)customCharacters ensureOccurance:(BOOL)ensureOccurance { + if(ensureOccurance) { + return 0; + } + else { + NSString *characters = nil; + characters = mergeWithoutDuplicates(allowedCharactersString(characterSet), customCharacters); + CGFloat alphabetCount = characters.composedCharacterLength; + CGFloat passwordLength = self.composedCharacterLength; + return passwordLength * ( log10(alphabetCount) / log10(2) ); + } } - (NSString *)shuffledString {