├── .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 |
--------------------------------------------------------------------------------