├── .gitignore ├── Example ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DBAppDelegate.h ├── DBAppDelegate.m ├── DBCreateAccountViewController.h ├── DBCreateAccountViewController.m ├── Info.plist └── main.m ├── LICENSE.txt ├── README.md ├── Zxcvbn.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Zxcvbn.xcscheme ├── Zxcvbn ├── DBMatcher.h ├── DBMatcher.m ├── DBPasswordStrengthMeterView.h ├── DBPasswordStrengthMeterView.m ├── DBScorer.h ├── DBScorer.m ├── DBZxcvbn.h ├── DBZxcvbn.m ├── Info.plist ├── Zxcvbn.h └── generated │ ├── adjacency_graphs.lzma │ └── frequency_lists.lzma ├── ZxcvbnTests ├── Info.plist └── ZxcvbnTests.m ├── iOS Example.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── iOS Example.xcscheme ├── zxcvbn-example.png └── zxcvbn-ios.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | xcuserdata/* 19 | -------------------------------------------------------------------------------- /Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /Example/DBAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBAppDelegate.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | @interface DBAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/DBAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBAppDelegate.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBAppDelegate.h" 10 | 11 | #import 12 | 13 | @implementation DBAppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | DBZxcvbn *zxcvbn = [[DBZxcvbn alloc] init]; 18 | 19 | NSArray *testPasswords = @[ 20 | @"zxcvbn", 21 | @"qwER43@!", 22 | @"Tr0ub4dour&3", 23 | @"correcthorsebatterystaple", 24 | @"coRrecth0rseba++ery9.23.2007staple$", 25 | 26 | @"D0g..................", 27 | @"abcdefghijk987654321", 28 | @"neverforget13/3/1997", 29 | @"1qaz2wsx3edc", 30 | 31 | @"temppass22", 32 | @"briansmith", 33 | @"briansmith4mayor", 34 | @"password1", 35 | @"viking", 36 | @"thx1138", 37 | @"ScoRpi0ns", 38 | @"do you know", 39 | 40 | @"ryanhunter2000", 41 | @"rianhunter2000", 42 | 43 | @"asdfghju7654rewq", 44 | @"AOEUIDHG&*()LS_", 45 | 46 | @"12345678", 47 | @"defghi6789", 48 | 49 | @"rosebud", 50 | @"Rosebud", 51 | @"ROSEBUD", 52 | @"rosebuD", 53 | @"ros3bud99", 54 | @"r0s3bud99", 55 | @"R0$38uD99", 56 | 57 | @"verlineVANDERMARK", 58 | 59 | @"eheuczkqyq", 60 | @"rWibMFACxAUGZmxhVncy", 61 | @"Ba9ZyWABu99[BK#6MBgbH88Tofv)vs$w", 62 | ]; 63 | 64 | for (NSString *password in testPasswords) { 65 | 66 | DBResult *result = [zxcvbn passwordStrength:password]; 67 | 68 | NSLog(@"password: %@", result.password); 69 | NSLog(@"entropy: %@", result.entropy); 70 | NSLog(@"crack time (seconds): %@", result.crackTime); 71 | NSLog(@"crack time (display): %@", result.crackTimeDisplay); 72 | NSLog(@"score from 0 to 4: %d", result.score); 73 | NSLog(@"calculation time (ms): %f", result.calcTime); 74 | 75 | NSLog(@"\n"); 76 | NSLog(@"match sequence:"); 77 | 78 | for (DBMatch *match in result.matchSequence) { 79 | 80 | NSLog(@"\n"); 81 | NSLog(@"'%@'", match.token); 82 | NSLog(@"pattern: %@", match.pattern); 83 | NSLog(@"entropy: %f", match.entropy); 84 | 85 | if ([match.pattern isEqualToString:@"dictionary"]) { 86 | NSLog(@"dict-name: %@", match.dictionaryName); 87 | NSLog(@"rank: %d", match.rank); 88 | NSLog(@"base-entropy: %f", match.baseEntropy); 89 | NSLog(@"upper-entropy: %f", match.upperCaseEntropy); 90 | } 91 | 92 | if ([match.pattern isEqualToString:@"bruteforce"]) { 93 | NSLog(@"cardinality: %d", match.cardinality); 94 | } 95 | 96 | if (match.l33t) { 97 | NSLog(@"l33t-entropy: %d", match.l33tEntropy); 98 | NSLog(@"l33t subs: %@", match.subDisplay); 99 | NSLog(@"un-l33ted: %@", match.matchedWord); 100 | } 101 | 102 | if ([match.pattern isEqualToString:@"spatial"]) { 103 | NSLog(@"graph: %@", match.graph); 104 | NSLog(@"turns: %d", match.turns); 105 | NSLog(@"shifted keys: %d", match.shiftedCount); 106 | } 107 | 108 | if ([match.pattern isEqualToString:@"repeat"]) { 109 | NSLog(@"repeat-char: '%@'", match.repeatedChar); 110 | } 111 | 112 | if ([match.pattern isEqualToString:@"sequence"]) { 113 | NSLog(@"sequence-name: %@", match.sequenceName); 114 | NSLog(@"sequence-size: %d", match.sequenceSpace); 115 | if (match.ascending) { 116 | NSLog(@"ascending: true"); 117 | } else { 118 | NSLog(@"ascending: false"); 119 | } 120 | } 121 | 122 | if ([match.pattern isEqualToString:@"date"]) { 123 | NSLog(@"day: %d", match.day); 124 | NSLog(@"month: %d", match.month); 125 | NSLog(@"year: %d", match.year); 126 | NSLog(@"separator: '%@'", match.separator); 127 | } 128 | } 129 | 130 | NSLog(@"\n\n"); 131 | } 132 | 133 | return YES; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /Example/DBCreateAccountViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBCreateAccountViewController.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/22/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DBCreateAccountViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/DBCreateAccountViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBCreateAccountViewController.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/22/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBCreateAccountViewController.h" 10 | 11 | #import 12 | 13 | @interface DBCreateAccountViewController () 14 | 15 | @property (weak, nonatomic) IBOutlet UITextField *firstNameTextField; 16 | @property (weak, nonatomic) IBOutlet UITextField *lastNameTextField; 17 | @property (weak, nonatomic) IBOutlet UITextField *emailTextField; 18 | @property (weak, nonatomic) IBOutlet DBPasswordStrengthMeterView *passwordStrengthMeterView; 19 | 20 | @end 21 | 22 | @implementation DBCreateAccountViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; 27 | 28 | self.passwordStrengthMeterView.delegate = self; 29 | } 30 | 31 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 32 | { 33 | NSString *password = [textField.text stringByReplacingCharactersInRange:range withString:string]; 34 | 35 | [self.passwordStrengthMeterView scorePassword:password userInputs:@[self.firstNameTextField.text, 36 | self.lastNameTextField.text, 37 | self.emailTextField.text]]; 38 | 39 | return YES; 40 | } 41 | 42 | - (void)passwordStrengthMeterViewTapped:(DBPasswordStrengthMeterView *)passwordStrengthMeterView 43 | { 44 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil 45 | message:@"Good passwords are hard to guess. Use uncommon words or inside jokes, non-standard uPPercasing, creative spelling, and non-obvious numbers and symbols." 46 | preferredStyle:UIAlertControllerStyleAlert]; 47 | [alertController addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleDefault handler:nil]]; 48 | [self presentViewController:alertController animated:YES completion:nil]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIMainStoryboardFile~ipad 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DBAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([DBAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Dropbox, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | .................................................bbb.................... 3 | .zzzzzzzzzz..xxx....xxx....cccccccc..vvv....vvv..bbb.........nnnnnnn.... 4 | .....zzzz......xxxxxx....cccc........vvv....vvv..bbbbbbbb....nnn...nnn.. 5 | ...zzzz........xxxxxx....cccc..........vvvvvv....bbb....bb...nnn...nnn.. 6 | .zzzzzzzzzz..xxx....xxx....cccccccc......vv......bbbbbbbb....nnn...nnn.. 7 | ........................................................................ 8 | ``` 9 | 10 | An obj-c port of zxcvbn, a password strength estimation library, designed for iOS. 11 | 12 | `DBZxcvbn` attempts to give sound password advice through pattern matching 13 | and conservative entropy calculations. It finds 10k common passwords, 14 | common American names and surnames, common English words, and common 15 | patterns like dates, repeats (aaa), sequences (abcd), and QWERTY 16 | patterns. 17 | 18 | Check out the original [JavaScript](https://github.com/dropbox/zxcvbn) (well, CoffeeScript) or the [Python port](https://github.com/dropbox/python-zxcvbn). 19 | 20 | For full motivation, see [zxcvbn: realistic password strength estimation](https://blogs.dropbox.com/tech/2012/04/zxcvbn-realistic-password-strength-estimation/). 21 | 22 | # Installation 23 | 24 | Coming soon. 25 | 26 | # Use 27 | 28 | The easiest way to use `DBZxcvbn` is by displaying a `DBPasswordStrengthMeter` in your form. Set up your `UITextFieldDelegate` and add a `DBPasswordStrengthMeter`. 29 | 30 | See the example here: [DBCreateAccountViewController.m](https://github.com/dropbox/zxcvbn-ios/blob/master/Example/DBCreateAccountViewController.m) 31 | 32 | As the user types, you can call `scorePassword:` like so: 33 | ``` objc 34 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 35 | { 36 | NSString *password = [textField.text stringByReplacingCharactersInRange:range withString:string]; 37 | 38 | [self.passwordStrengthMeterView scorePassword:password]; 39 | 40 | return YES; 41 | } 42 | ``` 43 | 44 | Here is what `DBPasswordStrengthMeter` looks like in a form: 45 | 46 |

47 | 48 |

49 | 50 | To use `DBZxcvbn` without the `DBPasswordStrengthMeter` view simply import `DBZxcvbn.h`, create a new instance of `DBZxcvbn`, then call `passwordStrength:userInputs:`. 51 | 52 | ``` objc 53 | #import 54 | 55 | DBZxcvbn *zxcvbn = [[DBZxcvbn alloc] init]; 56 | DBResult *result = [zxcvbn passwordStrength:password userInputs:userInputs]; 57 | ``` 58 | 59 | The DBResult includes a few properties: 60 | 61 | ``` objc 62 | result.entropy // bits 63 | 64 | result.crackTime // estimation of actual crack time, in seconds. 65 | 66 | result.crackTimeDisplay // same crack time, as a friendlier string: 67 | // "instant", "6 minutes", "centuries", etc. 68 | 69 | result.score // [0,1,2,3,4] if crack time is less than 70 | // [10**2, 10**4, 10**6, 10**8, Infinity]. 71 | // (useful for implementing a strength bar.) 72 | 73 | result.matchSequence // the list of patterns that zxcvbn based the 74 | // entropy calculation on. 75 | 76 | result.calcTime // how long it took to calculate an answer, 77 | // in milliseconds. usually only a few ms. 78 | ```` 79 | 80 | The optional `userInputs` argument is an array of strings that `DBZxcvbn` 81 | will add to its internal dictionary. This can be whatever list of 82 | strings you like, but is meant for user inputs from other fields of the 83 | form, like name and email. That way a password that includes the user's 84 | personal info can be heavily penalized. This list is also good for 85 | site-specific vocabulary. 86 | 87 | # Acknowledgments 88 | 89 | Thanks to Dropbox for supporting independent projects and open source software. 90 | 91 | A huge thanks to [Dan Wheeler](https://github.com/lowe) for the original [CoffeeScript implementation](https://github.com/dropbox/zxcvbn). Thanks to [Ryan Pearl](https://github.com/dropbox/python-zxcvbn) for his [Python port](). I've enjoyed copying your code :) 92 | 93 | Echoing the acknowledgments from earlier libraries... 94 | 95 | Many thanks to Mark Burnett for releasing his 10k top passwords list: 96 | 97 | http://xato.net/passwords/more-top-worst-passwords 98 | 99 | and for his 2006 book, 100 | "Perfect Passwords: Selection, Protection, Authentication" 101 | 102 | Huge thanks to Wiktionary contributors for building a frequency list 103 | of English as used in television and movies: 104 | http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists 105 | 106 | Last but not least, big thanks to xkcd :) 107 | https://xkcd.com/936/ 108 | 109 | -------------------------------------------------------------------------------- /Zxcvbn.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 72CD21882A672FDD00508DC2 /* adjacency_graphs.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 72CD21862A672FDD00508DC2 /* adjacency_graphs.lzma */; }; 11 | 72CD21892A672FDD00508DC2 /* frequency_lists.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 72CD21872A672FDD00508DC2 /* frequency_lists.lzma */; }; 12 | 72CD218A2A67311600508DC2 /* frequency_lists.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 72CD21872A672FDD00508DC2 /* frequency_lists.lzma */; }; 13 | 72CD218B2A67312500508DC2 /* adjacency_graphs.lzma in Resources */ = {isa = PBXBuildFile; fileRef = 72CD21862A672FDD00508DC2 /* adjacency_graphs.lzma */; }; 14 | D80ECB6C1BDEAA420055EF0A /* Zxcvbn.h in Headers */ = {isa = PBXBuildFile; fileRef = D80ECB6B1BDEAA420055EF0A /* Zxcvbn.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | D80ECB731BDEAA420055EF0A /* Zxcvbn.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D80ECB681BDEAA420055EF0A /* Zxcvbn.framework */; }; 16 | D80ECB781BDEAA420055EF0A /* ZxcvbnTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB771BDEAA420055EF0A /* ZxcvbnTests.m */; }; 17 | D80ECB8B1BDEAA7D0055EF0A /* DBMatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = D80ECB821BDEAA7D0055EF0A /* DBMatcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | D80ECB8C1BDEAA7D0055EF0A /* DBMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB831BDEAA7D0055EF0A /* DBMatcher.m */; }; 19 | D80ECB8D1BDEAA7D0055EF0A /* DBMatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB831BDEAA7D0055EF0A /* DBMatcher.m */; }; 20 | D80ECB8E1BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.h in Headers */ = {isa = PBXBuildFile; fileRef = D80ECB841BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | D80ECB8F1BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB851BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m */; }; 22 | D80ECB901BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB851BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m */; }; 23 | D80ECB911BDEAA7D0055EF0A /* DBScorer.h in Headers */ = {isa = PBXBuildFile; fileRef = D80ECB861BDEAA7D0055EF0A /* DBScorer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | D80ECB921BDEAA7D0055EF0A /* DBScorer.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB871BDEAA7D0055EF0A /* DBScorer.m */; }; 25 | D80ECB931BDEAA7D0055EF0A /* DBScorer.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB871BDEAA7D0055EF0A /* DBScorer.m */; }; 26 | D80ECB941BDEAA7D0055EF0A /* DBZxcvbn.h in Headers */ = {isa = PBXBuildFile; fileRef = D80ECB881BDEAA7D0055EF0A /* DBZxcvbn.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27 | D80ECB951BDEAA7D0055EF0A /* DBZxcvbn.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB891BDEAA7D0055EF0A /* DBZxcvbn.m */; }; 28 | D80ECB961BDEAA7D0055EF0A /* DBZxcvbn.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECB891BDEAA7D0055EF0A /* DBZxcvbn.m */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | D80ECB741BDEAA420055EF0A /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = D80ECB5F1BDEAA420055EF0A /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = D80ECB671BDEAA420055EF0A; 37 | remoteInfo = Zxcvbn; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 72CD21862A672FDD00508DC2 /* adjacency_graphs.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; name = adjacency_graphs.lzma; path = generated/adjacency_graphs.lzma; sourceTree = ""; }; 43 | 72CD21872A672FDD00508DC2 /* frequency_lists.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; name = frequency_lists.lzma; path = generated/frequency_lists.lzma; sourceTree = ""; }; 44 | D80ECB681BDEAA420055EF0A /* Zxcvbn.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Zxcvbn.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | D80ECB6B1BDEAA420055EF0A /* Zxcvbn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Zxcvbn.h; sourceTree = ""; }; 46 | D80ECB6D1BDEAA420055EF0A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | D80ECB721BDEAA420055EF0A /* ZxcvbnTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ZxcvbnTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | D80ECB771BDEAA420055EF0A /* ZxcvbnTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZxcvbnTests.m; sourceTree = ""; }; 49 | D80ECB791BDEAA420055EF0A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | D80ECB821BDEAA7D0055EF0A /* DBMatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBMatcher.h; sourceTree = ""; }; 51 | D80ECB831BDEAA7D0055EF0A /* DBMatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBMatcher.m; sourceTree = ""; }; 52 | D80ECB841BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPasswordStrengthMeterView.h; sourceTree = ""; }; 53 | D80ECB851BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPasswordStrengthMeterView.m; sourceTree = ""; }; 54 | D80ECB861BDEAA7D0055EF0A /* DBScorer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBScorer.h; sourceTree = ""; }; 55 | D80ECB871BDEAA7D0055EF0A /* DBScorer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBScorer.m; sourceTree = ""; }; 56 | D80ECB881BDEAA7D0055EF0A /* DBZxcvbn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBZxcvbn.h; sourceTree = ""; }; 57 | D80ECB891BDEAA7D0055EF0A /* DBZxcvbn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBZxcvbn.m; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | D80ECB641BDEAA420055EF0A /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | D80ECB6F1BDEAA420055EF0A /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | D80ECB731BDEAA420055EF0A /* Zxcvbn.framework in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | D80ECB5E1BDEAA420055EF0A = { 80 | isa = PBXGroup; 81 | children = ( 82 | D80ECB6A1BDEAA420055EF0A /* Zxcvbn */, 83 | D80ECB761BDEAA420055EF0A /* ZxcvbnTests */, 84 | D80ECB691BDEAA420055EF0A /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | D80ECB691BDEAA420055EF0A /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | D80ECB681BDEAA420055EF0A /* Zxcvbn.framework */, 92 | D80ECB721BDEAA420055EF0A /* ZxcvbnTests.xctest */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | D80ECB6A1BDEAA420055EF0A /* Zxcvbn */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | D80ECB6B1BDEAA420055EF0A /* Zxcvbn.h */, 101 | D80ECB821BDEAA7D0055EF0A /* DBMatcher.h */, 102 | D80ECB831BDEAA7D0055EF0A /* DBMatcher.m */, 103 | D80ECB861BDEAA7D0055EF0A /* DBScorer.h */, 104 | D80ECB871BDEAA7D0055EF0A /* DBScorer.m */, 105 | D80ECB881BDEAA7D0055EF0A /* DBZxcvbn.h */, 106 | D80ECB891BDEAA7D0055EF0A /* DBZxcvbn.m */, 107 | D80ECB841BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.h */, 108 | D80ECB851BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m */, 109 | D80ECB991BDEAAA10055EF0A /* Generated */, 110 | D80ECB6D1BDEAA420055EF0A /* Info.plist */, 111 | ); 112 | path = Zxcvbn; 113 | sourceTree = ""; 114 | }; 115 | D80ECB761BDEAA420055EF0A /* ZxcvbnTests */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | D80ECB771BDEAA420055EF0A /* ZxcvbnTests.m */, 119 | D80ECB791BDEAA420055EF0A /* Info.plist */, 120 | ); 121 | path = ZxcvbnTests; 122 | sourceTree = ""; 123 | }; 124 | D80ECB991BDEAAA10055EF0A /* Generated */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 72CD21862A672FDD00508DC2 /* adjacency_graphs.lzma */, 128 | 72CD21872A672FDD00508DC2 /* frequency_lists.lzma */, 129 | ); 130 | name = Generated; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXHeadersBuildPhase section */ 136 | D80ECB651BDEAA420055EF0A /* Headers */ = { 137 | isa = PBXHeadersBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | D80ECB6C1BDEAA420055EF0A /* Zxcvbn.h in Headers */, 141 | D80ECB8B1BDEAA7D0055EF0A /* DBMatcher.h in Headers */, 142 | D80ECB911BDEAA7D0055EF0A /* DBScorer.h in Headers */, 143 | D80ECB941BDEAA7D0055EF0A /* DBZxcvbn.h in Headers */, 144 | D80ECB8E1BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.h in Headers */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXHeadersBuildPhase section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | D80ECB671BDEAA420055EF0A /* Zxcvbn */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = D80ECB7C1BDEAA420055EF0A /* Build configuration list for PBXNativeTarget "Zxcvbn" */; 154 | buildPhases = ( 155 | D80ECB631BDEAA420055EF0A /* Sources */, 156 | D80ECB641BDEAA420055EF0A /* Frameworks */, 157 | D80ECB651BDEAA420055EF0A /* Headers */, 158 | D80ECB661BDEAA420055EF0A /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = Zxcvbn; 165 | productName = Zxcvbn; 166 | productReference = D80ECB681BDEAA420055EF0A /* Zxcvbn.framework */; 167 | productType = "com.apple.product-type.framework"; 168 | }; 169 | D80ECB711BDEAA420055EF0A /* ZxcvbnTests */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = D80ECB7F1BDEAA420055EF0A /* Build configuration list for PBXNativeTarget "ZxcvbnTests" */; 172 | buildPhases = ( 173 | D80ECB6E1BDEAA420055EF0A /* Sources */, 174 | D80ECB6F1BDEAA420055EF0A /* Frameworks */, 175 | D80ECB701BDEAA420055EF0A /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | D80ECB751BDEAA420055EF0A /* PBXTargetDependency */, 181 | ); 182 | name = ZxcvbnTests; 183 | productName = ZxcvbnTests; 184 | productReference = D80ECB721BDEAA420055EF0A /* ZxcvbnTests.xctest */; 185 | productType = "com.apple.product-type.bundle.unit-test"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | D80ECB5F1BDEAA420055EF0A /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastUpgradeCheck = 0710; 194 | ORGANIZATIONNAME = Dropbox; 195 | TargetAttributes = { 196 | D80ECB671BDEAA420055EF0A = { 197 | CreatedOnToolsVersion = 7.1; 198 | }; 199 | D80ECB711BDEAA420055EF0A = { 200 | CreatedOnToolsVersion = 7.1; 201 | }; 202 | }; 203 | }; 204 | buildConfigurationList = D80ECB621BDEAA420055EF0A /* Build configuration list for PBXProject "Zxcvbn" */; 205 | compatibilityVersion = "Xcode 3.2"; 206 | developmentRegion = English; 207 | hasScannedForEncodings = 0; 208 | knownRegions = ( 209 | English, 210 | en, 211 | ); 212 | mainGroup = D80ECB5E1BDEAA420055EF0A; 213 | productRefGroup = D80ECB691BDEAA420055EF0A /* Products */; 214 | projectDirPath = ""; 215 | projectRoot = ""; 216 | targets = ( 217 | D80ECB671BDEAA420055EF0A /* Zxcvbn */, 218 | D80ECB711BDEAA420055EF0A /* ZxcvbnTests */, 219 | ); 220 | }; 221 | /* End PBXProject section */ 222 | 223 | /* Begin PBXResourcesBuildPhase section */ 224 | D80ECB661BDEAA420055EF0A /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 72CD21882A672FDD00508DC2 /* adjacency_graphs.lzma in Resources */, 229 | 72CD21892A672FDD00508DC2 /* frequency_lists.lzma in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | D80ECB701BDEAA420055EF0A /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 72CD218A2A67311600508DC2 /* frequency_lists.lzma in Resources */, 238 | 72CD218B2A67312500508DC2 /* adjacency_graphs.lzma in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | D80ECB631BDEAA420055EF0A /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | D80ECB921BDEAA7D0055EF0A /* DBScorer.m in Sources */, 250 | D80ECB8F1BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m in Sources */, 251 | D80ECB951BDEAA7D0055EF0A /* DBZxcvbn.m in Sources */, 252 | D80ECB8C1BDEAA7D0055EF0A /* DBMatcher.m in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | D80ECB6E1BDEAA420055EF0A /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | D80ECB961BDEAA7D0055EF0A /* DBZxcvbn.m in Sources */, 261 | D80ECB8D1BDEAA7D0055EF0A /* DBMatcher.m in Sources */, 262 | D80ECB931BDEAA7D0055EF0A /* DBScorer.m in Sources */, 263 | D80ECB781BDEAA420055EF0A /* ZxcvbnTests.m in Sources */, 264 | D80ECB901BDEAA7D0055EF0A /* DBPasswordStrengthMeterView.m in Sources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXSourcesBuildPhase section */ 269 | 270 | /* Begin PBXTargetDependency section */ 271 | D80ECB751BDEAA420055EF0A /* PBXTargetDependency */ = { 272 | isa = PBXTargetDependency; 273 | target = D80ECB671BDEAA420055EF0A /* Zxcvbn */; 274 | targetProxy = D80ECB741BDEAA420055EF0A /* PBXContainerItemProxy */; 275 | }; 276 | /* End PBXTargetDependency section */ 277 | 278 | /* Begin XCBuildConfiguration section */ 279 | D80ECB7A1BDEAA420055EF0A /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ALWAYS_SEARCH_USER_PATHS = NO; 283 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 284 | CLANG_CXX_LIBRARY = "libc++"; 285 | CLANG_ENABLE_MODULES = YES; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | CURRENT_PROJECT_VERSION = 1; 299 | DEBUG_INFORMATION_FORMAT = dwarf; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_TESTABILITY = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_OPTIMIZATION_LEVEL = 0; 306 | GCC_PREPROCESSOR_DEFINITIONS = ( 307 | "DEBUG=1", 308 | "$(inherited)", 309 | ); 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 317 | MTL_ENABLE_DEBUG_INFO = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = iphoneos; 320 | TARGETED_DEVICE_FAMILY = "1,2"; 321 | VERSIONING_SYSTEM = "apple-generic"; 322 | VERSION_INFO_PREFIX = ""; 323 | }; 324 | name = Debug; 325 | }; 326 | D80ECB7B1BDEAA420055EF0A /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | CURRENT_PROJECT_VERSION = 1; 346 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 347 | ENABLE_NS_ASSERTIONS = NO; 348 | ENABLE_STRICT_OBJC_MSGSEND = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu99; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 358 | MTL_ENABLE_DEBUG_INFO = NO; 359 | SDKROOT = iphoneos; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | VALIDATE_PRODUCT = YES; 362 | VERSIONING_SYSTEM = "apple-generic"; 363 | VERSION_INFO_PREFIX = ""; 364 | }; 365 | name = Release; 366 | }; 367 | D80ECB7D1BDEAA420055EF0A /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | APPLICATION_EXTENSION_API_ONLY = YES; 371 | DEFINES_MODULE = YES; 372 | DYLIB_COMPATIBILITY_VERSION = 1; 373 | DYLIB_CURRENT_VERSION = 1; 374 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 375 | INFOPLIST_FILE = Zxcvbn/Info.plist; 376 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 378 | PRODUCT_BUNDLE_IDENTIFIER = com.dropbox.Zxcvbn; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SKIP_INSTALL = YES; 381 | }; 382 | name = Debug; 383 | }; 384 | D80ECB7E1BDEAA420055EF0A /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | APPLICATION_EXTENSION_API_ONLY = YES; 388 | DEFINES_MODULE = YES; 389 | DYLIB_COMPATIBILITY_VERSION = 1; 390 | DYLIB_CURRENT_VERSION = 1; 391 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 392 | INFOPLIST_FILE = Zxcvbn/Info.plist; 393 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 394 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 395 | PRODUCT_BUNDLE_IDENTIFIER = com.dropbox.Zxcvbn; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | SKIP_INSTALL = YES; 398 | }; 399 | name = Release; 400 | }; 401 | D80ECB801BDEAA420055EF0A /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | INFOPLIST_FILE = ZxcvbnTests/Info.plist; 405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 406 | PRODUCT_BUNDLE_IDENTIFIER = com.dropbox.Zxcvbn.ZxcvbnTests; 407 | PRODUCT_NAME = "$(TARGET_NAME)"; 408 | }; 409 | name = Debug; 410 | }; 411 | D80ECB811BDEAA420055EF0A /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | INFOPLIST_FILE = ZxcvbnTests/Info.plist; 415 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 416 | PRODUCT_BUNDLE_IDENTIFIER = com.dropbox.Zxcvbn.ZxcvbnTests; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | D80ECB621BDEAA420055EF0A /* Build configuration list for PBXProject "Zxcvbn" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | D80ECB7A1BDEAA420055EF0A /* Debug */, 428 | D80ECB7B1BDEAA420055EF0A /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | D80ECB7C1BDEAA420055EF0A /* Build configuration list for PBXNativeTarget "Zxcvbn" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | D80ECB7D1BDEAA420055EF0A /* Debug */, 437 | D80ECB7E1BDEAA420055EF0A /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | D80ECB7F1BDEAA420055EF0A /* Build configuration list for PBXNativeTarget "ZxcvbnTests" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | D80ECB801BDEAA420055EF0A /* Debug */, 446 | D80ECB811BDEAA420055EF0A /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | /* End XCConfigurationList section */ 452 | }; 453 | rootObject = D80ECB5F1BDEAA420055EF0A /* Project object */; 454 | } 455 | -------------------------------------------------------------------------------- /Zxcvbn.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Zxcvbn.xcodeproj/xcshareddata/xcschemes/Zxcvbn.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Zxcvbn/DBMatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBMatcher.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DBMatcher : NSObject 12 | 13 | @property (nonatomic, assign) NSUInteger keyboardAverageDegree; 14 | @property (nonatomic, assign) NSUInteger keypadAverageDegree; 15 | @property (nonatomic, assign) NSUInteger keyboardStartingPositions; 16 | @property (nonatomic, assign) NSUInteger keypadStartingPositions; 17 | 18 | - (NSArray *)omnimatch:(NSString *)password userInputs:(NSArray *)userInputs; 19 | 20 | @end 21 | 22 | @interface DBMatchResources : NSObject 23 | 24 | @property (nonatomic, strong) NSArray *dictionaryMatchers; 25 | @property (nonatomic, strong) NSDictionary *graphs; 26 | 27 | + (DBMatchResources *)sharedDBMatcherResources; 28 | 29 | @end 30 | 31 | @interface DBMatch : NSObject 32 | 33 | @property (nonatomic, assign) NSString *pattern; 34 | @property (strong, nonatomic) NSString *token; 35 | @property (nonatomic, assign) NSUInteger i; 36 | @property (nonatomic, assign) NSUInteger j; 37 | @property (nonatomic, assign) float entropy; 38 | @property (nonatomic, assign) int cardinality; 39 | 40 | // Dictionary 41 | @property (strong, nonatomic) NSString *matchedWord; 42 | @property (strong, nonatomic) NSString *dictionaryName; 43 | @property (nonatomic, assign) int rank; 44 | @property (nonatomic, assign) float baseEntropy; 45 | @property (nonatomic, assign) float upperCaseEntropy; 46 | 47 | // l33t 48 | @property (nonatomic, assign) BOOL l33t; 49 | @property (strong, nonatomic) NSDictionary *sub; 50 | @property (strong, nonatomic) NSString *subDisplay; 51 | @property (nonatomic, assign) int l33tEntropy; 52 | 53 | // Spatial 54 | @property (strong, nonatomic) NSString *graph; 55 | @property (nonatomic, assign) int turns; 56 | @property (nonatomic, assign) int shiftedCount; 57 | 58 | // Repeat 59 | @property (strong, nonatomic) NSString *repeatedChar; 60 | 61 | // Sequence 62 | @property (strong, nonatomic) NSString *sequenceName; 63 | @property (nonatomic, assign) int sequenceSpace; 64 | @property (nonatomic, assign) BOOL ascending; 65 | 66 | // Date 67 | @property (nonatomic, assign) int day; 68 | @property (nonatomic, assign) int month; 69 | @property (nonatomic, assign) int year; 70 | @property (strong, nonatomic) NSString *separator; 71 | 72 | @end -------------------------------------------------------------------------------- /Zxcvbn/DBMatcher.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBMatcher.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBMatcher.h" 10 | 11 | typedef NSArray* (^MatcherBlock)(NSString *password); 12 | 13 | @interface DBMatcher () 14 | 15 | @property (nonatomic, strong) NSArray *dictionaryMatchers; 16 | @property (nonatomic, strong) NSDictionary *graphs; 17 | @property (nonatomic, strong) NSMutableArray *matchers; 18 | 19 | @end 20 | 21 | @implementation DBMatcher 22 | 23 | - (id)init 24 | { 25 | self = [super init]; 26 | 27 | if (self != nil) { 28 | DBMatchResources *resource = [DBMatchResources sharedDBMatcherResources]; 29 | self.dictionaryMatchers = resource.dictionaryMatchers; 30 | self.graphs = resource.graphs; 31 | 32 | self.keyboardAverageDegree = [self calcAverageDegree:[self.graphs objectForKey:@"qwerty"]]; 33 | self.keypadAverageDegree = [self calcAverageDegree:[self.graphs objectForKey:@"keypad"]]; // slightly different for keypad/mac keypad, but close enough 34 | 35 | self.keyboardStartingPositions = [[self.graphs objectForKey:@"qwerty"] count]; 36 | self.keypadStartingPositions = [[self.graphs objectForKey:@"keypad"] count]; 37 | 38 | self.matchers = [[NSMutableArray alloc] initWithArray:self.dictionaryMatchers]; 39 | [self.matchers addObjectsFromArray:@[[self l33tMatch], 40 | [self digitsMatch], [self yearMatch], [self dateMatch], 41 | [self repeatMatch], [self sequenceMatch], 42 | [self spatialMatch]]]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | #pragma mark - omnimatch -- combine everything 49 | 50 | - (NSArray *)omnimatch:(NSString *)password userInputs:(NSArray *)userInputs 51 | { 52 | if ([userInputs count]) { 53 | NSMutableDictionary *rankedUserInputsDict = [[NSMutableDictionary alloc] initWithCapacity:[userInputs count]]; 54 | for (int i = 0; i < [userInputs count]; i++) { 55 | [rankedUserInputsDict setObject:[NSNumber numberWithInt:i + 1] forKey:[userInputs[i] lowercaseString]]; 56 | } 57 | [self.matchers addObject:[self buildDictMatcher:@"user_inputs" rankedDict:rankedUserInputsDict]]; 58 | } 59 | 60 | NSMutableArray *matches = [[NSMutableArray alloc] init]; 61 | 62 | for (MatcherBlock matcher in self.matchers) { 63 | [matches addObjectsFromArray:matcher(password)]; 64 | } 65 | 66 | return [matches sortedArrayUsingDescriptors: @[[[NSSortDescriptor alloc] initWithKey:@"i" ascending:YES], 67 | [[NSSortDescriptor alloc] initWithKey:@"j" ascending:NO]]]; 68 | } 69 | 70 | #pragma mark - dictionary match (common passwords, english, last names, etc) 71 | 72 | - (NSMutableArray *)dictionaryMatch:(NSString *)password rankedDict:(NSMutableDictionary *)rankedDict 73 | { 74 | NSMutableArray *result = [[NSMutableArray alloc] init]; 75 | NSUInteger length = [password length]; 76 | NSString *passwordLower = [password lowercaseString]; 77 | 78 | for (int i = 0; i < length; i++) { 79 | for (int j = i; j < length; j++) { 80 | NSString *word = [passwordLower substringWithRange:NSMakeRange(i, j - i + 1)]; 81 | NSNumber *rank = [rankedDict objectForKey:word]; 82 | 83 | if (rank != nil) { 84 | DBMatch *match = [[DBMatch alloc] init]; 85 | match.pattern = @"dictionary"; 86 | match.i = i; 87 | match.j = j; 88 | match.token = [password substringWithRange:NSMakeRange(i, j - i + 1)]; 89 | match.matchedWord = word; 90 | match.rank = [rank intValue]; 91 | [result addObject:match]; 92 | } 93 | } 94 | } 95 | 96 | return result; 97 | } 98 | 99 | - (MatcherBlock)buildDictMatcher:(NSString *)dictName rankedDict:(NSMutableDictionary *)rankedDict 100 | { 101 | __weak typeof(self) weakSelf = self; 102 | MatcherBlock block = ^ NSArray* (NSString *password) { 103 | 104 | NSMutableArray *matches = [weakSelf dictionaryMatch:password rankedDict:rankedDict]; 105 | 106 | for (DBMatch *match in matches) { 107 | match.dictionaryName = dictName; 108 | } 109 | 110 | return matches; 111 | }; 112 | 113 | return block; 114 | } 115 | 116 | - (float)calcAverageDegree:(NSDictionary *)graph 117 | { 118 | // on qwerty, 'g' has degree 6, being adjacent to 'ftyhbv'. '\' has degree 1. 119 | // this calculates the average over all keys. 120 | float average = 0.0; 121 | for (NSString *key in [graph allKeys]) { 122 | NSMutableArray *neighbors = [[NSMutableArray alloc] init]; 123 | for (NSString *n in (NSArray *)[graph objectForKey:key]) { 124 | if (n != (id)[NSNull null]) { 125 | [neighbors addObject:n]; 126 | } 127 | } 128 | average += [neighbors count]; 129 | } 130 | average /= [graph count]; 131 | return average; 132 | } 133 | 134 | #pragma mark - dictionary match with common l33t substitutions 135 | 136 | - (NSDictionary *)l33tTable 137 | { 138 | return @{ 139 | @"a": @[@"4", @"@"], 140 | @"b": @[@"8"], 141 | @"c": @[@"(", @"{", @"[", @"<"], 142 | @"e": @[@"3"], 143 | @"g": @[@"6", @"9"], 144 | @"i": @[@"1", @"!", @"|"], 145 | @"l": @[@"1", @"|", @"7"], 146 | @"o": @[@"0"], 147 | @"s": @[@"$", @"5"], 148 | @"t": @[@"+", @"7"], 149 | @"x": @[@"%"], 150 | @"z": @[@"2"], 151 | }; 152 | } 153 | 154 | - (NSDictionary *)relevantL33tSubtable:(NSString *)password 155 | { 156 | // makes a pruned copy of l33t_table that only includes password's possible substitutions 157 | NSMutableDictionary *filtered = [[NSMutableDictionary alloc] init]; 158 | 159 | for (NSString *letter in [self l33tTable]) { 160 | NSArray *subs = [[self l33tTable] objectForKey:letter]; 161 | NSMutableArray *relevantSubs = [[NSMutableArray alloc] initWithCapacity:[subs count]]; 162 | for (NSString *sub in subs) { 163 | if ([password rangeOfString:sub].location != NSNotFound) { 164 | [relevantSubs addObject:sub]; 165 | } 166 | } 167 | if ([relevantSubs count] > 0) { 168 | [filtered setObject:relevantSubs forKey:letter]; 169 | } 170 | } 171 | 172 | return filtered; 173 | } 174 | 175 | - (NSArray *)enumerateL33tSubs:(NSDictionary *)table 176 | { 177 | // returns the list of possible 1337 replacement dictionaries for a given password 178 | NSMutableArray *subs = [[NSMutableArray alloc] initWithObjects:[[NSMutableArray alloc] init], nil]; 179 | 180 | NSMutableArray* (^dedup)(NSArray *) = ^ NSMutableArray* (NSArray *subs) { 181 | NSMutableArray *deduped = [[NSMutableArray alloc] init]; 182 | NSMutableArray *members = [[NSMutableArray alloc] init]; 183 | for (NSArray *sub in subs) { 184 | NSArray *assoc = [sub sortedArrayUsingComparator:^NSComparisonResult(NSArray *kv1, NSArray *kv2) { 185 | return [kv1[0] caseInsensitiveCompare:kv2[0]]; 186 | }]; 187 | NSMutableArray *kvs = [[NSMutableArray alloc] initWithCapacity:[assoc count]]; 188 | for (NSArray *kv in assoc) { 189 | [kvs addObject:[kv componentsJoinedByString:@","]]; 190 | } 191 | NSString *label = [kvs componentsJoinedByString:@"-"]; 192 | if (![members containsObject:label]) { 193 | [members addObject:label]; 194 | [deduped addObject:sub]; 195 | } 196 | } 197 | return deduped; 198 | }; 199 | 200 | NSArray *keys = [table allKeys]; 201 | 202 | while ([keys count] > 0) { 203 | NSString *firstKey = [keys objectAtIndex:0]; 204 | NSArray *restKeys = [keys count] > 1 ? [keys subarrayWithRange:NSMakeRange(1, [keys count] - 1)] : @[]; 205 | NSMutableArray *nextSubs = [[NSMutableArray alloc] init]; 206 | 207 | for (NSString *l33tChr in (NSArray *)[table objectForKey:firstKey]) { 208 | for (NSMutableArray *sub in subs) { 209 | 210 | int dupL33tIndex = -1; 211 | for (int i = 0; i < [sub count]; i++) { 212 | if ([[[sub objectAtIndex:i] objectAtIndex:0] isEqualToString:l33tChr]) { 213 | dupL33tIndex = i; 214 | break; 215 | } 216 | } 217 | 218 | if (dupL33tIndex == -1) { 219 | NSMutableArray *subExtension = [[NSMutableArray alloc] initWithArray:sub]; 220 | [subExtension addObject:@[l33tChr, firstKey]]; 221 | [nextSubs addObject:subExtension]; 222 | } else { 223 | NSMutableArray *subAlternative = [[NSMutableArray alloc] initWithArray:sub]; 224 | [subAlternative removeObjectAtIndex:dupL33tIndex]; 225 | [subAlternative addObject:@[l33tChr, firstKey]]; 226 | [nextSubs addObject:sub]; 227 | [nextSubs addObject:subAlternative]; 228 | } 229 | } 230 | } 231 | 232 | subs = dedup(nextSubs); 233 | keys = restKeys; 234 | } 235 | 236 | NSMutableArray *subDicts = [[NSMutableArray alloc] init]; // convert from assoc lists to dicts 237 | for (NSMutableArray *sub in subs) { 238 | NSMutableDictionary *subDict = [[NSMutableDictionary alloc] initWithCapacity:[sub count]]; 239 | for (NSArray *pair in sub) { 240 | [subDict setObject:[pair objectAtIndex:1] forKey:[pair objectAtIndex:0]]; 241 | } 242 | [subDicts addObject:subDict]; 243 | } 244 | return subDicts; 245 | } 246 | 247 | - (MatcherBlock)l33tMatch 248 | { 249 | __weak typeof(self) weakSelf = self; 250 | MatcherBlock block = ^ NSArray* (NSString *password) { 251 | 252 | NSMutableArray *matches = [[NSMutableArray alloc] init]; 253 | 254 | for (NSDictionary *sub in [weakSelf enumerateL33tSubs:[weakSelf relevantL33tSubtable:password]]) { 255 | if ([sub count] == 0) { break; } // corner case: password has no relevent subs. 256 | 257 | NSString *subbedPassword = [weakSelf translate:password characterMap:sub]; 258 | 259 | for (MatcherBlock matcher in weakSelf.dictionaryMatchers) { 260 | for (DBMatch *match in matcher(subbedPassword)) { 261 | 262 | NSString *token = [password substringWithRange:NSMakeRange(match.i, match.j - match.i + 1)]; 263 | if ([[token lowercaseString] isEqualToString:match.matchedWord]) { 264 | continue; // only return the matches that contain an actual substitution 265 | } 266 | 267 | NSMutableDictionary *matchSub = [[NSMutableDictionary alloc] init]; // subset of mappings in sub that are in use for this match 268 | NSMutableArray *subDisplay = [[NSMutableArray alloc] init]; 269 | for (NSString *subbedChr in sub) { 270 | NSString *chr = [sub objectForKey:subbedChr]; 271 | if ([token rangeOfString:subbedChr].location != NSNotFound) { 272 | [matchSub setObject:chr forKey:subbedChr]; 273 | [subDisplay addObject:[NSString stringWithFormat:@"%@ -> %@", subbedChr, chr]]; 274 | } 275 | } 276 | 277 | match.l33t = YES; 278 | match.token = token; 279 | match.sub = matchSub; 280 | match.subDisplay = [subDisplay componentsJoinedByString:@","]; 281 | [matches addObject:match]; 282 | } 283 | } 284 | } 285 | 286 | return matches; 287 | }; 288 | 289 | return block; 290 | } 291 | 292 | #pragma mark - spatial match (qwerty/dvorak/keypad) 293 | 294 | - (MatcherBlock)spatialMatch 295 | { 296 | __weak typeof(self) weakSelf = self; 297 | MatcherBlock block = ^ NSArray* (NSString *password) { 298 | NSMutableArray *matches = [[NSMutableArray alloc] init]; 299 | 300 | for (NSString *graphName in weakSelf.graphs) { 301 | NSDictionary *graph = [weakSelf.graphs objectForKey:graphName]; 302 | [matches addObjectsFromArray:[weakSelf spatialMatchHelper:password graph:graph graphName:graphName]]; 303 | } 304 | 305 | return matches; 306 | }; 307 | 308 | return block; 309 | } 310 | 311 | - (NSArray *)spatialMatchHelper:(NSString *)password graph:(NSDictionary *)graph graphName:(NSString *)graphName 312 | { 313 | NSMutableArray *result = [[NSMutableArray alloc] init]; 314 | 315 | int i = 0; 316 | while (i < [password length] - 1 && [password length] > 0) { 317 | int j = i + 1; 318 | int lastDirection = -1; 319 | int turns = 0; 320 | int shiftedCount = 0; 321 | while (YES) { 322 | NSString *prevChar = [password substringWithRange:NSMakeRange(j - 1, 1)]; 323 | BOOL found = NO; 324 | int foundDirection = -1; 325 | int curDirection = -1; 326 | NSArray *adjacents = [[graph allKeys] containsObject:prevChar] ? [graph objectForKey:prevChar] : @[]; 327 | // consider growing pattern by one character if j hasn't gone over the edge. 328 | if (j < [password length]) { 329 | NSString *curChar = [password substringWithRange:NSMakeRange(j, 1)]; 330 | for (NSString *adj in adjacents) { 331 | curDirection++; 332 | if (adj != (id)[NSNull null] && [adj rangeOfString:curChar].location != NSNotFound) { 333 | found = YES; 334 | foundDirection = curDirection; 335 | if ([adj rangeOfString:curChar].location == 1) { 336 | // index 1 in the adjacency means the key is shifted, 0 means unshifted: A vs a, % vs 5, etc. 337 | // for example, 'q' is adjacent to the entry '2@'. @ is shifted w/ index 1, 2 is unshifted. 338 | shiftedCount++; 339 | } 340 | if (lastDirection != foundDirection) { 341 | // adding a turn is correct even in the initial case when last_direction is null: 342 | // every spatial pattern starts with a turn. 343 | turns++; 344 | lastDirection = foundDirection; 345 | } 346 | break; 347 | } 348 | } 349 | } 350 | // if the current pattern continued, extend j and try to grow again 351 | if (found) { 352 | j ++; 353 | // otherwise push the pattern discovered so far, if any... 354 | } else { 355 | if (j - i > 2) { // don't consider length 1 or 2 chains. 356 | DBMatch *match = [[DBMatch alloc] init]; 357 | match.pattern = @"spatial"; 358 | match.i = i; 359 | match.j = j - 1; 360 | match.token = [password substringWithRange:NSMakeRange(i, j - i)]; 361 | match.graph = graphName; 362 | match.turns = turns; 363 | match.shiftedCount = shiftedCount; 364 | [result addObject:match]; 365 | } 366 | // ...and then start a new search for the rest of the password. 367 | i = j; 368 | break; 369 | } 370 | } 371 | } 372 | 373 | return result; 374 | } 375 | 376 | #pragma mark - repeats (aaa) and sequences (abcdef) 377 | 378 | - (MatcherBlock)repeatMatch 379 | { 380 | MatcherBlock block = ^ NSArray* (NSString *password) { 381 | NSMutableArray *result = [[NSMutableArray alloc] init]; 382 | int i = 0; 383 | while (i < [password length]) { 384 | int j = i + 1; 385 | while (YES) { 386 | NSString *prevChar = [password substringWithRange:NSMakeRange(j - 1, 1)]; 387 | NSString *curChar = j < [password length] ? [password substringWithRange:NSMakeRange(j, 1)] : @""; 388 | if ([prevChar isEqualToString:curChar]) { 389 | j++; 390 | } else { 391 | if (j - i > 2) { // don't consider length 1 or 2 chains. 392 | DBMatch *match = [[DBMatch alloc] init]; 393 | match.pattern = @"repeat"; 394 | match.i = i; 395 | match.j = j - 1; 396 | match.token = [password substringWithRange:NSMakeRange(i, j - i)]; 397 | match.repeatedChar = [password substringWithRange:NSMakeRange(i, 1)]; 398 | [result addObject:match]; 399 | } 400 | break; 401 | } 402 | } 403 | i = j; 404 | } 405 | return result; 406 | }; 407 | 408 | return block; 409 | } 410 | 411 | - (MatcherBlock)sequenceMatch 412 | { 413 | NSDictionary *sequences = @{ 414 | @"lower": @"abcdefghijklmnopqrstuvwxyz", 415 | @"upper": @"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 416 | @"digits": @"01234567890", 417 | }; 418 | 419 | MatcherBlock block = ^ NSArray* (NSString *password) { 420 | NSMutableArray *result = [[NSMutableArray alloc] init]; 421 | int i = 0; 422 | while (i < [password length]) { 423 | int j = i + 1; 424 | NSString *seq = nil; // either lower, upper, or digits 425 | NSString *seqName = nil; 426 | NSUInteger seqDirection = 0; // 1 for ascending seq abcd, -1 for dcba 427 | for (NSString *seqCandidateName in sequences) { 428 | NSString *seqCandidate = [sequences objectForKey:seqCandidateName]; 429 | NSUInteger iN = [seqCandidate rangeOfString:[password substringWithRange:NSMakeRange(i, 1)]].location; 430 | NSUInteger jN = j < [password length] ? [seqCandidate rangeOfString:[password substringWithRange:NSMakeRange(j, 1)]].location : NSNotFound; 431 | if (iN != NSNotFound && jN != NSNotFound) { 432 | NSUInteger direction = jN - iN; 433 | if (direction == 1 || direction == -1) { 434 | seq = seqCandidate; 435 | seqName = seqCandidateName; 436 | seqDirection = direction; 437 | break; 438 | } 439 | } 440 | } 441 | if (seq) { 442 | while (YES) { 443 | NSString *prevChar = [password substringWithRange:NSMakeRange(j - 1, 1)]; 444 | NSString *curChar = j < [password length] ? [password substringWithRange:NSMakeRange(j, 1)] : nil; 445 | NSUInteger prevN = [seq rangeOfString:prevChar].location; 446 | NSUInteger curN = curChar == nil ? NSNotFound : [seq rangeOfString:curChar].location; 447 | if (curN - prevN == seqDirection) { 448 | j++; 449 | } else { 450 | if (j - i > 2) { // don't consider length 1 or 2 chains. 451 | DBMatch *match = [[DBMatch alloc] init]; 452 | match.pattern = @"sequence"; 453 | match.i = i; 454 | match.j = j - 1; 455 | match.token = [password substringWithRange:NSMakeRange(i, j - i)]; 456 | match.sequenceName = seqName; 457 | match.sequenceSpace = (int)[seq length]; 458 | match.ascending = seqDirection == 1; 459 | [result addObject:match]; 460 | } 461 | break; 462 | } 463 | } 464 | } 465 | i = j; 466 | } 467 | 468 | return result; 469 | }; 470 | 471 | return block; 472 | } 473 | 474 | #pragma mark - digits, years, dates 475 | 476 | - (NSArray *)findAll:(NSString *)password patternName:(NSString *)patternName rx:(NSRegularExpression *)rx 477 | { 478 | NSMutableArray *matches = [[NSMutableArray alloc] init]; 479 | 480 | for (NSTextCheckingResult *result in [rx matchesInString:password options:0 range:NSMakeRange(0, [password length])]) { 481 | 482 | DBMatch *match = [[DBMatch alloc] init]; 483 | match.pattern = patternName; 484 | match.i = [result range].location; 485 | match.j = [result range].length + match.i - 1; 486 | match.token = [password substringWithRange:[result range]]; 487 | 488 | if ([match.pattern isEqualToString:@"date"] && [result numberOfRanges] == 6) { 489 | int month; 490 | int day; 491 | int year; 492 | @try { 493 | month = [[password substringWithRange:[result rangeAtIndex:1]] intValue]; 494 | day = [[password substringWithRange:[result rangeAtIndex:3]] intValue]; 495 | year = [[password substringWithRange:[result rangeAtIndex:5]] intValue]; 496 | } 497 | @catch (NSException *exception) { 498 | continue; 499 | } 500 | 501 | match.separator = [result rangeAtIndex:2].location < [password length] ? [password substringWithRange:[result rangeAtIndex:2]] : @""; 502 | 503 | if (month >= 12 && month <= 31 && day <= 12) { // tolerate both day-month and month-day order 504 | int temp = day; 505 | day = month; 506 | month = temp; 507 | } 508 | if (day > 31 || month > 12) { 509 | continue; 510 | } 511 | if (year < 20) { 512 | year += 2000; // hey, it could be 1920, but this is only for display 513 | } else if (year < 100) { 514 | year += 1900; 515 | } 516 | 517 | match.day = day; 518 | match.month = month; 519 | match.year = year; 520 | } 521 | 522 | [matches addObject:match]; 523 | } 524 | 525 | return matches; 526 | } 527 | 528 | - (MatcherBlock)digitsMatch 529 | { 530 | NSRegularExpression *digitsRx = [NSRegularExpression regularExpressionWithPattern:@"\\d{3,}" options:0 error:nil]; 531 | 532 | __weak typeof(self) weakSelf = self; 533 | MatcherBlock block = ^ NSArray* (NSString *password) { 534 | return [weakSelf findAll:password patternName:@"digits" rx:digitsRx]; 535 | }; 536 | 537 | return block; 538 | } 539 | 540 | - (MatcherBlock)yearMatch 541 | { 542 | // 4-digit years only. 2-digit years have the same entropy as 2-digit brute force. 543 | NSRegularExpression *yearRx = [NSRegularExpression regularExpressionWithPattern:@"19\\d\\d|200\\d|201\\d" options:0 error:nil]; 544 | 545 | __weak typeof(self) weakSelf = self; 546 | MatcherBlock block = ^ NSArray* (NSString *password) { 547 | return [weakSelf findAll:password patternName:@"year" rx:yearRx]; 548 | }; 549 | 550 | return block; 551 | } 552 | 553 | - (MatcherBlock)dateMatch 554 | { 555 | // known bug: this doesn't cover all short dates w/o separators like 111911. 556 | NSRegularExpression *dateRx = [NSRegularExpression regularExpressionWithPattern:@"(\\d{1,2})( |-|\\/|\\.|_)?(\\d{1,2})( |-|\\/|\\.|_)?(19\\d{2}|200\\d|201\\d|\\d{2})" options:0 error:nil]; 557 | 558 | __weak typeof(self) weakSelf = self; 559 | MatcherBlock block = ^ NSArray* (NSString *password) { 560 | return [weakSelf findAll:password patternName:@"date" rx:dateRx]; 561 | }; 562 | 563 | return block; 564 | } 565 | 566 | #pragma mark - utilities 567 | 568 | - (NSString *)translate:(NSString *)string characterMap:(NSDictionary *)chrMap 569 | { 570 | for (NSString *key in chrMap) { 571 | string = [string stringByReplacingOccurrencesOfString:key withString:[chrMap objectForKey:key]]; 572 | } 573 | return string; 574 | } 575 | 576 | @end 577 | 578 | @implementation DBMatchResources 579 | 580 | + (DBMatchResources *)sharedDBMatcherResources 581 | { 582 | // singleton containing adjacency graphs and frequency graphs 583 | static DBMatchResources *sharedMatcher = nil; 584 | static dispatch_once_t pred; 585 | 586 | dispatch_once(&pred, ^{ 587 | sharedMatcher = [[self alloc] init]; 588 | }); 589 | 590 | return sharedMatcher; 591 | } 592 | 593 | - (id)init 594 | { 595 | self = [super init]; 596 | 597 | if (self != nil) { 598 | _dictionaryMatchers = [self loadFrequencyLists]; 599 | _graphs = [self loadAdjacencyGraphs]; 600 | } 601 | 602 | return self; 603 | } 604 | 605 | - (NSArray *)loadFrequencyLists 606 | { 607 | NSMutableArray *dictionaryMatchers = [[NSMutableArray alloc] init]; 608 | 609 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"frequency_lists" ofType:@"lzma"]; 610 | NSData *compressed = [NSData dataWithContentsOfFile:filePath]; 611 | NSError *error; 612 | NSData *data = [compressed decompressedDataUsingAlgorithm:NSDataCompressionAlgorithmLZMA error:&error]; 613 | if (error != nil) { 614 | NSLog(@"Error reading frequency lists: %@", error); 615 | return dictionaryMatchers; 616 | } 617 | 618 | error = nil; 619 | id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 620 | 621 | if (error == nil) { 622 | for (NSString *dictName in (NSDictionary *)json) { 623 | 624 | NSArray *wordList = [(NSDictionary *)json objectForKey:dictName]; 625 | NSMutableDictionary *rankedDict = [self buildRankedDict:wordList]; 626 | 627 | [dictionaryMatchers addObject:[self buildDictMatcher:dictName rankedDict:rankedDict]]; 628 | } 629 | } else { 630 | NSLog(@"Error parsing frequency lists: %@", error); 631 | } 632 | 633 | return dictionaryMatchers; 634 | } 635 | 636 | - (NSDictionary *)loadAdjacencyGraphs 637 | { 638 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"adjacency_graphs" ofType:@"lzma"]; 639 | NSData *compressed = [NSData dataWithContentsOfFile:filePath]; 640 | NSError *error; 641 | NSData *data = [compressed decompressedDataUsingAlgorithm:NSDataCompressionAlgorithmLZMA error:&error]; 642 | if (error != nil) { 643 | NSLog(@"Error reading adjacency graphs: %@", error); 644 | return nil; 645 | } 646 | 647 | error = nil; 648 | id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; 649 | 650 | if (error == nil) { 651 | return (NSDictionary *)json; 652 | } else { 653 | NSLog(@"Error parsing adjacency graphs: %@", error); 654 | } 655 | 656 | return nil; 657 | } 658 | 659 | 660 | - (NSMutableDictionary *)buildRankedDict:(NSArray *)unrankedList 661 | { 662 | NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; 663 | int i = 1; // rank starts at 1, not 0 664 | 665 | for (NSString *word in unrankedList) { 666 | [result setObject:[NSNumber numberWithInt:i] forKey:word]; 667 | i++; 668 | } 669 | 670 | return result; 671 | } 672 | 673 | - (MatcherBlock)buildDictMatcher:(NSString *)dictName rankedDict:(NSMutableDictionary *)rankedDict 674 | { 675 | __typeof__(self) __weak weakSelf = self; 676 | MatcherBlock block = ^ NSArray* (NSString *password) { 677 | 678 | NSMutableArray *matches = [weakSelf dictionaryMatch:password rankedDict:rankedDict]; 679 | 680 | for (DBMatch *match in matches) { 681 | match.dictionaryName = dictName; 682 | } 683 | 684 | return matches; 685 | }; 686 | 687 | return block; 688 | } 689 | 690 | #pragma mark - dictionary match (common passwords, english, last names, etc) 691 | 692 | - (NSMutableArray *)dictionaryMatch:(NSString *)password rankedDict:(NSMutableDictionary *)rankedDict 693 | { 694 | NSMutableArray *result = [[NSMutableArray alloc] init]; 695 | NSUInteger length = [password length]; 696 | NSString *passwordLower = [password lowercaseString]; 697 | 698 | for (int i = 0; i < length; i++) { 699 | for (int j = i; j < length; j++) { 700 | NSString *word = [passwordLower substringWithRange:NSMakeRange(i, j - i + 1)]; 701 | NSNumber *rank = [rankedDict objectForKey:word]; 702 | 703 | if (rank != nil) { 704 | DBMatch *match = [[DBMatch alloc] init]; 705 | match.pattern = @"dictionary"; 706 | match.i = i; 707 | match.j = j; 708 | match.token = [password substringWithRange:NSMakeRange(i, j - i + 1)]; 709 | match.matchedWord = word; 710 | match.rank = [rank intValue]; 711 | [result addObject:match]; 712 | } 713 | } 714 | } 715 | 716 | return result; 717 | } 718 | 719 | @end 720 | 721 | 722 | @implementation DBMatch 723 | 724 | @end 725 | -------------------------------------------------------------------------------- /Zxcvbn/DBPasswordStrengthMeterView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBPasswordStrengthMeterView.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/22/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol DBPasswordStrengthMeterViewDelegate; 12 | 13 | @interface DBPasswordStrengthMeterView : UIView 14 | 15 | @property (nonatomic, assign) id delegate; 16 | 17 | - (void)setLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor; 18 | - (void)scorePassword:(NSString *)password; 19 | - (void)scorePassword:(NSString *)password userInputs:(NSArray *)userInputs; 20 | 21 | @end 22 | 23 | @protocol DBPasswordStrengthMeterViewDelegate 24 | 25 | - (void)passwordStrengthMeterViewTapped:(DBPasswordStrengthMeterView *)passwordStrengthMeterView; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Zxcvbn/DBPasswordStrengthMeterView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBPasswordStrengthMeterView.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/22/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBPasswordStrengthMeterView.h" 10 | 11 | #import "DBZxcvbn.h" 12 | 13 | @interface DBPasswordStrengthMeterView () 14 | 15 | @property (nonatomic, strong) DBZxcvbn *zxcvbn; 16 | 17 | @property (nonatomic, strong) UIView *view1; 18 | @property (nonatomic, strong) UIView *view2; 19 | @property (nonatomic, strong) UIView *view3; 20 | @property (nonatomic, strong) UIView *view4; 21 | @property (nonatomic, strong) NSArray *meterViews; 22 | 23 | @property (nonatomic, strong) UIColor *lightColor; 24 | @property (nonatomic, strong) UIColor *darkColor; 25 | 26 | @end 27 | 28 | @implementation DBPasswordStrengthMeterView 29 | 30 | - (id)init 31 | { 32 | return [self initWithFrame:CGRectMake(0.0, 0.0, 8.0, 30.0)]; 33 | } 34 | 35 | - (id)initWithFrame:(CGRect)frame 36 | { 37 | self = [super initWithFrame:frame]; 38 | if (self) { 39 | [self sharedInit]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)awakeFromNib 45 | { 46 | [super awakeFromNib]; 47 | 48 | [self sharedInit]; 49 | } 50 | 51 | - (void)sharedInit 52 | { 53 | self.zxcvbn = [[DBZxcvbn alloc] init]; 54 | 55 | float unitHeight = 6.0; 56 | float padding = 2.0; 57 | float width = self.frame.size.width; 58 | 59 | self.lightColor = [UIColor colorWithRed:204.0/255.0 green:230.0/255.0 blue:249.0/255.0 alpha:1.0]; 60 | self.darkColor = [UIColor colorWithRed:64.0/255.0 green:147.0/255.0 blue:224.0/255.0 alpha:1.0]; 61 | 62 | self.view1 = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - unitHeight, width, unitHeight)]; 63 | self.view1.backgroundColor = self.lightColor; 64 | [self addSubview:self.view1]; 65 | 66 | self.view2 = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - (unitHeight * 2 + padding), width, unitHeight)]; 67 | self.view2.backgroundColor = self.lightColor; 68 | [self addSubview:self.view2]; 69 | 70 | self.view3 = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - (unitHeight * 3 + padding * 2), width, unitHeight)]; 71 | self.view3.backgroundColor = self.lightColor; 72 | [self addSubview:self.view3]; 73 | 74 | self.view4 = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - (unitHeight * 4 + padding * 3), width, unitHeight)]; 75 | self.view4.backgroundColor = self.lightColor; 76 | [self addSubview:self.view4]; 77 | 78 | self.meterViews = @[self.view1, self.view2, self.view3, self.view4]; 79 | 80 | UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(strengthMeterTapped:)]; 81 | [self addGestureRecognizer:tapGestureRecognizer]; 82 | } 83 | 84 | - (void)strengthMeterTapped:(UITapGestureRecognizer *)tapGestureRecognizer 85 | { 86 | if ([self.delegate respondsToSelector:@selector(passwordStrengthMeterViewTapped:)]) { 87 | [self.delegate passwordStrengthMeterViewTapped:self]; 88 | } 89 | } 90 | 91 | - (void)setLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor 92 | { 93 | self.lightColor = lightColor; 94 | self.darkColor = darkColor; 95 | 96 | for (UIView *view in self.meterViews) { 97 | view.backgroundColor = lightColor; 98 | } 99 | } 100 | 101 | - (void)scorePassword:(NSString *)password 102 | { 103 | [self scorePassword:password userInputs:nil]; 104 | } 105 | 106 | - (void)scorePassword:(NSString *)password userInputs:(NSArray *)userInputs 107 | { 108 | int score = [self.zxcvbn passwordStrength:password userInputs:userInputs].score; 109 | 110 | for (int i = 0; i < [self.meterViews count]; i++) { 111 | UIView *meterView = [self.meterViews objectAtIndex:i]; 112 | meterView.backgroundColor = i < score ? self.darkColor : self.lightColor; 113 | } 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /Zxcvbn/DBScorer.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBScorer.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class DBResult; 12 | 13 | @interface DBScorer : NSObject 14 | 15 | - (DBResult *)minimumEntropyMatchSequence:(NSString *)password matches:(NSArray *)matches; 16 | 17 | @end 18 | 19 | 20 | @interface DBResult : NSObject 21 | 22 | @property (strong, nonatomic) NSString *password; 23 | @property (strong, nonatomic) NSString *entropy; // bits 24 | @property (strong, nonatomic) NSString *crackTime; // estimation of actual crack time, in seconds. 25 | @property (strong, nonatomic) NSString *crackTimeDisplay; // same crack time, as a friendlier string: "instant", "6 minutes", "centuries", etc. 26 | @property (nonatomic, assign) int score; // [0,1,2,3,4] if crack time is less than [10**2, 10**4, 10**6, 10**8, Infinity]. (useful for implementing a strength bar.) 27 | @property (strong, nonatomic) NSArray *matchSequence; // the list of patterns that zxcvbn based the entropy calculation on. 28 | @property (nonatomic, assign) float calcTime; // how long it took to calculate an answer, in milliseconds. usually only a few ms. 29 | 30 | @end -------------------------------------------------------------------------------- /Zxcvbn/DBScorer.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBScorer.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBScorer.h" 10 | 11 | #import "DBMatcher.h" 12 | 13 | @implementation DBScorer 14 | 15 | - (DBResult *)minimumEntropyMatchSequence:(NSString *)password matches:(NSArray *)matches 16 | { 17 | /* minimum entropy search 18 | 19 | takes a list of overlapping matches, returns the non-overlapping sublist with 20 | minimum entropy. O(nm) dp alg for length-n password with m candidate matches. 21 | */ 22 | 23 | float bruteforceCardinality = [self calcBruteforceCardinality:password]; // e.g. 26 for lowercase 24 | 25 | NSMutableArray *upToK = [[NSMutableArray alloc] init]; // minimum entropy up to k. 26 | NSMutableArray *backpointers = [[NSMutableArray alloc] init]; // for the optimal sequence of matches up to k, holds the final match (match.j == k). null means the sequence ends w/ a brute-force character. 27 | 28 | for (int k = 0; k < [password length]; k++) { 29 | // starting scenario to try and beat: adding a brute-force character to the minimum entropy sequence at k-1. 30 | [upToK insertObject:[NSNumber numberWithFloat:[get(upToK, k-1) floatValue] + lg(bruteforceCardinality)] atIndex:k]; 31 | [backpointers insertObject:[NSNull null] atIndex:k]; 32 | for (DBMatch *match in matches) { 33 | NSUInteger i = match.i; 34 | NSUInteger j = match.j; 35 | if (j != k) { 36 | continue; 37 | } 38 | // see if best entropy up to i-1 + entropy of this match is less than the current minimum at j. 39 | float candidateEntropy = [get(upToK, (int)i-1) floatValue] + [self calcEntropy:match]; 40 | if (candidateEntropy < [[upToK objectAtIndex:j] floatValue]) { 41 | [upToK insertObject:[NSNumber numberWithFloat:candidateEntropy] atIndex:j]; 42 | [backpointers insertObject:match atIndex:j]; 43 | } 44 | } 45 | } 46 | 47 | // walk backwards and decode the best sequence 48 | NSMutableArray *matchSequence = [[NSMutableArray alloc] init]; 49 | NSInteger k = [password length] - 1; 50 | while (k >= 0) { 51 | DBMatch *match = [backpointers objectAtIndex:k]; 52 | if (![match isEqual:[NSNull null]]) { 53 | [matchSequence addObject:match]; 54 | k = match.i - 1; 55 | } else { 56 | k -= 1; 57 | } 58 | } 59 | matchSequence = [[NSMutableArray alloc] initWithArray:[[matchSequence reverseObjectEnumerator] allObjects]]; 60 | 61 | // fill in the blanks between pattern matches with bruteforce "matches" 62 | // that way the match sequence fully covers the password: match1.j == match2.i - 1 for every adjacent match1, match2. 63 | DBMatch* (^makeBruteforceMatch)(NSUInteger i, NSUInteger j) = ^ DBMatch* (NSUInteger i, NSUInteger j) { 64 | DBMatch *match = [[DBMatch alloc] init]; 65 | match.pattern = @"bruteforce"; 66 | match.i = i; 67 | match.j = j; 68 | match.token = [password substringWithRange:NSMakeRange(i, j - i + 1)]; 69 | match.entropy = lg(pow(bruteforceCardinality, j - i + 1)); 70 | match.cardinality = bruteforceCardinality; 71 | return match; 72 | }; 73 | k = 0; 74 | NSMutableArray *matchSequenceCopy = [[NSMutableArray alloc] init]; 75 | for (DBMatch *match in matchSequence) { 76 | NSUInteger i = match.i; 77 | NSUInteger j = match.j; 78 | if (i - k > 0) { 79 | [matchSequenceCopy addObject:makeBruteforceMatch(k, i-1)]; 80 | } 81 | k = j + 1; 82 | [matchSequenceCopy addObject:match]; 83 | } 84 | if (k < [password length]) { 85 | [matchSequenceCopy addObject:makeBruteforceMatch(k, [password length] - 1)]; 86 | matchSequence = matchSequenceCopy; 87 | } 88 | 89 | float minEntropy = 0.0; 90 | if ([password length] > 0) { // corner case is for an empty password '' 91 | minEntropy = [[upToK objectAtIndex:[password length] - 1] floatValue]; 92 | } 93 | float crackTime = [self entropyToCrackTime:minEntropy]; 94 | 95 | // final result object 96 | DBResult *result = [[DBResult alloc] init]; 97 | result.password = password; 98 | result.entropy = roundToXDigits(minEntropy, 3); 99 | result.matchSequence = matchSequence; 100 | result.crackTime = roundToXDigits(crackTime, 3); 101 | result.crackTimeDisplay = [self displayTime:crackTime]; 102 | result.score = [self crackTimeToScore:crackTime]; 103 | return result; 104 | } 105 | 106 | - (float)entropyToCrackTime:(float)entropy 107 | { 108 | /* 109 | threat model -- stolen hash catastrophe scenario 110 | 111 | assumes: 112 | * passwords are stored as salted hashes, different random salt per user. 113 | (making rainbow attacks infeasable.) 114 | * hashes and salts were stolen. attacker is guessing passwords at max rate. 115 | * attacker has several CPUs at their disposal. 116 | 117 | * for a hash function like bcrypt/scrypt/PBKDF2, 10ms per guess is a safe lower bound. 118 | * (usually a guess would take longer -- this assumes fast hardware and a small work factor.) 119 | * adjust for your site accordingly if you use another hash function, possibly by 120 | * several orders of magnitude! 121 | */ 122 | 123 | float singleGuess = .010; 124 | float numAttackers = 100; // number of cores guessing in parallel. 125 | 126 | float secondsPerGuess = singleGuess / numAttackers; 127 | 128 | return .5 * pow(2, entropy) * secondsPerGuess; // average, not total 129 | } 130 | 131 | - (int)crackTimeToScore:(float)seconds 132 | { 133 | if (seconds < pow(10, 2)) { 134 | return 0; 135 | } 136 | if (seconds < pow(10, 4)) { 137 | return 1; 138 | } 139 | if (seconds < pow(10, 6)) { 140 | return 2; 141 | } 142 | if (seconds < pow(10, 8)) { 143 | return 3; 144 | } 145 | return 4; 146 | } 147 | 148 | #pragma mark - entropy calcs -- one function per match pattern 149 | 150 | - (float)calcEntropy:(DBMatch *)match 151 | { 152 | if (match.entropy > 0) { 153 | // a match's entropy doesn't change. cache it. 154 | return match.entropy; 155 | } 156 | 157 | if ([match.pattern isEqualToString:@"repeat"]) { 158 | match.entropy = [self repeatEntropy:match]; 159 | } else if ([match.pattern isEqualToString:@"sequence"]) { 160 | match.entropy = [self sequenceEntropy:match]; 161 | } else if ([match.pattern isEqualToString:@"digits"]) { 162 | match.entropy = [self digitsEntropy:match]; 163 | } else if ([match.pattern isEqualToString:@"year"]) { 164 | match.entropy = [self yearEntropy:match]; 165 | } else if ([match.pattern isEqualToString:@"date"]) { 166 | match.entropy = [self dateEntropy:match]; 167 | } else if ([match.pattern isEqualToString:@"spatial"]) { 168 | match.entropy = [self spatialEntropy:match]; 169 | } else if ([match.pattern isEqualToString:@"dictionary"]) { 170 | match.entropy = [self dictionaryEntropy:match]; 171 | } 172 | 173 | return match.entropy; 174 | } 175 | 176 | - (float)repeatEntropy:(DBMatch *)match 177 | { 178 | float cardinality = [self calcBruteforceCardinality:match.token]; 179 | return lg(cardinality * [match.token length]); 180 | } 181 | 182 | - (float)sequenceEntropy:(DBMatch *)match 183 | { 184 | NSString *firstChr = [match.token substringToIndex:1]; 185 | float baseEntropy = 0; 186 | if ([@[@"a", @"1"] containsObject:firstChr]) { 187 | baseEntropy = 1; 188 | } else { 189 | unichar chr = [firstChr characterAtIndex:0]; 190 | if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:chr]) { 191 | baseEntropy = lg(10); // digits 192 | } else if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:chr]) { 193 | baseEntropy = lg(26); // lower 194 | } else { 195 | baseEntropy = lg(26) + 1; // extra bit for uppercase 196 | } 197 | } 198 | if (!match.ascending) { 199 | baseEntropy += 1; // extra bit for descending instead of ascending 200 | } 201 | return baseEntropy + lg([match.token length]); 202 | } 203 | 204 | - (float)digitsEntropy:(DBMatch *)match 205 | { 206 | return lg(pow(10, [match.token length])); 207 | } 208 | 209 | static int kNumYears = 119; // years match against 1900 - 2019 210 | static int kNumMonths = 12; 211 | static int kNumDays = 31; 212 | 213 | - (float)yearEntropy:(DBMatch *)match 214 | { 215 | return lg(kNumYears); 216 | } 217 | 218 | - (float)dateEntropy:(DBMatch *)match 219 | { 220 | float entropy = 0.0; 221 | if (match.year < 100) { 222 | entropy = lg(kNumDays * kNumMonths * 100); // two-digit year 223 | } else { 224 | entropy = lg(kNumDays * kNumMonths * kNumYears); // four-digit year 225 | } 226 | if ([match.separator length]) { 227 | entropy += 2; // add two bits for separator selection [/,-,.,etc] 228 | } 229 | return entropy; 230 | } 231 | 232 | - (float)spatialEntropy:(DBMatch *)match 233 | { 234 | DBMatcher *matcher = [[DBMatcher alloc] init]; 235 | NSUInteger s; 236 | NSUInteger d; 237 | if ([@[@"qwerty", @"dvorak"] containsObject:match.graph]) { 238 | s = matcher.keyboardStartingPositions; 239 | d = matcher.keyboardAverageDegree; 240 | } else { 241 | s = matcher.keypadStartingPositions; 242 | d = matcher.keypadAverageDegree; 243 | } 244 | int possibilities = 0; 245 | NSUInteger L = [match.token length]; 246 | int t = match.turns; 247 | // estimate the number of possible patterns w/ length L or less with t turns or less. 248 | for (int i = 2; i <= L; i++) { 249 | int possibleTurns = MIN(t, i - 1); 250 | for (int j = 1; j <= possibleTurns; j++) { 251 | possibilities += binom(i - 1, j - 1) * s * pow(d, j); 252 | } 253 | } 254 | float entropy = lg(possibilities); 255 | // add extra entropy for shifted keys. (% instead of 5, A instead of a.) 256 | // math is similar to extra entropy from uppercase letters in dictionary matches. 257 | if (match.shiftedCount) { 258 | int S = match.shiftedCount; 259 | NSUInteger U = [match.token length] - match.shiftedCount; // unshifted count 260 | NSUInteger possibilities = 0; 261 | for (int i = 0; i <= MIN(S, U); i++) { 262 | possibilities += binom(S + U, i); 263 | } 264 | entropy += lg(possibilities); 265 | } 266 | return entropy; 267 | } 268 | 269 | - (float)dictionaryEntropy:(DBMatch *)match 270 | { 271 | match.baseEntropy = lg(match.rank); // keep these as properties for display purposes 272 | match.upperCaseEntropy = [self extraUppercaseEntropy:match]; 273 | match.l33tEntropy = [self extraL33tEntropy:match]; 274 | return match.baseEntropy + match.upperCaseEntropy + match.l33tEntropy; 275 | } 276 | 277 | - (float)extraUppercaseEntropy:(DBMatch *)match 278 | { 279 | NSString *word = match.token; 280 | if ([word rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location == NSNotFound) { 281 | return 0; // all lower 282 | } 283 | 284 | // a capitalized word is the most common capitalization scheme, 285 | // so it only doubles the search space (uncapitalized + capitalized): 1 extra bit of entropy. 286 | // allcaps and end-capitalized are common enough too, underestimate as 1 extra bit to be safe. 287 | NSString *startUpper = @"^[A-Z][^A-Z]+$"; 288 | NSString *endUpper = @"^[^A-Z]+[A-Z]$"; 289 | NSString *allUpper = @"^[A-Z]+$"; 290 | for (NSString *regex in @[startUpper, endUpper, allUpper]) { 291 | if ([[NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex] evaluateWithObject:word]) { 292 | return 1; 293 | } 294 | } 295 | 296 | // otherwise calculate the number of ways to capitalize U+L uppercase+lowercase letters with U uppercase letters or less. 297 | // or, if there's more uppercase than lower (for e.g. PASSwORD), the number of ways to lowercase U+L letters with L lowercase letters or less. 298 | int uppercaseLength = 0; 299 | int lowercaseLength = 0; 300 | for (int i = 0; i < [word length]; i++) { 301 | unichar chr = [word characterAtIndex:i]; 302 | if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:chr]) { 303 | uppercaseLength++; 304 | } else if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:chr]) { 305 | lowercaseLength++; 306 | } 307 | } 308 | 309 | float possibilities = 0.0; 310 | for (int i = 0; i <= MIN(uppercaseLength, lowercaseLength); i++) { 311 | possibilities += binom(uppercaseLength + lowercaseLength, i); 312 | } 313 | return lg(possibilities); 314 | } 315 | 316 | - (int)extraL33tEntropy:(DBMatch *)match 317 | { 318 | if (!match.l33t) { 319 | return 0; 320 | } 321 | 322 | int possibilities = 0; 323 | 324 | for (NSString *subbed in [match.sub allKeys]) { 325 | NSString *unsubbed = [match.sub objectForKey:subbed]; 326 | NSUInteger subLength = [[match.token componentsSeparatedByString:subbed] count] - 1; 327 | NSUInteger unsubLength = [[match.token componentsSeparatedByString:unsubbed] count] - 1; 328 | for (int i = 0; i <= MIN(unsubLength, subLength); i++) { 329 | possibilities += binom(unsubLength + subLength, i); 330 | } 331 | } 332 | 333 | // corner: return 1 bit for single-letter subs, like 4pple -> apple, instead of 0. 334 | return possibilities <= 1 ? 1 : lg(possibilities); 335 | } 336 | 337 | #pragma mark - utilities 338 | 339 | - (float)calcBruteforceCardinality:(NSString *)password 340 | { 341 | int digits = 0; 342 | int upper = 0; 343 | int lower = 0; 344 | int symbols = 0; 345 | 346 | for (int i = 0; i < [password length]; i++) { 347 | unichar chr = [password characterAtIndex:i]; 348 | 349 | if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:chr]) { 350 | digits = 10; 351 | } else if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:chr]) { 352 | upper = 26; 353 | } else if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:chr]) { 354 | lower = 26; 355 | } else { 356 | symbols = 33; 357 | } 358 | } 359 | 360 | return digits + upper + lower + symbols; 361 | } 362 | 363 | - (NSString *)displayTime:(float)seconds 364 | { 365 | int minute = 60; 366 | int hour = minute * 60; 367 | int day = hour * 24; 368 | int month = day * 31; 369 | int year = month * 12; 370 | int century = year * 100; 371 | if (seconds < minute) 372 | return @"instant"; 373 | if (seconds < hour) 374 | return [NSString stringWithFormat:@"%d minutes", 1 + (int)ceil(seconds / minute)]; 375 | if (seconds < day) 376 | return [NSString stringWithFormat:@"%d hours", 1 + (int)ceil(seconds / hour)]; 377 | if (seconds < month) 378 | return [NSString stringWithFormat:@"%d days", 1 + (int)ceil(seconds / day)]; 379 | if (seconds < year) 380 | return [NSString stringWithFormat:@"%d months", 1 + (int)ceil(seconds / month)]; 381 | if (seconds < century) 382 | return [NSString stringWithFormat:@"%d years", 1 + (int)ceil(seconds / year)]; 383 | return @"centuries"; 384 | } 385 | 386 | #pragma mark - functions 387 | 388 | float binom(NSUInteger n, NSUInteger k) 389 | { 390 | // Returns binomial coefficient (n choose k). 391 | // http://blog.plover.com/math/choose.html 392 | if (k > n) { return 0; } 393 | if (k == 0) { return 1; } 394 | float result = 1; 395 | for (int denom = 1; denom <= k; denom++) { 396 | result *= n; 397 | result /= denom; 398 | n -= 1; 399 | } 400 | return result; 401 | } 402 | 403 | float lg(float n) 404 | { 405 | return log2f(n); 406 | } 407 | 408 | NSString* roundToXDigits(float number, int digits) 409 | { 410 | //return round(number * pow(10, digits)) / pow(10, digits); 411 | return [NSString stringWithFormat:@"%.*f", digits, number]; 412 | } 413 | 414 | id get(NSArray *a, int i) 415 | { 416 | if (i < 0 || i >= [a count]) { 417 | return 0; 418 | } 419 | return [a objectAtIndex:i]; 420 | } 421 | 422 | @end 423 | 424 | 425 | @implementation DBResult 426 | 427 | @end -------------------------------------------------------------------------------- /Zxcvbn/DBZxcvbn.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBZxcvbn.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBMatcher.h" 10 | #import "DBScorer.h" 11 | 12 | @interface DBZxcvbn : NSObject 13 | 14 | - (DBResult *)passwordStrength:(NSString *)password; 15 | - (DBResult *)passwordStrength:(NSString *)password userInputs:(NSArray *)userInputs; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Zxcvbn/DBZxcvbn.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBZxcvbn.m 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import "DBZxcvbn.h" 10 | #import 11 | 12 | @interface DBZxcvbn () 13 | 14 | @property (nonatomic, strong) DBMatcher *matcher; 15 | @property (nonatomic, strong) DBScorer *scorer; 16 | 17 | @end 18 | 19 | @implementation DBZxcvbn 20 | 21 | - (id)init 22 | { 23 | self = [super init]; 24 | 25 | if (self != nil) { 26 | self.matcher = [[DBMatcher alloc] init]; 27 | self.scorer = [[DBScorer alloc] init]; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | - (DBResult *)passwordStrength:(NSString *)password 34 | { 35 | return [self passwordStrength:password userInputs:nil]; 36 | } 37 | 38 | - (DBResult *)passwordStrength:(NSString *)password userInputs:(NSArray *)userInputs 39 | { 40 | CFTimeInterval start = CACurrentMediaTime(); 41 | NSArray *matches = [self.matcher omnimatch:password userInputs:userInputs]; 42 | DBResult *result = [self.scorer minimumEntropyMatchSequence:password matches:matches]; 43 | CFTimeInterval end = CACurrentMediaTime(); 44 | result.calcTime = (end - start) * 1000.0; 45 | 46 | return result; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Zxcvbn/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Zxcvbn/Zxcvbn.h: -------------------------------------------------------------------------------- 1 | // 2 | // Zxcvbn.h 3 | // Zxcvbn 4 | // 5 | // Created by Leah Culver on 26 Oct 2015. 6 | // Copyright © 2015 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Zxcvbn. 12 | FOUNDATION_EXPORT double ZxcvbnVersionNumber; 13 | 14 | //! Project version string for Zxcvbn. 15 | FOUNDATION_EXPORT const unsigned char ZxcvbnVersionString[]; 16 | 17 | #import 18 | #import 19 | #import 20 | #import 21 | -------------------------------------------------------------------------------- /Zxcvbn/generated/adjacency_graphs.lzma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropbox/zxcvbn-ios/18876239f96aaecceea91051bbcc3b3161b19d20/Zxcvbn/generated/adjacency_graphs.lzma -------------------------------------------------------------------------------- /Zxcvbn/generated/frequency_lists.lzma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropbox/zxcvbn-ios/18876239f96aaecceea91051bbcc3b3161b19d20/Zxcvbn/generated/frequency_lists.lzma -------------------------------------------------------------------------------- /ZxcvbnTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ZxcvbnTests/ZxcvbnTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ZxcvbnTests.m 3 | // ZxcvbnTests 4 | // 5 | // Created by Leah Culver on 2/9/14. 6 | // Copyright (c) 2014 Dropbox. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "DBZxcvbn.h" 12 | 13 | @interface ZxcvbnTests : XCTestCase 14 | 15 | @end 16 | 17 | @implementation ZxcvbnTests 18 | 19 | - (void)setUp 20 | { 21 | [super setUp]; 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | - (void)tearDown 26 | { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | [super tearDown]; 29 | } 30 | 31 | - (void)testScore0Password 32 | { 33 | 34 | DBZxcvbn *zxcvbn = [[DBZxcvbn alloc] init]; 35 | DBResult *result = [zxcvbn passwordStrength:@"easy password" userInputs:nil]; 36 | 37 | XCTAssertTrue([@"english" isEqualToString:[(DBMatch *)result.matchSequence[0] dictionaryName]]); 38 | XCTAssertTrue([@"dictionary" isEqualToString:[(DBMatch *)result.matchSequence[0] pattern]]); 39 | 40 | XCTAssertEqual(result.score, 0); 41 | 42 | } 43 | 44 | - (void)testScore1Password 45 | { 46 | 47 | DBZxcvbn *zxcvbn = [[DBZxcvbn alloc] init]; 48 | DBResult *result = [zxcvbn passwordStrength:@"easy password2" userInputs:nil]; 49 | 50 | XCTAssertEqual(result.score, 1); 51 | 52 | } 53 | 54 | - (void)testStrongPassword 55 | { 56 | 57 | DBZxcvbn *zxcvbn = [[DBZxcvbn alloc] init]; 58 | DBResult *result = [zxcvbn passwordStrength:@"dkgit dldig394595 &&(3" userInputs:nil]; 59 | 60 | XCTAssertEqual(result.score, 4); 61 | 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /iOS Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0CF371F21D508A9B008907F2 /* Zxcvbn.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = D80ECC021BDEB0FE0055EF0A /* Zxcvbn.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 11 | 4CF9DAD21CD16B6F006A229D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CF9DAD01CD16B6F006A229D /* Main.storyboard */; }; 12 | D80ECBB11BDEAE9D0055EF0A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECBB01BDEAE9D0055EF0A /* main.m */; }; 13 | D80ECBBC1BDEAE9D0055EF0A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D80ECBBB1BDEAE9D0055EF0A /* Assets.xcassets */; }; 14 | D80ECBBF1BDEAE9D0055EF0A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D80ECBBD1BDEAE9D0055EF0A /* LaunchScreen.storyboard */; }; 15 | D80ECBF81BDEB0890055EF0A /* DBAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECBF71BDEB0890055EF0A /* DBAppDelegate.m */; }; 16 | D80ECBFB1BDEB0B10055EF0A /* DBCreateAccountViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D80ECBFA1BDEB0B10055EF0A /* DBCreateAccountViewController.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | D80ECC011BDEB0FE0055EF0A /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */; 23 | proxyType = 2; 24 | remoteGlobalIDString = D80ECB681BDEAA420055EF0A; 25 | remoteInfo = Zxcvbn; 26 | }; 27 | D80ECC031BDEB0FE0055EF0A /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */; 30 | proxyType = 2; 31 | remoteGlobalIDString = D80ECB721BDEAA420055EF0A; 32 | remoteInfo = ZxcvbnTests; 33 | }; 34 | D8EF582B1BDEB1420014E84B /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */; 37 | proxyType = 1; 38 | remoteGlobalIDString = D80ECB671BDEAA420055EF0A; 39 | remoteInfo = Zxcvbn; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | 0CF371F11D508A8B008907F2 /* CopyFiles */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | 0CF371F21D508A9B008907F2 /* Zxcvbn.framework in CopyFiles */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 4CF9DAD11CD16B6F006A229D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | D80ECBAC1BDEAE9D0055EF0A /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | D80ECBB01BDEAE9D0055EF0A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 60 | D80ECBBB1BDEAE9D0055EF0A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | D80ECBBE1BDEAE9D0055EF0A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | D80ECBC01BDEAE9D0055EF0A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | D80ECBF61BDEB0890055EF0A /* DBAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBAppDelegate.h; sourceTree = ""; }; 64 | D80ECBF71BDEB0890055EF0A /* DBAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBAppDelegate.m; sourceTree = ""; }; 65 | D80ECBF91BDEB0B10055EF0A /* DBCreateAccountViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBCreateAccountViewController.h; sourceTree = ""; }; 66 | D80ECBFA1BDEB0B10055EF0A /* DBCreateAccountViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBCreateAccountViewController.m; sourceTree = ""; }; 67 | D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Zxcvbn.xcodeproj; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | D80ECBA91BDEAE9D0055EF0A /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | D80ECBA31BDEAE9D0055EF0A = { 82 | isa = PBXGroup; 83 | children = ( 84 | D80ECBAE1BDEAE9D0055EF0A /* Source */, 85 | D80ECBAD1BDEAE9D0055EF0A /* Products */, 86 | D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | D80ECBAD1BDEAE9D0055EF0A /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | D80ECBAC1BDEAE9D0055EF0A /* iOS Example.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | D80ECBAE1BDEAE9D0055EF0A /* Source */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | D80ECBF61BDEB0890055EF0A /* DBAppDelegate.h */, 102 | D80ECBF71BDEB0890055EF0A /* DBAppDelegate.m */, 103 | D80ECBF91BDEB0B10055EF0A /* DBCreateAccountViewController.h */, 104 | D80ECBFA1BDEB0B10055EF0A /* DBCreateAccountViewController.m */, 105 | D80ECBAF1BDEAE9D0055EF0A /* Supporting Files */, 106 | ); 107 | name = Source; 108 | path = Example; 109 | sourceTree = ""; 110 | }; 111 | D80ECBAF1BDEAE9D0055EF0A /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | D80ECBB01BDEAE9D0055EF0A /* main.m */, 115 | D80ECBBB1BDEAE9D0055EF0A /* Assets.xcassets */, 116 | 4CF9DAD01CD16B6F006A229D /* Main.storyboard */, 117 | D80ECBBD1BDEAE9D0055EF0A /* LaunchScreen.storyboard */, 118 | D80ECBC01BDEAE9D0055EF0A /* Info.plist */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | D80ECBFD1BDEB0FE0055EF0A /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | D80ECC021BDEB0FE0055EF0A /* Zxcvbn.framework */, 127 | D80ECC041BDEB0FE0055EF0A /* ZxcvbnTests.xctest */, 128 | ); 129 | name = Products; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | D80ECBAB1BDEAE9D0055EF0A /* iOS Example */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = D80ECBD91BDEAE9E0055EF0A /* Build configuration list for PBXNativeTarget "iOS Example" */; 138 | buildPhases = ( 139 | D80ECBA81BDEAE9D0055EF0A /* Sources */, 140 | D80ECBA91BDEAE9D0055EF0A /* Frameworks */, 141 | D80ECBAA1BDEAE9D0055EF0A /* Resources */, 142 | 0CF371F11D508A8B008907F2 /* CopyFiles */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | D8EF582C1BDEB1420014E84B /* PBXTargetDependency */, 148 | ); 149 | name = "iOS Example"; 150 | productName = "iOS Example"; 151 | productReference = D80ECBAC1BDEAE9D0055EF0A /* iOS Example.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | D80ECBA41BDEAE9D0055EF0A /* Project object */ = { 158 | isa = PBXProject; 159 | attributes = { 160 | LastUpgradeCheck = 0710; 161 | ORGANIZATIONNAME = Dropbox; 162 | TargetAttributes = { 163 | D80ECBAB1BDEAE9D0055EF0A = { 164 | CreatedOnToolsVersion = 7.1; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = D80ECBA71BDEAE9D0055EF0A /* Build configuration list for PBXProject "iOS Example" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = English; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = D80ECBA31BDEAE9D0055EF0A; 177 | productRefGroup = D80ECBAD1BDEAE9D0055EF0A /* Products */; 178 | projectDirPath = ""; 179 | projectReferences = ( 180 | { 181 | ProductGroup = D80ECBFD1BDEB0FE0055EF0A /* Products */; 182 | ProjectRef = D80ECBFC1BDEB0FE0055EF0A /* Zxcvbn.xcodeproj */; 183 | }, 184 | ); 185 | projectRoot = ""; 186 | targets = ( 187 | D80ECBAB1BDEAE9D0055EF0A /* iOS Example */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXReferenceProxy section */ 193 | D80ECC021BDEB0FE0055EF0A /* Zxcvbn.framework */ = { 194 | isa = PBXReferenceProxy; 195 | fileType = wrapper.framework; 196 | path = Zxcvbn.framework; 197 | remoteRef = D80ECC011BDEB0FE0055EF0A /* PBXContainerItemProxy */; 198 | sourceTree = BUILT_PRODUCTS_DIR; 199 | }; 200 | D80ECC041BDEB0FE0055EF0A /* ZxcvbnTests.xctest */ = { 201 | isa = PBXReferenceProxy; 202 | fileType = wrapper.cfbundle; 203 | path = ZxcvbnTests.xctest; 204 | remoteRef = D80ECC031BDEB0FE0055EF0A /* PBXContainerItemProxy */; 205 | sourceTree = BUILT_PRODUCTS_DIR; 206 | }; 207 | /* End PBXReferenceProxy section */ 208 | 209 | /* Begin PBXResourcesBuildPhase section */ 210 | D80ECBAA1BDEAE9D0055EF0A /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 4CF9DAD21CD16B6F006A229D /* Main.storyboard in Resources */, 215 | D80ECBBF1BDEAE9D0055EF0A /* LaunchScreen.storyboard in Resources */, 216 | D80ECBBC1BDEAE9D0055EF0A /* Assets.xcassets in Resources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXResourcesBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | D80ECBA81BDEAE9D0055EF0A /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | D80ECBFB1BDEB0B10055EF0A /* DBCreateAccountViewController.m in Sources */, 228 | D80ECBF81BDEB0890055EF0A /* DBAppDelegate.m in Sources */, 229 | D80ECBB11BDEAE9D0055EF0A /* main.m in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | /* End PBXSourcesBuildPhase section */ 234 | 235 | /* Begin PBXTargetDependency section */ 236 | D8EF582C1BDEB1420014E84B /* PBXTargetDependency */ = { 237 | isa = PBXTargetDependency; 238 | name = Zxcvbn; 239 | targetProxy = D8EF582B1BDEB1420014E84B /* PBXContainerItemProxy */; 240 | }; 241 | /* End PBXTargetDependency section */ 242 | 243 | /* Begin PBXVariantGroup section */ 244 | 4CF9DAD01CD16B6F006A229D /* Main.storyboard */ = { 245 | isa = PBXVariantGroup; 246 | children = ( 247 | 4CF9DAD11CD16B6F006A229D /* Base */, 248 | ); 249 | name = Main.storyboard; 250 | sourceTree = ""; 251 | }; 252 | D80ECBBD1BDEAE9D0055EF0A /* LaunchScreen.storyboard */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | D80ECBBE1BDEAE9D0055EF0A /* Base */, 256 | ); 257 | name = LaunchScreen.storyboard; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXVariantGroup section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | D80ECBD71BDEAE9E0055EF0A /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_EMPTY_BODY = YES; 275 | CLANG_WARN_ENUM_CONVERSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 281 | COPY_PHASE_STRIP = NO; 282 | DEBUG_INFORMATION_FORMAT = dwarf; 283 | ENABLE_STRICT_OBJC_MSGSEND = YES; 284 | ENABLE_TESTABILITY = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu99; 286 | GCC_DYNAMIC_NO_PIC = NO; 287 | GCC_NO_COMMON_BLOCKS = YES; 288 | GCC_OPTIMIZATION_LEVEL = 0; 289 | GCC_PREPROCESSOR_DEFINITIONS = ( 290 | "DEBUG=1", 291 | "$(inherited)", 292 | ); 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 300 | MTL_ENABLE_DEBUG_INFO = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | }; 305 | name = Debug; 306 | }; 307 | D80ECBD81BDEAE9E0055EF0A /* Release */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 325 | COPY_PHASE_STRIP = NO; 326 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 327 | ENABLE_NS_ASSERTIONS = NO; 328 | ENABLE_STRICT_OBJC_MSGSEND = YES; 329 | GCC_C_LANGUAGE_STANDARD = gnu99; 330 | GCC_NO_COMMON_BLOCKS = YES; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_VARIABLE = YES; 337 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 338 | MTL_ENABLE_DEBUG_INFO = NO; 339 | SDKROOT = iphoneos; 340 | TARGETED_DEVICE_FAMILY = "1,2"; 341 | VALIDATE_PRODUCT = YES; 342 | }; 343 | name = Release; 344 | }; 345 | D80ECBDA1BDEAE9E0055EF0A /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | INFOPLIST_FILE = Example/Info.plist; 350 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 351 | PRODUCT_BUNDLE_IDENTIFIER = "com.dropbox.Zxcvbn.iOS-Example"; 352 | PRODUCT_NAME = "$(TARGET_NAME)"; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | D80ECBDB1BDEAE9E0055EF0A /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 361 | INFOPLIST_FILE = Example/Info.plist; 362 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 363 | PRODUCT_BUNDLE_IDENTIFIER = "com.dropbox.Zxcvbn.iOS-Example"; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | D80ECBA71BDEAE9D0055EF0A /* Build configuration list for PBXProject "iOS Example" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | D80ECBD71BDEAE9E0055EF0A /* Debug */, 376 | D80ECBD81BDEAE9E0055EF0A /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | D80ECBD91BDEAE9E0055EF0A /* Build configuration list for PBXNativeTarget "iOS Example" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | D80ECBDA1BDEAE9E0055EF0A /* Debug */, 385 | D80ECBDB1BDEAE9E0055EF0A /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = D80ECBA41BDEAE9D0055EF0A /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /iOS Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /zxcvbn-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dropbox/zxcvbn-ios/18876239f96aaecceea91051bbcc3b3161b19d20/zxcvbn-example.png -------------------------------------------------------------------------------- /zxcvbn-ios.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "zxcvbn-ios" 3 | s.version = "1.0.4" 4 | s.summary = "A realistic password strength estimator." 5 | s.description = <<-DESC 6 | An obj-c port of zxcvbn, a password strength estimation library, 7 | designed for iOS. 8 | 9 | DBZxcvbn attempts to give sound password advice through pattern 10 | matching and conservative entropy calculations. It finds 10k common 11 | passwords, common American names and surnames, common English words, 12 | and common patterns like dates, repeats (aaa), sequences (abcd), 13 | and QWERTY patterns. 14 | DESC 15 | s.homepage = "https://github.com/dropbox/zxcvbn-ios" 16 | s.screenshots = "https://raw.githubusercontent.com/dropbox/zxcvbn-ios/master/zxcvbn-example.png" 17 | s.license = "MIT" 18 | s.author = { "Leah Culver" => "leah@dropbox.com" } 19 | s.platform = :ios, "7.0" 20 | s.source = { :git => "https://github.com/dropbox/zxcvbn-ios.git", :tag => "v1.0.4"} 21 | s.source_files = "Zxcvbn/*.{h,m}" 22 | s.exclude_files = "Zxcvbn/Zxcvbn.h" 23 | s.resources = "Zxcvbn/generated/*.json" 24 | s.requires_arc = true 25 | end 26 | --------------------------------------------------------------------------------