├── .editorconfig
├── .gitattributes
├── .gitignore
├── GitVersion.yml
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── zxcvbn-core-list-builder
├── GlobalSuppressions.cs
├── ListBuilder.cs
├── Program.cs
├── RankDictionaryName.cs
└── zxcvbn-core-list-builder.csproj
├── zxcvbn-core-test
├── Core
│ └── CoreTests.cs
├── GlobalSuppressions.cs
├── Matcher
│ ├── DateMatcherTests.cs
│ ├── DictionaryMatcherTests.cs
│ ├── L33tMatcherTests.cs
│ ├── RegexMatcherTests.cs
│ ├── RepeatMatcherTests.cs
│ ├── ReverseDictionaryMatcherTests.cs
│ ├── SequenceMatcherTests.cs
│ └── SpatialMatcherTests.cs
├── Scoring
│ ├── BruteForceScoringTests.cs
│ ├── DateScoringTests.cs
│ ├── DictionaryScoringTests.cs
│ ├── RegexScoringTests.cs
│ ├── RepeatScoringTests.cs
│ ├── ScoringTests.cs
│ ├── SequenceScoringTests.cs
│ └── SpatialTests.cs
├── test_dictionary_1.txt
├── test_dictionary_2.txt
├── test_dictionary_3.txt
├── zxcvbn-core-test.csproj
└── zxcvbn-strong-name-key.snk
├── zxcvbn-core
├── AttackTimes.cs
├── Core.cs
├── CrackTimes.cs
├── CrackTimesDisplay.cs
├── Data
│ ├── english.lst
│ ├── female_names.lst
│ ├── male_names.lst
│ ├── passwords.lst
│ ├── surnames.lst
│ └── us_tv_and_film.lst
├── DefaultMatcherFactory.cs
├── Dictionaries
│ ├── english.lst
│ ├── female_names.lst
│ ├── male_names.lst
│ ├── passwords.lst
│ ├── surnames.lst
│ └── us_tv_and_film.lst
├── Feedback.cs
├── FeedbackItem.cs
├── GlobalSuppressions.cs
├── Matcher
│ ├── DateMatcher.cs
│ ├── DictionaryMatcher.cs
│ ├── IMatcher.cs
│ ├── L33tMatcher.cs
│ ├── LooseDate.cs
│ ├── Matches
│ │ ├── BruteForceMatch.cs
│ │ ├── DateMatch.cs
│ │ ├── DictionaryMatch.cs
│ │ ├── Match.cs
│ │ ├── RegexMatch.cs
│ │ ├── RepeatMatch.cs
│ │ ├── SequenceMatch.cs
│ │ └── SpatialMatch.cs
│ ├── Point.cs
│ ├── RegexMatcher.cs
│ ├── RepeatMatcher.cs
│ ├── ReverseDictionaryMatcher.cs
│ ├── SequenceMatcher.cs
│ ├── SpatialGraph.cs
│ └── SpatialMatcher.cs
├── MostGuessableMatchResult.cs
├── OptimalValues.cs
├── PasswordScoring.cs
├── Properties
│ └── AssemblyInfo.cs
├── Result.cs
├── Scoring
│ ├── BruteForceGuessesCalculator.cs
│ ├── DateGuessesCalculator.cs
│ ├── DictionaryGuessesCalculator.cs
│ ├── RegexGuessesCalculator.cs
│ ├── RepeatGuessesCalculator.cs
│ ├── SequenceGuessesCalculator.cs
│ └── SpatialGuessesCalculator.cs
├── TimeEstimates.cs
├── Utility.cs
├── zxcvbn-core.csproj
├── zxcvbn-core.xml
└── zxcvbn-strong-name-key.snk
└── zxcvbn-cs.sln
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\trich\Source\Repos\trichards57\zxcvbn-cs codebase based on best match to current usage at 21/01/2022
2 | # You can modify the rules from these initially generated values to suit your own policies
3 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
4 | [*.cs]
5 |
6 | #Core editorconfig formatting - indentation
7 |
8 | #use soft tabs (spaces) for indentation
9 | indent_style = space
10 |
11 | #Formatting - indentation options
12 |
13 | #indent switch case contents.
14 | csharp_indent_case_contents = true
15 | #indent switch labels
16 | csharp_indent_switch_labels = true
17 |
18 | #Formatting - new line options
19 |
20 | #place else statements on a new line
21 | csharp_new_line_before_else = true
22 | #require members of object intializers to be on separate lines
23 | csharp_new_line_before_members_in_object_initializers = true
24 | #require braces to be on a new line for object_collection_array_initializers, methods, control_blocks, types, and lambdas (also known as "Allman" style)
25 | csharp_new_line_before_open_brace = object_collection_array_initializers, methods, control_blocks, types, lambdas
26 |
27 | #Formatting - organize using options
28 |
29 | #sort System.* using directives alphabetically, and place them before other usings
30 | dotnet_sort_system_directives_first = true
31 |
32 | #Formatting - spacing options
33 |
34 | #require NO space between a cast and the value
35 | csharp_space_after_cast = false
36 | #require a space before the colon for bases or interfaces in a type declaration
37 | csharp_space_after_colon_in_inheritance_clause = true
38 | #require a space after a keyword in a control flow statement such as a for loop
39 | csharp_space_after_keywords_in_control_flow_statements = true
40 | #require a space before the colon for bases or interfaces in a type declaration
41 | csharp_space_before_colon_in_inheritance_clause = true
42 | #remove space within empty argument list parentheses
43 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
44 | #remove space between method call name and opening parenthesis
45 | csharp_space_between_method_call_name_and_opening_parenthesis = false
46 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
47 | csharp_space_between_method_call_parameter_list_parentheses = false
48 | #remove space within empty parameter list parentheses for a method declaration
49 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
50 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
51 | csharp_space_between_method_declaration_parameter_list_parentheses = false
52 |
53 | #Formatting - wrapping options
54 |
55 | #leave code block on single line
56 | csharp_preserve_single_line_blocks = true
57 | #leave statements and member declarations on the same line
58 | csharp_preserve_single_line_statements = true
59 |
60 | #Style - Code block preferences
61 |
62 | #prefer no curly braces if allowed
63 | csharp_prefer_braces = false:suggestion
64 |
65 | #Style - expression bodied member options
66 |
67 | #prefer block bodies for constructors
68 | csharp_style_expression_bodied_constructors = false:suggestion
69 | #prefer block bodies for methods
70 | csharp_style_expression_bodied_methods = false:suggestion
71 |
72 | #Style - expression level options
73 |
74 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
75 | dotnet_style_predefined_type_for_member_access = true:suggestion
76 |
77 | #Style - Expression-level preferences
78 |
79 | #prefer objects to be initialized using object initializers when possible
80 | dotnet_style_object_initializer = true:suggestion
81 |
82 | #Style - implicit and explicit types
83 |
84 | #prefer var over explicit type in all cases, unless overridden by another code style rule
85 | csharp_style_var_elsewhere = true:suggestion
86 | #prefer var is used to declare variables with built-in system types such as int
87 | csharp_style_var_for_built_in_types = true:suggestion
88 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression
89 | csharp_style_var_when_type_is_apparent = true:suggestion
90 |
91 | #Style - language keyword and framework type options
92 |
93 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
94 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
95 |
96 | #Style - Miscellaneous preferences
97 |
98 | #prefer local functions over anonymous functions
99 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
100 |
101 | #Style - modifier options
102 |
103 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
104 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
105 |
106 | #Style - Modifier preferences
107 |
108 | #when this rule is set to a list of modifiers, prefer the specified ordering.
109 | csharp_preferred_modifier_order = public,private,internal,static,readonly,virtual:suggestion
110 |
111 | #Style - qualification options
112 |
113 | #prefer fields not to be prefaced with this. or Me. in Visual Basic
114 | dotnet_style_qualification_for_field = false:suggestion
115 | #prefer methods not to be prefaced with this. or Me. in Visual Basic
116 | dotnet_style_qualification_for_method = false:suggestion
117 | #prefer properties not to be prefaced with this. or Me. in Visual Basic
118 | dotnet_style_qualification_for_property = false:suggestion
119 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.pch
68 | *.pdb
69 | *.pgc
70 | *.pgd
71 | *.rsp
72 | *.sbr
73 | *.tlb
74 | *.tli
75 | *.tlh
76 | *.tmp
77 | *.tmp_proj
78 | *.log
79 | *.vspscc
80 | *.vssscc
81 | .builds
82 | *.pidb
83 | *.svclog
84 | *.scc
85 |
86 | # Chutzpah Test files
87 | _Chutzpah*
88 |
89 | # Visual C++ cache files
90 | ipch/
91 | *.aps
92 | *.ncb
93 | *.opendb
94 | *.opensdf
95 | *.sdf
96 | *.cachefile
97 | *.VC.db
98 | *.VC.VC.opendb
99 |
100 | # Visual Studio profiler
101 | *.psess
102 | *.vsp
103 | *.vspx
104 | *.sap
105 |
106 | # Visual Studio Trace Files
107 | *.e2e
108 |
109 | # TFS 2012 Local Workspace
110 | $tf/
111 |
112 | # Guidance Automation Toolkit
113 | *.gpState
114 |
115 | # ReSharper is a .NET coding add-in
116 | _ReSharper*/
117 | *.[Rr]e[Ss]harper
118 | *.DotSettings.user
119 |
120 | # JustCode is a .NET coding add-in
121 | .JustCode
122 |
123 | # TeamCity is a build add-in
124 | _TeamCity*
125 |
126 | # DotCover is a Code Coverage Tool
127 | *.dotCover
128 |
129 | # AxoCover is a Code Coverage Tool
130 | .axoCover/*
131 | !.axoCover/settings.json
132 |
133 | # Visual Studio code coverage results
134 | *.coverage
135 | *.coveragexml
136 |
137 | # NCrunch
138 | _NCrunch_*
139 | .*crunch*.local.xml
140 | nCrunchTemp_*
141 |
142 | # MightyMoose
143 | *.mm.*
144 | AutoTest.Net/
145 |
146 | # Web workbench (sass)
147 | .sass-cache/
148 |
149 | # Installshield output folder
150 | [Ee]xpress/
151 |
152 | # DocProject is a documentation generator add-in
153 | DocProject/buildhelp/
154 | DocProject/Help/*.HxT
155 | DocProject/Help/*.HxC
156 | DocProject/Help/*.hhc
157 | DocProject/Help/*.hhk
158 | DocProject/Help/*.hhp
159 | DocProject/Help/Html2
160 | DocProject/Help/html
161 |
162 | # Click-Once directory
163 | publish/
164 |
165 | # Publish Web Output
166 | *.[Pp]ublish.xml
167 | *.azurePubxml
168 | # Note: Comment the next line if you want to checkin your web deploy settings,
169 | # but database connection strings (with potential passwords) will be unencrypted
170 | *.pubxml
171 | *.publishproj
172 |
173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
174 | # checkin your Azure Web App publish settings, but sensitive information contained
175 | # in these scripts will be unencrypted
176 | PublishScripts/
177 |
178 | # NuGet Packages
179 | *.nupkg
180 | # The packages folder can be ignored because of Package Restore
181 | **/[Pp]ackages/*
182 | # except build/, which is used as an MSBuild target.
183 | !**/[Pp]ackages/build/
184 | # Uncomment if necessary however generally it will be regenerated when needed
185 | #!**/[Pp]ackages/repositories.config
186 | # NuGet v3's project.json files produces more ignorable files
187 | *.nuget.props
188 | *.nuget.targets
189 |
190 | # Microsoft Azure Build Output
191 | csx/
192 | *.build.csdef
193 |
194 | # Microsoft Azure Emulator
195 | ecf/
196 | rcf/
197 |
198 | # Windows Store app package directories and files
199 | AppPackages/
200 | BundleArtifacts/
201 | Package.StoreAssociation.xml
202 | _pkginfo.txt
203 | *.appx
204 |
205 | # Visual Studio cache files
206 | # files ending in .cache can be ignored
207 | *.[Cc]ache
208 | # but keep track of directories ending in .cache
209 | !*.[Cc]ache/
210 |
211 | # Others
212 | ClientBin/
213 | ~$*
214 | *~
215 | *.dbmdl
216 | *.dbproj.schemaview
217 | *.jfm
218 | *.pfx
219 | *.publishsettings
220 | orleans.codegen.cs
221 |
222 | # Including strong name files can present a security risk
223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
224 | #*.snk
225 |
226 | # Since there are multiple workflows, uncomment next line to ignore bower_components
227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
228 | #bower_components/
229 |
230 | # RIA/Silverlight projects
231 | Generated_Code/
232 |
233 | # Backup & report files from converting an old project file
234 | # to a newer Visual Studio version. Backup files are not needed,
235 | # because we have git ;-)
236 | _UpgradeReport_Files/
237 | Backup*/
238 | UpgradeLog*.XML
239 | UpgradeLog*.htm
240 | ServiceFabricBackup/
241 |
242 | # SQL Server files
243 | *.mdf
244 | *.ldf
245 | *.ndf
246 |
247 | # Business Intelligence projects
248 | *.rdl.data
249 | *.bim.layout
250 | *.bim_*.settings
251 |
252 | # Microsoft Fakes
253 | FakesAssemblies/
254 |
255 | # GhostDoc plugin setting file
256 | *.GhostDoc.xml
257 |
258 | # Node.js Tools for Visual Studio
259 | .ntvs_analysis.dat
260 | node_modules/
261 |
262 | # TypeScript v1 declaration files
263 | typings/
264 |
265 | # Visual Studio 6 build log
266 | *.plg
267 |
268 | # Visual Studio 6 workspace options file
269 | *.opt
270 |
271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
272 | *.vbw
273 |
274 | # Visual Studio LightSwitch build output
275 | **/*.HTMLClient/GeneratedArtifacts
276 | **/*.DesktopClient/GeneratedArtifacts
277 | **/*.DesktopClient/ModelManifest.xml
278 | **/*.Server/GeneratedArtifacts
279 | **/*.Server/ModelManifest.xml
280 | _Pvt_Extensions
281 |
282 | # Paket dependency manager
283 | .paket/paket.exe
284 | paket-files/
285 |
286 | # FAKE - F# Make
287 | .fake/
288 |
289 | # JetBrains Rider
290 | .idea/
291 | *.sln.iml
292 |
293 | # CodeRush
294 | .cr/
295 |
296 | # Python Tools for Visual Studio (PTVS)
297 | __pycache__/
298 | *.pyc
299 |
300 | # Cake - Uncomment if you are using it
301 | # tools/**
302 | # !tools/packages.config
303 |
304 | # Tabs Studio
305 | *.tss
306 |
307 | # Telerik's JustMock configuration file
308 | *.jmconfig
309 |
310 | # BizTalk build output
311 | *.btp.cs
312 | *.btm.cs
313 | *.odx.cs
314 | *.xsd.cs
315 |
316 | # OpenCover UI analysis results
317 | OpenCover/
318 |
319 | # Azure Stream Analytics local run output
320 | ASALocalRun/
321 |
322 | # MSBuild Binary and Structured Log
323 | *.binlog
324 |
325 | /reports
326 | /tools
327 | /nuget.exe
328 |
--------------------------------------------------------------------------------
/GitVersion.yml:
--------------------------------------------------------------------------------
1 | next-version: 8.0
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Dropbox, Inc.
2 | Copyright (c) 2020 Tony Richards
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Zxcvbn C#/.NET
2 | ==============
3 |
4 | [](https://ci.appveyor.com/project/trichards57/zxcvbn-cs-62692)
5 | [](https://coveralls.io/github/trichards57/zxcvbn-cs)
6 | [](https://www.nuget.org/packages/zxcvbn-core)
7 |
8 | This is a port of the `Zxcvbn` JavaScript password strength estimation library at
9 | https://github.com/lowe/zxcvbn to .NET, written in C#. This fork moves the library
10 | to support .Net Core.
11 |
12 | From the `Zxcvbn` readme:
13 |
14 | > `zxcvbn`, named after a crappy password, is a JavaScript password strength
15 | > estimation library. Use it to implement a custom strength bar on a
16 | > signup form near you!
17 | >
18 | > `zxcvbn` attempts to give sound password advice through pattern matching
19 | > and conservative entropy calculations. It finds 10k common passwords,
20 | > common American names and surnames, common English words, and common
21 | > patterns like dates, repeats (aaa), sequences (abcd), and QWERTY
22 | > patterns.
23 | >
24 | > For full motivation, see:
25 | >
26 | > http://tech.dropbox.com/?p=165
27 |
28 | This port aims to produce comparable results with the Typescript version of `Zxcvbn` which I have also put out and is here https://github.com/trichards57/zxcvbn.
29 | The results structure that is returned can be interpreted in the same way as with JS `Zxcvbn` and this port has been tested with a variety of passwords to ensure
30 | that it return the same score as the JS version (some other details vary a little).
31 |
32 | I have tried to keep the implementation as close as possible, but there is still a chance of some small changes. Let me know if you find any differences
33 | and I can investigate.
34 |
35 | ### Using `Zxcvbn-cs`
36 |
37 | The included Visual Studio project will create a single assembly, Zxcvbn.dll, which is all that is
38 | required to be included in your project.
39 |
40 | To evaluate a password:
41 |
42 | ``` C#
43 | using Zxcvbn;
44 |
45 | //...
46 |
47 | var result = Zxcvbn.Core.EvaluatePassword("p@ssw0rd");
48 | ```
49 |
50 | `EvaluatePassword` takes an optional second parameter that contains an enumerable of
51 | user data strings to also match the password against.
52 |
53 | ### Interpreting Results
54 |
55 | The `Result` structure returned from password evaluation is interpreted the same way as with JS `Zxcvbn`.
56 |
57 | - `result.Score`: 0-4 indicating the estimated strength of the password.
58 |
59 | ### Licence
60 |
61 | Since `Zxcvbn-cs` is a port of the original `Zxcvbn` the original copyright and licensing applies. Cf. the LICENSE file.
62 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - feature/*
4 | - release/*
5 |
6 | pool:
7 | vmImage: 'windows-latest'
8 |
9 | variables:
10 | solution: '**/*.sln'
11 | buildPlatform: 'Any CPU'
12 | buildConfiguration: 'Release'
13 | GitVersion.SemVer: ''
14 |
15 | steps:
16 |
17 | - task: gitversion/setup@0
18 | inputs:
19 | versionSpec: '5.x'
20 |
21 | - task: gitversion/execute@0
22 |
23 | - task: DotNetCoreCLI@2
24 | inputs:
25 | command: 'restore'
26 | projects: '**/*.csproj'
27 |
28 | - task: DotNetCoreCLI@2
29 | inputs:
30 | command: 'build'
31 | arguments: '-p:Version=$(GitVersion.SemVer) -c Release'
32 |
33 | - task: DotNetCoreCLI@2
34 | inputs:
35 | command: 'pack'
36 | packagesToPack: '**/*.csproj'
37 | includesymbols: true
38 | includesource: true
39 | versioningScheme: 'byEnvVar'
40 | versionEnvVar: 'GitVersion.NuGetVersion'
41 | nobuild: true
42 |
43 | - task: DotNetCoreCLI@2
44 | inputs:
45 | command: 'test'
46 | nobuild: true
47 | arguments: '-c Release'
48 |
49 | - task: PublishPipelineArtifact@1
50 | inputs:
51 | targetPath: '$(Build.ArtifactStagingDirectory)'
52 | artifact: 'drop'
53 | publishLocation: 'pipeline'
54 |
--------------------------------------------------------------------------------
/zxcvbn-core-list-builder/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "To match this project's coding style.")]
9 | [assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:Using directives should be placed correctly", Justification = "To match this project's coding style.")]
10 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "To match this project's coding style.")]
11 | [assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "This normalization isn't security critical.")]
12 | [assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1208:System using directives should be placed before other using directives", Justification = "Using CodeMaid for this task.")]
13 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This isn't actually publicly released.")]
14 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "No header required.")]
15 |
--------------------------------------------------------------------------------
/zxcvbn-core-list-builder/ListBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.IO;
5 | using System.Linq;
6 |
7 | namespace Zxcvbn.ListBuilder
8 | {
9 | internal static class ListBuilder
10 | {
11 | ///
12 | /// Returns the maximum number of words for a dictionary.
13 | /// null means take everything.
14 | ///
15 | private static readonly ReadOnlyDictionary DictionaryLimits = new ReadOnlyDictionary(new Dictionary
16 | {
17 | { "us_tv_and_film", 30000 },
18 | { "english", 30000 },
19 | { "passwords", 30000 },
20 | { "surnames", 10000 },
21 | { "male_names", null },
22 | { "female_names", null },
23 | });
24 |
25 | ///
26 | /// Filters the frequency lists to remove:
27 | /// - Tokens which are short and rare
28 | /// - Tokens that are already in another dictionary and a lower rank
29 | /// - Tokens that end up beyond the end of the limits in .
30 | ///
31 | /// The list to filter.
32 | ///
33 | /// The filtered output.
34 | ///
35 | public static Dictionary> FilterFrequencyLists(Dictionary> frequencyLists)
36 | {
37 | var filteredTokenAndRank = new Dictionary>();
38 | var tokenCounts = new Dictionary();
39 |
40 | foreach (var name in frequencyLists.Keys)
41 | {
42 | filteredTokenAndRank[name] = new Dictionary();
43 | tokenCounts[name] = 0;
44 | }
45 |
46 | var minimumValues = new Dictionary();
47 |
48 | foreach (var kvp in frequencyLists)
49 | {
50 | var name = kvp.Key;
51 | foreach (var tvp in kvp.Value)
52 | {
53 | var token = tvp.Key;
54 | var rank = tvp.Value;
55 |
56 | if (!minimumValues.ContainsKey(token))
57 | {
58 | minimumValues[token] = new RankDictionaryName
59 | {
60 | Name = name,
61 | Rank = rank,
62 | };
63 | }
64 | else
65 | {
66 | if (rank < minimumValues[token].Rank)
67 | {
68 | minimumValues[token] = new RankDictionaryName
69 | {
70 | Rank = rank,
71 | Name = name,
72 | };
73 | }
74 | }
75 | }
76 | }
77 |
78 | foreach (var kvp in frequencyLists)
79 | {
80 | var name = kvp.Key;
81 | foreach (var tvp in kvp.Value)
82 | {
83 | var token = tvp.Key;
84 | var rank = tvp.Value;
85 |
86 | if (token == "o")
87 | {
88 | Console.Write(string.Empty);
89 | }
90 |
91 | if (minimumValues[token].Name != name)
92 | continue;
93 | if (IsRareAndShort(token, rank) || HasCommaOrDoubleQuote(token))
94 | continue;
95 | filteredTokenAndRank[name][token] = rank;
96 | tokenCounts[name]++;
97 | }
98 | }
99 |
100 | var result = new Dictionary>();
101 |
102 | foreach (var kvp in filteredTokenAndRank)
103 | {
104 | var name = kvp.Key;
105 | var values = kvp.Value;
106 | var res = values.OrderBy(s => s.Value).Select(kvp => kvp.Key);
107 | if (DictionaryLimits[name].HasValue)
108 | res = res.Take(DictionaryLimits[name].Value);
109 | result[name] = res.ToList();
110 | }
111 |
112 | return result;
113 | }
114 |
115 | public static Dictionary> ParseFrequencyLists(string directory)
116 | {
117 | var result = new Dictionary>();
118 |
119 | foreach (var file in Directory.GetFiles(directory))
120 | {
121 | var name = Path.GetFileNameWithoutExtension(file);
122 | if (!DictionaryLimits.ContainsKey(name))
123 | {
124 | Console.WriteLine($"Warning: {name} is in directory {directory} but was not expected. Skipped.");
125 | continue;
126 | }
127 |
128 | var tokenToRank = new Dictionary();
129 | var rank = 0;
130 |
131 | foreach (var line in File.ReadAllLines(file).Where(l => !string.IsNullOrWhiteSpace(l)))
132 | {
133 | rank++;
134 | var token = line.Split(" ")[0];
135 | tokenToRank[token] = rank;
136 | }
137 |
138 | result[name] = tokenToRank;
139 | }
140 |
141 | foreach (var expectedKey in DictionaryLimits.Keys)
142 | {
143 | if (!result.ContainsKey(expectedKey))
144 | {
145 | Console.WriteLine($"Warning: {expectedKey} is not in directory {directory} but was expected.");
146 | }
147 | }
148 |
149 | return result;
150 | }
151 |
152 | private static bool HasCommaOrDoubleQuote(string token)
153 | {
154 | return token.Contains(",") || token.Contains("\"");
155 | }
156 |
157 | private static bool IsRareAndShort(string token, int rank)
158 | {
159 | return rank >= Math.Pow(10, token.Length);
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/zxcvbn-core-list-builder/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 |
5 | namespace Zxcvbn.ListBuilder
6 | {
7 | internal class Program
8 | {
9 | private static void Main(string[] args)
10 | {
11 | if (args.Length != 2)
12 | {
13 | PrintUsage(Assembly.GetExecutingAssembly().GetName().Name);
14 | return;
15 | }
16 |
17 | var dataDir = args[0];
18 | var outputDir = args[1];
19 |
20 | var unfilteredLists = ListBuilder.ParseFrequencyLists(dataDir);
21 | var filteredLists = ListBuilder.FilterFrequencyLists(unfilteredLists);
22 |
23 | foreach (var kvp in filteredLists)
24 | {
25 | var name = kvp.Key;
26 | var list = kvp.Value;
27 |
28 | File.WriteAllLines(Path.Combine(outputDir, $"{name}.lst"), list);
29 | }
30 | }
31 |
32 | private static void PrintUsage(string name)
33 | {
34 | Console.WriteLine("Usage:");
35 | Console.WriteLine($" {name} dataDirectory outputDirectory");
36 | Console.WriteLine();
37 | Console.WriteLine("Converts the raw data used by zxcvbn's build_frequency_lists scripts into input files for embedding into the assembly.");
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/zxcvbn-core-list-builder/RankDictionaryName.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.ListBuilder
2 | {
3 | internal struct RankDictionaryName
4 | {
5 | public string Name { get; set; }
6 |
7 | public int Rank { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/zxcvbn-core-list-builder/zxcvbn-core-list-builder.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | Zxcvbn.ListBuilder
7 | false
8 | False
9 |
10 |
11 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Core/CoreTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 |
4 | namespace Zxcvbn.Tests.Core
5 | {
6 | public class CoreTests
7 | {
8 | [Fact]
9 | public void GoodScoreShallHaveNoSuggestions()
10 | {
11 | var result = Zxcvbn.Core.EvaluatePassword("turtledogspicemagic");
12 |
13 | result.Score.Should().BeGreaterOrEqualTo(3);
14 | result.Feedback.Suggestions.Count.Should().Be(0);
15 | }
16 |
17 | [Fact]
18 | public void GoodScoreShallHaveNoWarning()
19 | {
20 | var result = Zxcvbn.Core.EvaluatePassword("turtleturtledogspicemagic");
21 | result.Score.Should().BeGreaterOrEqualTo(3);
22 | result.Feedback.Warning.Should().Be(string.Empty);
23 | }
24 |
25 | [Fact]
26 | public void EmptyPasswordShallHaveZeroScore()
27 | {
28 | var result = Zxcvbn.Core.EvaluatePassword(string.Empty);
29 | result.Score.Should().Be(0);
30 | }
31 |
32 | [Fact]
33 | public void EmptyPasswordShallYieldDefaultSuggestions()
34 | {
35 | var result = Zxcvbn.Core.EvaluatePassword(string.Empty);
36 | var defaultFeedback = new[]
37 | {
38 | "Use a few words, avoid common phrases",
39 | "No need for symbols, digits, or uppercase letters",
40 | };
41 | result.Feedback.Suggestions.Should().BeEquivalentTo(defaultFeedback);
42 | }
43 |
44 | [Fact]
45 | public void EnglishNounsShallBeRecognisedAsSuch()
46 | {
47 | var result = Zxcvbn.Core.EvaluatePassword("geologic");
48 | var warning = "A word by itself is easy to guess";
49 | result.Feedback.Warning.Should().BeEquivalentTo(warning);
50 | }
51 |
52 | [Fact]
53 | public void SurnamesShallBeRecognisedAsSuch()
54 | {
55 | var result = Zxcvbn.Core.EvaluatePassword("grajeda");
56 | var warning = "Names and surnames by themselves are easy to guess";
57 | result.Feedback.Warning.Should().BeEquivalentTo(warning);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 | using Xunit;
8 |
9 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "To match this project's coding style.")]
10 | [assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:Using directives should be placed correctly", Justification = "To match this project's coding style.")]
11 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "To match this project's coding style.")]
12 | [assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "This normalization isn't security critical.")]
13 | [assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1208:System using directives should be placed before other using directives", Justification = "Using CodeMaid for this task.")]
14 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This isn't actually publicly released.")]
15 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "No header required.")]
16 | [assembly: CollectionBehavior(DisableTestParallelization = true)]
17 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/DateMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Linq;
3 | using Xunit;
4 | using Zxcvbn.Matcher;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Matcher
8 | {
9 | public class DateMatcherTests
10 | {
11 | [Fact]
12 | public void DateMatcherMatchesDatesPaddedByNonAmbiguousDigits()
13 | {
14 | const string password = "912/20/919";
15 |
16 | var expected = new[]
17 | {
18 | new DateMatch
19 | {
20 | i = 1,
21 | j = 8,
22 | Token = "12/20/91",
23 | Day = 20,
24 | Month = 12,
25 | Year = 1991,
26 | Separator = "/",
27 | },
28 | };
29 |
30 | var matcher = new DateMatcher();
31 |
32 | var matches = matcher.MatchPassword(password).ToList();
33 |
34 | matches.Should().BeEquivalentTo(expected);
35 | }
36 |
37 | [Theory(DisplayName = "DateMatcher.DifferentSeperator")]
38 | [InlineData("")]
39 | [InlineData(" ")]
40 | [InlineData("-")]
41 | [InlineData("/")]
42 | [InlineData("\\")]
43 | [InlineData("_")]
44 | [InlineData(".")]
45 | public void DateMatcherMatchesDatesWithVariedSeperators(string seperator)
46 | {
47 | var password = $"13{seperator}2{seperator}1921";
48 |
49 | var expected = new[]
50 | {
51 | new DateMatch
52 | {
53 | i = 0,
54 | j = password.Length - 1,
55 | Token = password,
56 | Day = 13,
57 | Month = 2,
58 | Year = 1921,
59 | Separator = seperator,
60 | },
61 | };
62 |
63 | var matcher = new DateMatcher();
64 |
65 | var matches = matcher.MatchPassword(password).ToList();
66 |
67 | matches.Should().BeEquivalentTo(expected);
68 | }
69 |
70 | [Theory(DisplayName = "DateMatcher.DifferentOrders")]
71 | [InlineData("mdy")]
72 | [InlineData("dmy")]
73 | [InlineData("ymd")]
74 | [InlineData("ydm")]
75 | public void DateMatcherMatchesDateWithVariedElementOrder(string order)
76 | {
77 | const int day = 22;
78 | const int month = 12;
79 | const int year = 1935;
80 |
81 | var password = order.Replace("d", day.ToString())
82 | .Replace("m", month.ToString()).Replace("y", year.ToString());
83 |
84 | var expected = new[]
85 | {
86 | new DateMatch
87 | {
88 | i = 0,
89 | j = password.Length - 1,
90 | Token = password,
91 | Day = day,
92 | Month = month,
93 | Year = year,
94 | Separator = string.Empty,
95 | },
96 | };
97 |
98 | var matcher = new DateMatcher();
99 |
100 | var matches = matcher.MatchPassword(password).ToList();
101 |
102 | matches.Should().BeEquivalentTo(expected);
103 | }
104 |
105 | [Theory(DisplayName = "DateMatcher.PrefixSuffix")]
106 | [InlineData("a", "")]
107 | [InlineData("ab", "")]
108 | [InlineData("", "!")]
109 | [InlineData("a", "!")]
110 | [InlineData("ab", "!")]
111 | public void DateMatcherMatchesEmbeddedDates(string prefix, string suffix)
112 | {
113 | var pattern = "1/1/91";
114 | var password = $"{prefix}{pattern}{suffix}";
115 |
116 | var expected = new[]
117 | {
118 | new DateMatch
119 | {
120 | i = prefix.Length,
121 | j = prefix.Length + pattern.Length - 1,
122 | Token = "1/1/91",
123 | Day = 1,
124 | Month = 1,
125 | Year = 1991,
126 | Separator = "/",
127 | },
128 | };
129 |
130 | var matcher = new DateMatcher();
131 |
132 | var matches = matcher.MatchPassword(password).ToList();
133 |
134 | matches.Should().BeEquivalentTo(expected);
135 | }
136 |
137 | [Fact]
138 | public void DateMatcherMatchesOverlappingDates()
139 | {
140 | const string password = "12/20/1991.12.20";
141 |
142 | var expected = new[]
143 | {
144 | new DateMatch
145 | {
146 | i = 0,
147 | j = 9,
148 | Token = "12/20/1991",
149 | Day = 20,
150 | Month = 12,
151 | Year = 1991,
152 | Separator = "/",
153 | },
154 | new DateMatch
155 | {
156 | i = 6,
157 | j = 15,
158 | Token = "1991.12.20",
159 | Day = 20,
160 | Month = 12,
161 | Year = 1991,
162 | Separator = ".",
163 | },
164 | };
165 |
166 | var matcher = new DateMatcher();
167 |
168 | var matches = matcher.MatchPassword(password).ToList();
169 |
170 | matches.Should().BeEquivalentTo(expected);
171 | }
172 |
173 | [Fact]
174 | public void DateMatcherMatchesTheDateWithYearClosestToReferenceYear()
175 | {
176 | const string password = "111504";
177 |
178 | var expected = new[]
179 | {
180 | new DateMatch
181 | {
182 | i = 0,
183 | j = password.Length - 1,
184 | Token = password,
185 | Day = 15,
186 | Month = 11,
187 | Year = 2004,
188 | Separator = string.Empty,
189 | },
190 | };
191 |
192 | var matcher = new DateMatcher();
193 |
194 | var matches = matcher.MatchPassword(password).ToList();
195 | matches.Should().BeEquivalentTo(expected);
196 | }
197 |
198 | [Theory]
199 | [InlineData(1, 1, 1999)]
200 | [InlineData(11, 8, 2000)]
201 | [InlineData(9, 12, 2005)]
202 | [InlineData(22, 11, 1551)]
203 | public void DateMatcherMatchesVariousDates(int day, int month, int year)
204 | {
205 | var password = $"{year}{month}{day}";
206 |
207 | var matcher = new DateMatcher();
208 |
209 | var matches = matcher.MatchPassword(password).ToList();
210 |
211 | matches.Should().HaveCountGreaterOrEqualTo(1);
212 | matches.Count(m => m is DateMatch).Should().Be(1);
213 |
214 | var match = matches.OfType().Single();
215 | match.Pattern.Should().Be("date");
216 | match.Token.Should().Be(password);
217 | match.i.Should().Be(0);
218 | match.j.Should().Be(password.Length - 1);
219 | match.Separator.Should().Be(string.Empty);
220 | match.Year.Should().Be(year);
221 |
222 | var dottedPassword = $"{year}.{month}.{day}";
223 |
224 | matches = matcher.MatchPassword(dottedPassword).ToList();
225 |
226 | matches.Should().HaveCountGreaterOrEqualTo(1);
227 | matches.Count(m => m is DateMatch).Should().Be(1);
228 |
229 | match = matches.OfType().Single();
230 | match.Pattern.Should().Be("date");
231 | match.Token.Should().Be(dottedPassword);
232 | match.i.Should().Be(0);
233 | match.j.Should().Be(dottedPassword.Length - 1);
234 | match.Separator.Should().Be(".");
235 | match.Year.Should().Be(year);
236 | }
237 |
238 | [Fact]
239 | public void DateMatchesMatchesZeroPaddedDates()
240 | {
241 | const string password = "02/02/02";
242 |
243 | var expected = new[]
244 | {
245 | new DateMatch
246 | {
247 | i = 0,
248 | j = password.Length - 1,
249 | Token = password,
250 | Day = 2,
251 | Month = 2,
252 | Year = 2002,
253 | Separator = "/",
254 | },
255 | };
256 |
257 | var matcher = new DateMatcher();
258 |
259 | var matches = matcher.MatchPassword(password).ToList();
260 |
261 | matches.Should().BeEquivalentTo(expected);
262 | }
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/DictionaryMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Xunit;
5 | using Zxcvbn.Matcher;
6 | using Zxcvbn.Matcher.Matches;
7 |
8 | namespace Zxcvbn.Tests.Matcher
9 | {
10 | public class DictionaryMatcherTests
11 | {
12 | private readonly DictionaryMatcher matcher1 = new DictionaryMatcher("d1", "test_dictionary_1.txt");
13 | private readonly DictionaryMatcher matcher2 = new DictionaryMatcher("d2", "test_dictionary_2.txt");
14 | private readonly DictionaryMatcher matcherTv = new DictionaryMatcher("us_tv_and_film", "us_tv_and_film.lst");
15 |
16 | [Theory]
17 | [InlineData("q", "%")]
18 | [InlineData("q", "qq")]
19 | [InlineData("%%", "%")]
20 | [InlineData("%%", "qq")]
21 | public void IdentifiesWordsSurroundedByNonWords(string prefix, string suffix)
22 | {
23 | var word = "asdf1234&*";
24 | var password = $"{prefix}{word}{suffix}";
25 | var result = RunMatches(password);
26 |
27 | var expected = new[]
28 | {
29 | new DictionaryMatch
30 | {
31 | DictionaryName = "d2",
32 | i = prefix.Length,
33 | j = prefix.Length + word.Length - 1,
34 | MatchedWord = word,
35 | Rank = 5,
36 | Reversed = false,
37 | L33t = false,
38 | Token = word,
39 | },
40 | };
41 | result.Should().BeEquivalentTo(expected);
42 | }
43 |
44 | [Fact]
45 | public void IgnoresUppercasing()
46 | {
47 | var result = RunMatches("BoaRdZ");
48 |
49 | var expected = new[]
50 | {
51 | new DictionaryMatch
52 | {
53 | DictionaryName = "d1",
54 | i = 0,
55 | j = 4,
56 | MatchedWord = "board",
57 | Rank = 3,
58 | Reversed = false,
59 | L33t = false,
60 | Token = "BoaRd",
61 | },
62 | new DictionaryMatch
63 | {
64 | DictionaryName = "d2",
65 | i = 5,
66 | j = 5,
67 | MatchedWord = "z",
68 | Rank = 1,
69 | Reversed = false,
70 | L33t = false,
71 | Token = "Z",
72 | },
73 | };
74 | result.Should().BeEquivalentTo(expected);
75 | }
76 |
77 | [Theory]
78 | [InlineData("mother", 2, "d1")]
79 | [InlineData("board", 3, "d1")]
80 | [InlineData("abcd", 4, "d1")]
81 | [InlineData("cdef", 5, "d1")]
82 | [InlineData("z", 1, "d2")]
83 | [InlineData("8", 2, "d2")]
84 | [InlineData("99", 3, "d2")]
85 | [InlineData("$", 4, "d2")]
86 | [InlineData("asdf1234&*", 5, "d2")]
87 | public void MatchesAgainstAllWordsInProvidedDictionaries(string word, int rank, string dictionary)
88 | {
89 | var result = RunMatches(word);
90 |
91 | var expected = new[]
92 | {
93 | new DictionaryMatch
94 | {
95 | DictionaryName = dictionary,
96 | i = 0,
97 | j = word.Length - 1,
98 | MatchedWord = word,
99 | Rank = rank,
100 | Reversed = false,
101 | L33t = false,
102 | Token = word,
103 | },
104 | };
105 | result.Should().BeEquivalentTo(expected);
106 | }
107 |
108 | [Fact]
109 | public void MatchesMultipleOverlappingWords()
110 | {
111 | var result = RunMatches("abcdef");
112 |
113 | var expected = new[]
114 | {
115 | new DictionaryMatch
116 | {
117 | DictionaryName = "d1",
118 | i = 0,
119 | j = 3,
120 | MatchedWord = "abcd",
121 | Rank = 4,
122 | Reversed = false,
123 | L33t = false,
124 | Token = "abcd",
125 | },
126 | new DictionaryMatch
127 | {
128 | DictionaryName = "d1",
129 | i = 2,
130 | j = 5,
131 | MatchedWord = "cdef",
132 | Rank = 5,
133 | Reversed = false,
134 | L33t = false,
135 | Token = "cdef",
136 | },
137 | };
138 | result.Should().BeEquivalentTo(expected);
139 | }
140 |
141 | [Fact]
142 | public void MatchesWithProvidedUserInputDictionary()
143 | {
144 | var matcher = new DictionaryMatcher("user", new[] { "foo", "bar" });
145 | var result = matcher.MatchPassword("foobar").OfType().Where(m => m.DictionaryName == "user").ToList();
146 |
147 | result.Should().HaveCount(2);
148 |
149 | result[0].Token.Should().Be("foo");
150 | result[0].MatchedWord.Should().Be("foo");
151 | result[0].Rank.Should().Be(1);
152 | result[0].i.Should().Be(0);
153 | result[0].j.Should().Be(2);
154 |
155 | result[1].Token.Should().Be("bar");
156 | result[1].MatchedWord.Should().Be("bar");
157 | result[1].Rank.Should().Be(2);
158 | result[1].i.Should().Be(3);
159 | result[1].j.Should().Be(5);
160 | }
161 |
162 | [Fact]
163 | public void MatchesWordsThatContainOtherWords()
164 | {
165 | var result = RunMatches("motherboard");
166 |
167 | var expected = new[]
168 | {
169 | new DictionaryMatch
170 | {
171 | DictionaryName = "d1",
172 | i = 0,
173 | j = 5,
174 | MatchedWord = "mother",
175 | Rank = 2,
176 | Reversed = false,
177 | L33t = false,
178 | Token = "mother",
179 | },
180 | new DictionaryMatch
181 | {
182 | DictionaryName = "d1",
183 | i = 0,
184 | j = 10,
185 | MatchedWord = "motherboard",
186 | Rank = 1,
187 | Reversed = false,
188 | L33t = false,
189 | Token = "motherboard",
190 | },
191 | new DictionaryMatch
192 | {
193 | DictionaryName = "d1",
194 | i = 6,
195 | j = 10,
196 | MatchedWord = "board",
197 | Rank = 3,
198 | Reversed = false,
199 | L33t = false,
200 | Token = "board",
201 | },
202 | };
203 |
204 | result.Should().BeEquivalentTo(expected);
205 | }
206 |
207 | [Fact]
208 | public void UsesTheDefaultDictionaries()
209 | {
210 | var result = matcherTv.MatchPassword("wow").OfType().ToList();
211 |
212 | var expected = new[]
213 | {
214 | new DictionaryMatch
215 | {
216 | DictionaryName = "us_tv_and_film",
217 | i = 0,
218 | j = 2,
219 | MatchedWord = "wow",
220 | Rank = 322,
221 | Reversed = false,
222 | L33t = false,
223 | Token = "wow",
224 | },
225 | };
226 | result.Should().BeEquivalentTo(expected);
227 | }
228 |
229 | [Fact]
230 | public void UsesTheUserInputDictionary()
231 | {
232 | var matcher = new DictionaryMatcher("user_inputs", new[] { "foo", "bar" });
233 |
234 | var result = matcher.MatchPassword("foobar").OfType().ToList();
235 |
236 | var expected = new[]
237 | {
238 | new DictionaryMatch
239 | {
240 | DictionaryName = "user_inputs",
241 | i = 0,
242 | j = 2,
243 | MatchedWord = "foo",
244 | Rank = 1,
245 | Reversed = false,
246 | L33t = false,
247 | Token = "foo",
248 | },
249 | new DictionaryMatch
250 | {
251 | DictionaryName = "user_inputs",
252 | i = 3,
253 | j = 5,
254 | MatchedWord = "bar",
255 | Rank = 2,
256 | Reversed = false,
257 | L33t = false,
258 | Token = "bar",
259 | },
260 | };
261 |
262 | result.Should().BeEquivalentTo(expected);
263 | }
264 |
265 | private List RunMatches(string word)
266 | {
267 | var result = matcher1.MatchPassword(word).Concat(matcher2.MatchPassword(word));
268 |
269 | return result.OfType().ToList();
270 | }
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/L33tMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using Xunit;
6 | using Zxcvbn.Matcher;
7 | using Zxcvbn.Matcher.Matches;
8 |
9 | namespace Zxcvbn.Tests.Matcher
10 | {
11 | public class L33tMatcherTests
12 | {
13 | private static readonly DictionaryMatcher TestDictionary1 = new DictionaryMatcher("words", new[] { "aac", string.Empty, "password", "paassword", "asdf0" });
14 |
15 | private static readonly DictionaryMatcher TestDictionary2 = new DictionaryMatcher("words2", new[] { "cgo" });
16 |
17 | private static readonly ReadOnlyDictionary TestL33tTable = new ReadOnlyDictionary(new Dictionary
18 | {
19 | ['a'] = new[] { '4', '@' },
20 | ['c'] = new[] { '(', '{', '[', '<' },
21 | ['g'] = new[] { '6', '9' },
22 | ['o'] = new[] { '0' },
23 | });
24 |
25 | [Fact]
26 | public void DoesNotMatchEmptyString()
27 | {
28 | L33tMatcher.L33tTable = TestL33tTable;
29 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
30 |
31 | var result = matcher.MatchPassword(string.Empty);
32 | result.Should().BeEmpty();
33 | }
34 |
35 | [Fact]
36 | public void DoesNotMatchNonL33tWords()
37 | {
38 | L33tMatcher.L33tTable = TestL33tTable;
39 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
40 |
41 | var result = matcher.MatchPassword("password");
42 | result.Should().BeEmpty();
43 | }
44 |
45 | [Fact]
46 | public void DoesNotMatchSingleCharacterL33tedWords()
47 | {
48 | L33tMatcher.L33tTable = TestL33tTable;
49 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
50 |
51 | var result = matcher.MatchPassword("4 1 @");
52 | result.Should().BeEmpty();
53 | }
54 |
55 | [Fact]
56 | public void DoesNotMatchWhenMultipleSubstitutionsAreNeededForTheSameLetter()
57 | {
58 | L33tMatcher.L33tTable = TestL33tTable;
59 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
60 |
61 | var result = matcher.MatchPassword("p4@ssword");
62 | result.Should().BeEmpty();
63 | }
64 |
65 | [Fact]
66 | public void DoesNotMatchWithSubsetsOfPossibleL33tCombinations()
67 | {
68 | L33tMatcher.L33tTable = TestL33tTable;
69 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
70 |
71 | var result = matcher.MatchPassword("4sdf0");
72 | result.Should().BeEmpty();
73 | }
74 |
75 | [Fact]
76 | public void EnumerateL33tSubsGetsTheSetOfSubstitutionsForAPassword()
77 | {
78 | L33tMatcher.L33tTable = TestL33tTable;
79 |
80 | var actual = L33tMatcher.EnumerateSubtitutions(new ReadOnlyDictionary(new Dictionary()));
81 | actual.Single().Should().BeEmpty();
82 |
83 | actual = L33tMatcher.EnumerateSubtitutions(new ReadOnlyDictionary(new Dictionary
84 | {
85 | { 'a', new[] { '@' } },
86 | }));
87 | var expected = new List>()
88 | {
89 | new Dictionary()
90 | {
91 | { '@', 'a' },
92 | },
93 | };
94 | actual.Should().BeEquivalentTo(expected);
95 |
96 | actual = L33tMatcher.EnumerateSubtitutions(new ReadOnlyDictionary(new Dictionary
97 | {
98 | { 'a', new[] { '@', '4' } },
99 | }));
100 | expected = new List>()
101 | {
102 | new Dictionary()
103 | {
104 | { '@', 'a' },
105 | },
106 | new Dictionary()
107 | {
108 | { '4', 'a' },
109 | },
110 | };
111 | actual.Should().BeEquivalentTo(expected);
112 |
113 | actual = L33tMatcher.EnumerateSubtitutions(new ReadOnlyDictionary(new Dictionary
114 | {
115 | { 'a', new[] { '@', '4' } },
116 | { 'c', new[] { '(' } },
117 | }));
118 | expected = new List>()
119 | {
120 | new Dictionary()
121 | {
122 | { '@', 'a' },
123 | { '(', 'c' },
124 | },
125 | new Dictionary()
126 | {
127 | { '4', 'a' },
128 | { '(', 'c' },
129 | },
130 | };
131 | actual.Should().BeEquivalentTo(expected);
132 | }
133 |
134 | [Fact]
135 | public void MatchesCommonL33tSubstitutions()
136 | {
137 | L33tMatcher.L33tTable = TestL33tTable;
138 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
139 |
140 | var expected = new List
141 | {
142 | new DictionaryMatch()
143 | {
144 | DictionaryName = "words",
145 | i = 0,
146 | j = 7,
147 | MatchedWord = "password",
148 | Rank = 3,
149 | Reversed = false,
150 | L33t = true,
151 | Token = "p4ssword",
152 | L33tSubs = new Dictionary
153 | {
154 | { '4', 'a' },
155 | },
156 | },
157 | };
158 | var result = matcher.MatchPassword("p4ssword");
159 | result.Should().BeEquivalentTo(expected);
160 |
161 | expected = new List
162 | {
163 | new DictionaryMatch()
164 | {
165 | DictionaryName = "words",
166 | i = 0,
167 | j = 7,
168 | MatchedWord = "password",
169 | Rank = 3,
170 | Reversed = false,
171 | L33t = true,
172 | Token = "p@ssw0rd",
173 | L33tSubs = new Dictionary
174 | {
175 | { '@', 'a' }, { '0', 'o' },
176 | },
177 | },
178 | };
179 | result = matcher.MatchPassword("p@ssw0rd");
180 | result.Should().BeEquivalentTo(expected);
181 |
182 | expected = new List
183 | {
184 | new DictionaryMatch()
185 | {
186 | DictionaryName = "words2",
187 | i = 5,
188 | j = 7,
189 | MatchedWord = "cgo",
190 | Rank = 1,
191 | Reversed = false,
192 | L33t = true,
193 | Token = "{G0",
194 | L33tSubs = new Dictionary
195 | {
196 | { '{', 'c' }, { '0', 'o' },
197 | },
198 | },
199 | };
200 | result = matcher.MatchPassword("aSdfO{G0asDfO");
201 | result.Should().BeEquivalentTo(expected);
202 | }
203 |
204 | [Fact]
205 | public void MatchesOveralppingL33tPatterns()
206 | {
207 | L33tMatcher.L33tTable = TestL33tTable;
208 | var matcher = new L33tMatcher(new List { TestDictionary1, TestDictionary2 });
209 |
210 | var expected = new List
211 | {
212 | new DictionaryMatch()
213 | {
214 | DictionaryName = "words",
215 | i = 0,
216 | j = 2,
217 | MatchedWord = "aac",
218 | Rank = 1,
219 | Reversed = false,
220 | L33t = true,
221 | Token = "@a(",
222 | L33tSubs = new Dictionary
223 | {
224 | { '@', 'a' },
225 | { '(', 'c' },
226 | },
227 | },
228 | new DictionaryMatch()
229 | {
230 | DictionaryName = "words2",
231 | i = 2,
232 | j = 4,
233 | MatchedWord = "cgo",
234 | Rank = 1,
235 | Reversed = false,
236 | L33t = true,
237 | Token = "(go",
238 | L33tSubs = new Dictionary
239 | {
240 | { '(', 'c' },
241 | },
242 | },
243 | new DictionaryMatch()
244 | {
245 | DictionaryName = "words2",
246 | i = 5,
247 | j = 7,
248 | MatchedWord = "cgo",
249 | Rank = 1,
250 | Reversed = false,
251 | L33t = true,
252 | Token = "{G0",
253 | L33tSubs = new Dictionary
254 | {
255 | { '{', 'c' }, { '0', 'o' },
256 | },
257 | },
258 | };
259 | var result = matcher.MatchPassword("@a(go{G0");
260 | result.Should().BeEquivalentTo(expected);
261 | }
262 |
263 | [Fact]
264 | public void RelevantL33tTableReducesSubstitutions()
265 | {
266 | L33tMatcher.L33tTable = TestL33tTable;
267 |
268 | var actual = L33tMatcher.RelevantL33tSubtable(string.Empty);
269 | actual.Should().BeEmpty();
270 |
271 | actual = L33tMatcher.RelevantL33tSubtable("abcdefgo123578!#$&*)]}>");
272 | actual.Should().BeEmpty();
273 |
274 | actual = L33tMatcher.RelevantL33tSubtable("a");
275 | actual.Should().BeEmpty();
276 |
277 | var expected = new Dictionary
278 | {
279 | { 'a', new[] { '4' } },
280 | };
281 | actual = L33tMatcher.RelevantL33tSubtable("4");
282 | actual.Should().BeEquivalentTo(expected);
283 | }
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/RegexMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Linq;
3 | using Xunit;
4 | using Zxcvbn.Matcher;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Matcher
8 | {
9 | public class RegexMatcherTests
10 | {
11 | [Theory]
12 | [InlineData("1922", "recent_year")]
13 | [InlineData("2017", "recent_year")]
14 | public void MatchesPattern(string pattern, string name)
15 | {
16 | var re = new RegexMatcher(pattern, name);
17 |
18 | var result = re.MatchPassword(pattern).OfType().ToList();
19 |
20 | var expected = new[]
21 | {
22 | new RegexMatch
23 | {
24 | i = 0,
25 | j = pattern.Length - 1,
26 | RegexName = name,
27 | Token = pattern,
28 | },
29 | };
30 | result.Should().BeEquivalentTo(expected);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/ReverseDictionaryMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Linq;
3 | using Xunit;
4 | using Zxcvbn.Matcher;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Matcher
8 | {
9 | public class ReverseDictionaryMatcherTests
10 | {
11 | private readonly ReverseDictionaryMatcher matcher = new ReverseDictionaryMatcher("d1", "test_dictionary_3.txt");
12 |
13 | [Fact]
14 | public void MatchesAganstReversedWords()
15 | {
16 | var result = matcher.MatchPassword("0123456789").OfType().ToList();
17 |
18 | var expected = new[]
19 | {
20 | new DictionaryMatch
21 | {
22 | DictionaryName = "d1",
23 | i = 1,
24 | j = 3,
25 | MatchedWord = "321",
26 | Rank = 2,
27 | Reversed = true,
28 | L33t = false,
29 | Token = "123",
30 | },
31 | new DictionaryMatch
32 | {
33 | DictionaryName = "d1",
34 | i = 4,
35 | j = 6,
36 | MatchedWord = "654",
37 | Rank = 4,
38 | Reversed = true,
39 | L33t = false,
40 | Token = "456",
41 | },
42 | };
43 | result.Should().BeEquivalentTo(expected);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/SequenceMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Linq;
3 | using Xunit;
4 | using Zxcvbn.Matcher;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Matcher
8 | {
9 | public class SequenceMatcherTests
10 | {
11 | private readonly SequenceMatcher matcher = new SequenceMatcher();
12 |
13 | [Theory]
14 | [InlineData("")]
15 | [InlineData("a")]
16 | [InlineData("1")]
17 | public void DoesntMatchVeryEmptyOrOneLengthSequences(string password)
18 | {
19 | var res = matcher.MatchPassword(password);
20 | res.Should().BeEmpty();
21 | }
22 |
23 | [Theory]
24 | [InlineData("", "!")]
25 | [InlineData("", "22")]
26 | [InlineData("!", "!")]
27 | [InlineData("!", "22")]
28 | [InlineData("22", "!")]
29 | [InlineData("22", "22")]
30 | public void MatchesEmbeddedSequencePatterns(string prefix, string suffix)
31 | {
32 | const string pattern = "jihg";
33 |
34 | var password = prefix + pattern + suffix;
35 | var i = prefix.Length;
36 | var j = i + pattern.Length - 1;
37 |
38 | var result = matcher.MatchPassword(password).OfType().ToList();
39 |
40 | var expected = new[]
41 | {
42 | new SequenceMatch
43 | {
44 | Ascending = false,
45 | i = i,
46 | j = j,
47 | SequenceName = "lower",
48 | SequenceSpace = 26,
49 | Token = pattern,
50 | },
51 | };
52 | result.Should().BeEquivalentTo(expected);
53 | }
54 |
55 | [Theory]
56 | [InlineData("ABC", "upper", true, 26)]
57 | [InlineData("CBA", "upper", false, 26)]
58 | [InlineData("PQR", "upper", true, 26)]
59 | [InlineData("RQP", "upper", false, 26)]
60 | [InlineData("XYZ", "upper", true, 26)]
61 | [InlineData("ZYX", "upper", false, 26)]
62 | [InlineData("abcd", "lower", true, 26)]
63 | [InlineData("dcba", "lower", false, 26)]
64 | [InlineData("jihg", "lower", false, 26)]
65 | [InlineData("wxyz", "lower", true, 26)]
66 | [InlineData("zxvt", "lower", false, 26)]
67 | [InlineData("0369", "digits", true, 10)]
68 | [InlineData("97531", "digits", false, 10)]
69 | public void MatchesGeneralSequence(string password, string name, bool ascending, int space)
70 | {
71 | var result = matcher.MatchPassword(password).OfType().ToList();
72 |
73 | var expected = new[]
74 | {
75 | new SequenceMatch
76 | {
77 | Ascending = ascending,
78 | i = 0,
79 | j = password.Length - 1,
80 | SequenceName = name,
81 | SequenceSpace = space,
82 | Token = password,
83 | },
84 | };
85 | result.Should().BeEquivalentTo(expected);
86 | }
87 |
88 | [Fact]
89 | public void MatchesOverlappingPatterns()
90 | {
91 | var result = matcher.MatchPassword("abcbabc").OfType().ToList();
92 |
93 | var expected = new[]
94 | {
95 | new SequenceMatch
96 | {
97 | Ascending = true,
98 | i = 0,
99 | j = 2,
100 | SequenceName = "lower",
101 | SequenceSpace = 26,
102 | Token = "abc",
103 | },
104 | new SequenceMatch
105 | {
106 | Ascending = false,
107 | i = 2,
108 | j = 4,
109 | SequenceName = "lower",
110 | SequenceSpace = 26,
111 | Token = "cba",
112 | },
113 | new SequenceMatch
114 | {
115 | Ascending = true,
116 | i = 4,
117 | j = 6,
118 | SequenceName = "lower",
119 | SequenceSpace = 26,
120 | Token = "abc",
121 | },
122 | };
123 | result.Should().BeEquivalentTo(expected);
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Matcher/SpatialMatcherTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Linq;
3 | using Xunit;
4 | using Zxcvbn.Matcher;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Matcher
8 | {
9 | public class SpatialMatcherTests
10 | {
11 | private readonly SpatialMatcher matcher = new SpatialMatcher();
12 |
13 | [Theory]
14 | [InlineData("")]
15 | [InlineData("/")]
16 | [InlineData("qw")]
17 | [InlineData("*/")]
18 | public void DoesNotmatch1And2CharacterSpatialPatterns(string password)
19 | {
20 | var matches = matcher.MatchPassword(password);
21 |
22 | matches.Should().BeEmpty();
23 | }
24 |
25 | [Theory]
26 | [InlineData("12345", "qwerty", 1, 0)]
27 | [InlineData("@WSX", "qwerty", 1, 4)]
28 | [InlineData("6tfGHJ", "qwerty", 2, 3)]
29 | [InlineData("hGFd", "qwerty", 1, 2)]
30 | [InlineData("/;p09876yhn", "qwerty", 3, 0)]
31 | [InlineData("Xdr%", "qwerty", 1, 2)]
32 | [InlineData("159-", "keypad", 1, 0)]
33 | [InlineData("*84", "keypad", 1, 0)]
34 | [InlineData("/8520", "keypad", 1, 0)]
35 | [InlineData("369", "keypad", 1, 0)]
36 | [InlineData("/963.", "mac_keypad", 1, 0)]
37 | [InlineData("*-632.0214", "mac_keypad", 9, 0)]
38 | [InlineData("aoEP%yIxkjq:", "dvorak", 4, 5)]
39 | [InlineData(";qoaOQ:Aoq;a", "dvorak", 11, 4)]
40 | public void MatchesGeneralPattern(string pattern, string keyboard, int turns, int shifts)
41 | {
42 | var result = matcher.MatchPassword(pattern).OfType().Where(m => m.Graph == keyboard).ToList();
43 | var expected = new[]
44 | {
45 | new SpatialMatch
46 | {
47 | Graph = keyboard,
48 | Turns = turns,
49 | ShiftedCount = shifts,
50 | i = 0,
51 | j = pattern.Length - 1,
52 | Token = pattern,
53 | },
54 | };
55 | result.Should().BeEquivalentTo(expected);
56 | }
57 |
58 | [Fact]
59 | public void MatchesSpatialPatternSurroundedByNonSpatialPatterns()
60 | {
61 | var matcher = new SpatialMatcher();
62 | var pattern = "6tfGHJ";
63 | var password = $"rz!{pattern}%z";
64 | var result = matcher.MatchPassword(password).OfType().Where(m => m.Graph == "qwerty").ToList();
65 |
66 | var expected = new[]
67 | {
68 | new SpatialMatch
69 | {
70 | Graph = "qwerty",
71 | Turns = 2,
72 | ShiftedCount = 3,
73 | i = 3,
74 | j = 3 + pattern.Length - 1,
75 | Token = pattern,
76 | },
77 | };
78 | result.Should().BeEquivalentTo(expected);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/BruteForceScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Xunit;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Tests.Scoring
8 | {
9 | public class BruteForceScoringTests
10 | {
11 | [Fact]
12 | public void MostGuessableMatchSequenceChoosesMatchWithFewestGuessesIfTheyMatchTheSameSpan()
13 | {
14 | var password = "0123456789";
15 | var worseMatch = CreateTestMatch(0, 9, 1);
16 | var bestMatch = CreateTestMatch(0, 9, 2);
17 |
18 | var expected = new List
19 | {
20 | worseMatch,
21 | };
22 |
23 | var result = PasswordScoring.MostGuessableMatchSequence(password, new List { worseMatch, bestMatch }, true);
24 | result.Sequence.Should().BeEquivalentTo(expected);
25 |
26 | result = PasswordScoring.MostGuessableMatchSequence(password, new List { bestMatch, worseMatch }, true);
27 | result.Sequence.Should().BeEquivalentTo(expected);
28 | }
29 |
30 | [Fact]
31 | public void MostGuessableMatchSequenceChoosesOptimalMatches()
32 | {
33 | var password = "0123456789";
34 | var m0 = CreateTestMatch(0, 9, 3);
35 | var m1 = CreateTestMatch(0, 3, 2);
36 | var m2 = CreateTestMatch(4, 9, 1);
37 |
38 | var expected = new List
39 | {
40 | m0,
41 | };
42 |
43 | var result = PasswordScoring.MostGuessableMatchSequence(password, new List { m0, m1, m2 }, true);
44 | result.Sequence.Should().BeEquivalentTo(expected);
45 |
46 | m0.Guesses = 5;
47 |
48 | expected = new List
49 | {
50 | m1, m2,
51 | };
52 |
53 | result = PasswordScoring.MostGuessableMatchSequence(password, new List { m0, m1, m2 }, true);
54 | result.Sequence.Should().BeEquivalentTo(expected);
55 | }
56 |
57 | [Fact]
58 | public void MostGuessableMatchSequenceReturnsBruteForceThenMatchThenBruteForceWhenMatchCoversAnInfixOfPassword()
59 | {
60 | var password = "0123456789";
61 | var existingMatch = CreateTestMatch(1, 8, 1);
62 |
63 | var expected = new List
64 | {
65 | new BruteForceMatch
66 | {
67 | i = 0,
68 | j = 0,
69 | Token = "0",
70 | Guesses = 11,
71 | },
72 | existingMatch,
73 | new BruteForceMatch
74 | {
75 | i = 9,
76 | j = 9,
77 | Token = "9",
78 | Guesses = 11,
79 | },
80 | };
81 |
82 | var result = PasswordScoring.MostGuessableMatchSequence(password, new List { existingMatch }, true);
83 | result.Sequence.Should().BeEquivalentTo(expected);
84 | }
85 |
86 | [Fact]
87 | public void MostGuessableMatchSequenceReturnsBruteForceThenMatchWhenMatchCoversASuffixOfPassword()
88 | {
89 | var password = "0123456789";
90 | var existingMatch = CreateTestMatch(3, 9, 1);
91 |
92 | var expected = new List
93 | {
94 | new BruteForceMatch
95 | {
96 | i = 0,
97 | j = 2,
98 | Token = "012",
99 | Guesses = 1000,
100 | },
101 | existingMatch,
102 | };
103 |
104 | var result = PasswordScoring.MostGuessableMatchSequence(password, new List { existingMatch }, true);
105 | result.Sequence.Should().BeEquivalentTo(expected);
106 | }
107 |
108 | [Fact]
109 | public void MostGuessableMatchSequenceReturnsMatchThenBruteForceWhenMatchCoversAPrefixOfPassword()
110 | {
111 | var password = "0123456789";
112 | var existingMatch = CreateTestMatch(0, 5, 1);
113 |
114 | var expected = new List
115 | {
116 | existingMatch,
117 | new BruteForceMatch
118 | {
119 | i = 6,
120 | j = 9,
121 | Token = "6789",
122 | Guesses = 10000,
123 | },
124 | };
125 |
126 | var result = PasswordScoring.MostGuessableMatchSequence(password, new List { existingMatch }, true);
127 | result.Sequence.Should().BeEquivalentTo(expected);
128 | }
129 |
130 | [Fact]
131 | public void MostGuessableMatchSequenceRetursnOneMatchForEmptySequence()
132 | {
133 | var password = "0123456789";
134 | var expected = new List
135 | {
136 | new BruteForceMatch
137 | {
138 | i = 0,
139 | j = 9,
140 | Token = password,
141 | Guesses = 10000000000,
142 | },
143 | };
144 |
145 | var result = PasswordScoring.MostGuessableMatchSequence(password, Enumerable.Empty());
146 | result.Sequence.Should().BeEquivalentTo(expected);
147 | }
148 |
149 | private Match CreateTestMatch(int i, int j, double guesses)
150 | {
151 | return new DateMatch
152 | {
153 | i = i,
154 | j = j,
155 | Guesses = guesses,
156 | Token = "abc",
157 | Day = 1,
158 | Month = 2,
159 | Separator = "/",
160 | Year = 3,
161 | };
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/DateScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 | using Zxcvbn.Matcher;
4 | using Zxcvbn.Matcher.Matches;
5 | using Zxcvbn.Scoring;
6 |
7 | namespace Zxcvbn.Tests.Scoring
8 | {
9 | public class DateScoringTests
10 | {
11 | [Fact]
12 | public void AssumesMinYearSpaceForRecentYears()
13 | {
14 | var match = new DateMatch
15 | {
16 | Token = "2010",
17 | Separator = string.Empty,
18 | Year = 2010,
19 | Month = 1,
20 | Day = 1,
21 | i = 1,
22 | j = 2,
23 | };
24 |
25 | var actual = DateGuessesCalculator.CalculateGuesses(match);
26 | var expected = 365 * DateGuessesCalculator.MinimumYearSpace;
27 |
28 | actual.Should().Be(expected);
29 | }
30 |
31 | [Fact]
32 | public void CalculatesBasedOnReferenceYear()
33 | {
34 | var match = new DateMatch
35 | {
36 | Token = "1923",
37 | Separator = string.Empty,
38 | Year = 1923,
39 | Month = 1,
40 | Day = 1,
41 | i = 1,
42 | j = 2,
43 | };
44 |
45 | var actual = DateGuessesCalculator.CalculateGuesses(match);
46 | var expected = 365 * (DateMatcher.ReferenceYear - match.Year);
47 |
48 | actual.Should().Be(expected);
49 | }
50 |
51 | [Fact]
52 | public void IsDelegatedToBeEstimateGuesses()
53 | {
54 | var match = new DateMatch
55 | {
56 | Token = "1923",
57 | Separator = string.Empty,
58 | Year = 1923,
59 | Month = 1,
60 | Day = 1,
61 | i = 1,
62 | j = 2,
63 | };
64 |
65 | var actual = PasswordScoring.EstimateGuesses(match, "1923");
66 | var expected = 365 * (DateMatcher.ReferenceYear - match.Year);
67 |
68 | actual.Should().Be(expected);
69 | }
70 |
71 | [Fact]
72 | public void MultipliesByFourForSeparators()
73 | {
74 | var match = new DateMatch
75 | {
76 | Token = "1923",
77 | Separator = "/",
78 | Year = 1923,
79 | Month = 1,
80 | Day = 1,
81 | i = 1,
82 | j = 2,
83 | };
84 |
85 | var actual = DateGuessesCalculator.CalculateGuesses(match);
86 | var expected = 365 * (DateMatcher.ReferenceYear - match.Year) * 4;
87 |
88 | actual.Should().Be(expected);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/RegexScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 | using Zxcvbn.Matcher;
4 | using Zxcvbn.Matcher.Matches;
5 |
6 | namespace Zxcvbn.Tests.Scoring
7 | {
8 | public class RegexScoringTests
9 | {
10 | [Fact]
11 | public void CalculatesWithReferenceYear()
12 | {
13 | var match = new RegexMatch
14 | {
15 | Token = "1972",
16 | RegexName = "recent_year",
17 | i = 1,
18 | j = 2,
19 | };
20 |
21 | var result = PasswordScoring.EstimateGuesses(match, "1972");
22 | result.Should().Be(DateMatcher.ReferenceYear - 1972);
23 | }
24 |
25 | [Fact]
26 | public void IsDelegatedToByEstimateGuesses()
27 | {
28 | var match = new RegexMatch
29 | {
30 | Token = "1972",
31 | RegexName = "recent_year",
32 | i = 1,
33 | j = 2,
34 | };
35 |
36 | var result = PasswordScoring.EstimateGuesses(match, "1972");
37 | result.Should().Be(DateMatcher.ReferenceYear - 1972);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/RepeatScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System.Collections.Generic;
3 | using Xunit;
4 | using Zxcvbn.Matcher.Matches;
5 | using Zxcvbn.Scoring;
6 |
7 | namespace Zxcvbn.Tests.Scoring
8 | {
9 | public class RepeatScoringTests
10 | {
11 | [Theory]
12 | [InlineData("aa", "a", 2)]
13 | [InlineData("999", "9", 3)]
14 | [InlineData("$$$$", "$", 4)]
15 | [InlineData("abab", "ab", 2)]
16 | [InlineData("batterystaplebatterystaplebatterystaple", "batterystaple", 3)]
17 | public void CalculatesTheRightNumberOfGuesses(string token, string baseToken, int expectedRepeats)
18 | {
19 | var baseGuesses = PasswordScoring.MostGuessableMatchSequence(baseToken, Zxcvbn.Core.GetAllMatches(baseToken)).Guesses;
20 |
21 | var match = new RepeatMatch
22 | {
23 | Token = token,
24 | BaseToken = baseToken,
25 | BaseGuesses = baseGuesses,
26 | RepeatCount = expectedRepeats,
27 | BaseMatchItems = new List(),
28 | i = 1,
29 | j = 2,
30 | };
31 |
32 | var expected = baseGuesses * expectedRepeats;
33 |
34 | var actual = RepeatGuessesCalculator.CalculateGuesses(match);
35 | actual.Should().Be(expected);
36 | }
37 |
38 | [Fact]
39 | public void IsDelegatedToByEstimateGuesses()
40 | {
41 | var match = new RepeatMatch
42 | {
43 | Token = "aa",
44 | BaseToken = "a",
45 | BaseGuesses = PasswordScoring.MostGuessableMatchSequence("a", Zxcvbn.Core.GetAllMatches("a")).Guesses,
46 | BaseMatchItems = new List(),
47 | RepeatCount = 2,
48 | i = 1,
49 | j = 2,
50 | };
51 |
52 | var expected = RepeatGuessesCalculator.CalculateGuesses(match);
53 | var actual = PasswordScoring.EstimateGuesses(match, "aa");
54 |
55 | actual.Should().Be(expected);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/ScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Tests.Scoring
6 | {
7 | public class ScoringTests
8 | {
9 | [Fact]
10 | public void ReturnsCachedGuessesIfAvailable()
11 | {
12 | var match = new DateMatch
13 | {
14 | Guesses = 1,
15 | Token = "1977",
16 | Year = 1977,
17 | Month = 8,
18 | Day = 14,
19 | Separator = "/",
20 | i = 1,
21 | j = 2,
22 | };
23 |
24 | var actual = PasswordScoring.EstimateGuesses(match, string.Empty);
25 | actual.Should().Be(1);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/SequenceScoringTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using Xunit;
3 | using Zxcvbn.Matcher.Matches;
4 | using Zxcvbn.Scoring;
5 |
6 | namespace Zxcvbn.Tests.Scoring
7 | {
8 | public class SequenceScoringTests
9 | {
10 | [Theory]
11 | [InlineData("ab", true, 8)]
12 | [InlineData("XYZ", true, 78)]
13 | [InlineData("4567", true, 40)]
14 | [InlineData("7654", false, 80)]
15 | [InlineData("ZYX", false, 24)]
16 | public void CalculatesTheCorrectNumberOfGuesses(string token, bool ascending, int expected)
17 | {
18 | var match = new SequenceMatch
19 | {
20 | Token = token,
21 | Ascending = ascending,
22 | i = 1,
23 | j = 2,
24 | SequenceName = "abc",
25 | SequenceSpace = 1,
26 | };
27 |
28 | var actual = SequenceGuessesCalculator.CalculateGuesses(match);
29 | actual.Should().Be(expected);
30 | }
31 |
32 | [Fact]
33 | public void IsDelegatedToByEstimateGuesses()
34 | {
35 | var match = new SequenceMatch
36 | {
37 | Token = "ab",
38 | Ascending = true,
39 | i = 1,
40 | j = 2,
41 | SequenceName = "abc",
42 | SequenceSpace = 1,
43 | };
44 |
45 | var actual = SequenceGuessesCalculator.CalculateGuesses(match);
46 | actual.Should().Be(8);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/Scoring/SpatialTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using System;
3 | using Xunit;
4 | using Zxcvbn.Matcher.Matches;
5 | using Zxcvbn.Scoring;
6 |
7 | namespace Zxcvbn.Tests.Scoring
8 | {
9 | public class SpatialTests
10 | {
11 | [Fact]
12 | public void AccountsForTurnPositionsDirectionsAndStartingKey()
13 | {
14 | var match = new SpatialMatch
15 | {
16 | Token = "zxcvbn",
17 | Graph = "qwerty",
18 | Turns = 3,
19 | ShiftedCount = 0,
20 | i = 1,
21 | j = 2,
22 | };
23 |
24 | var l = match.Token.Length;
25 | var s = SpatialGuessesCalculator.KeyboardStartingPositions;
26 | var d = SpatialGuessesCalculator.KeyboardAverageDegree;
27 | var expected = 0.0;
28 |
29 | for (var i = 2; i <= l; i++)
30 | {
31 | for (var j = 1; j <= Math.Min(match.Turns, i - 1); j++)
32 | {
33 | expected += PasswordScoring.Binomial(i - 1, j - 1) * s * Math.Pow(d, j);
34 | }
35 | }
36 |
37 | var actual = SpatialGuessesCalculator.CalculateGuesses(match);
38 |
39 | actual.Should().Be(expected);
40 | }
41 |
42 | [Fact]
43 | public void AddsToTheGuessesForShiftedKeys()
44 | {
45 | var match = new SpatialMatch
46 | {
47 | Token = "zxcvbn",
48 | Graph = "qwerty",
49 | Turns = 1,
50 | ShiftedCount = 2,
51 | i = 1,
52 | j = 2,
53 | };
54 |
55 | var expected = SpatialGuessesCalculator.KeyboardStartingPositions * SpatialGuessesCalculator.KeyboardAverageDegree * (match.Token.Length - 1) * 21;
56 |
57 | var actual = SpatialGuessesCalculator.CalculateGuesses(match);
58 |
59 | actual.Should().Be(expected);
60 | }
61 |
62 | [Fact]
63 | public void DoublesGuessesIfEverythingIsShifted()
64 | {
65 | var match = new SpatialMatch
66 | {
67 | Token = "zxcvbn",
68 | Graph = "qwerty",
69 | Turns = 1,
70 | ShiftedCount = 6,
71 | i = 1,
72 | j = 2,
73 | };
74 |
75 | var expected = SpatialGuessesCalculator.KeyboardStartingPositions * SpatialGuessesCalculator.KeyboardAverageDegree * (match.Token.Length - 1) * 2;
76 |
77 | var actual = SpatialGuessesCalculator.CalculateGuesses(match);
78 |
79 | actual.Should().Be(expected);
80 | }
81 |
82 | [Fact]
83 | public void GuessesWhenThereAreNoTurns()
84 | {
85 | var match = new SpatialMatch
86 | {
87 | Token = "zxcvbn",
88 | Graph = "qwerty",
89 | Turns = 1,
90 | ShiftedCount = 0,
91 | i = 1,
92 | j = 2,
93 | };
94 |
95 | var expected = SpatialGuessesCalculator.KeyboardStartingPositions * SpatialGuessesCalculator.KeyboardAverageDegree * (match.Token.Length - 1);
96 |
97 | var actual = SpatialGuessesCalculator.CalculateGuesses(match);
98 |
99 | actual.Should().Be(expected);
100 | }
101 |
102 | [Fact]
103 | public void IsDelegatedToByEstimateGuesses()
104 | {
105 | var match = new SpatialMatch
106 | {
107 | Token = "zxcvbn",
108 | Graph = "qwerty",
109 | Turns = 1,
110 | ShiftedCount = 0,
111 | i = 1,
112 | j = 2,
113 | };
114 |
115 | var expected = SpatialGuessesCalculator.KeyboardStartingPositions * SpatialGuessesCalculator.KeyboardAverageDegree * (match.Token.Length - 1);
116 |
117 | var actual = PasswordScoring.EstimateGuesses(match, match.Token);
118 |
119 | actual.Should().Be(expected);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/test_dictionary_1.txt:
--------------------------------------------------------------------------------
1 | motherboard
2 | mother
3 | board
4 | abcd
5 | cdef
--------------------------------------------------------------------------------
/zxcvbn-core-test/test_dictionary_2.txt:
--------------------------------------------------------------------------------
1 | z
2 | 8
3 | 99
4 | $
5 | asdf1234&*
--------------------------------------------------------------------------------
/zxcvbn-core-test/test_dictionary_3.txt:
--------------------------------------------------------------------------------
1 | 123
2 | 321
3 | 456
4 | 654
--------------------------------------------------------------------------------
/zxcvbn-core-test/zxcvbn-core-test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net5.0
4 | false
5 | zxcvbn-core-test
6 | Zxcvbn.Tests
7 | true
8 | zxcvbn-strong-name-key.snk
9 | False
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Always
34 |
35 |
36 | Always
37 |
38 |
39 | Always
40 |
41 |
42 |
--------------------------------------------------------------------------------
/zxcvbn-core-test/zxcvbn-strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trichards57/zxcvbn-cs/2e680287ca64bde8ee5366a17ac0d2af7a11d8ea/zxcvbn-core-test/zxcvbn-strong-name-key.snk
--------------------------------------------------------------------------------
/zxcvbn-core/AttackTimes.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn
2 | {
3 | ///
4 | /// A summary of the attack times for the password.
5 | ///
6 | internal class AttackTimes
7 | {
8 | ///
9 | /// Gets or sets the display version of the crack times.
10 | ///
11 | public CrackTimesDisplay CrackTimesDisplay { get; set; }
12 |
13 | ///
14 | /// Gets or sets the numerical version of the crack times.
15 | ///
16 | public CrackTimes CrackTimesSeconds { get; set; }
17 |
18 | ///
19 | /// Gets or sets the overall score.
20 | ///
21 | public int Score { get; set; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/zxcvbn-core/Core.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn
6 | {
7 | ///
8 | /// Estimates the strength of passwords.
9 | ///
10 | public static class Core
11 | {
12 | ///
13 | /// Perform the password matching on the given password and user inputs.
14 | ///
15 | /// The password to assess.
16 | /// Optionally, an enumarable of user data.
17 | /// Result for the password.
18 | public static Result EvaluatePassword(string password, IEnumerable userInputs = null)
19 | {
20 | password = password ?? string.Empty;
21 | userInputs = userInputs ?? Enumerable.Empty();
22 |
23 | var timer = System.Diagnostics.Stopwatch.StartNew();
24 |
25 | var matches = GetAllMatches(password, userInputs);
26 | var result = PasswordScoring.MostGuessableMatchSequence(password, matches);
27 | timer.Stop();
28 |
29 | var attackTimes = TimeEstimates.EstimateAttackTimes(result.Guesses);
30 | result.Score = attackTimes.Score;
31 | var feedback = Feedback.GetFeedback(result.Score, result.Sequence);
32 |
33 | var finalResult = new Result
34 | {
35 | CalcTime = timer.ElapsedMilliseconds,
36 | CrackTime = attackTimes.CrackTimesSeconds,
37 | CrackTimeDisplay = attackTimes.CrackTimesDisplay,
38 | Score = attackTimes.Score,
39 | MatchSequence = result.Sequence,
40 | Guesses = result.Guesses,
41 | Password = result.Password,
42 | Feedback = feedback,
43 | };
44 |
45 | return finalResult;
46 | }
47 |
48 | ///
49 | /// Gets all matches associated with a password.
50 | ///
51 | /// The password to assess.
52 | /// The user input dictionary.
53 | /// An enumerable of relevant matches.
54 | internal static IEnumerable GetAllMatches(string token, IEnumerable userInputs = null)
55 | {
56 | userInputs = userInputs ?? Enumerable.Empty();
57 |
58 | return DefaultMatcherFactory.CreateMatchers(userInputs).SelectMany(matcher => matcher.MatchPassword(token));
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/zxcvbn-core/CrackTimes.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn
2 | {
3 | ///
4 | /// Represents the time needed to crack the password under different conditions.
5 | ///
6 | public class CrackTimes
7 | {
8 | ///
9 | /// Gets the time if offline hashing at 1e10 per second.
10 | ///
11 | public double OfflineFastHashing1e10PerSecond { get; internal set; }
12 |
13 | ///
14 | /// Gets the time if offline hashing at 1e4 per second.
15 | ///
16 | public double OfflineSlowHashing1e4PerSecond { get; internal set; }
17 |
18 | ///
19 | /// Gets the time if online attempting the password at 10 per second.
20 | ///
21 | public double OnlineNoThrottling10PerSecond { get; internal set; }
22 |
23 | ///
24 | /// Gets the time if online attempting the password at 100 per hour.
25 | ///
26 | public double OnlineThrottling100PerHour { get; internal set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/zxcvbn-core/CrackTimesDisplay.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn
2 | {
3 | ///
4 | /// Represents the time needed to crack the password under different conditions.
5 | ///
6 | public class CrackTimesDisplay
7 | {
8 | ///
9 | /// Gets the time if offline hashing at 1e10 per second.
10 | ///
11 | public string OfflineFastHashing1e10PerSecond { get; internal set; }
12 |
13 | ///
14 | /// Gets the time if offline hashing at 1e4 per second.
15 | ///
16 | public string OfflineSlowHashing1e4PerSecond { get; internal set; }
17 |
18 | ///
19 | /// Gets the time if online attempting the password at 10 per second.
20 | ///
21 | public string OnlineNoThrottling10PerSecond { get; internal set; }
22 |
23 | ///
24 | /// Gets the time if online attempting the password at 100 per hour.
25 | ///
26 | public string OnlineThrottling100PerHour { get; internal set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/zxcvbn-core/DefaultMatcherFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Zxcvbn.Matcher;
3 |
4 | namespace Zxcvbn
5 | {
6 | ///
7 | /// Creates the default matchers.
8 | ///
9 | internal static class DefaultMatcherFactory
10 | {
11 | private static readonly IEnumerable BuiltInMatchers = BuildBuiltInMatchers();
12 |
13 | ///
14 | /// Gets all of the built in matchers, as well as matchers for the custom dictionaries.
15 | ///
16 | /// Enumerable of user information.
17 | /// Enumerable of matchers to use.
18 | public static IEnumerable CreateMatchers(IEnumerable userInputs)
19 | {
20 | var userInputDict = new DictionaryMatcher("user_inputs", userInputs);
21 | var leetUser = new L33tMatcher(userInputDict);
22 |
23 | return new List(BuiltInMatchers) { userInputDict, leetUser };
24 | }
25 |
26 | private static IEnumerable BuildBuiltInMatchers()
27 | {
28 | var dictionaryMatchers = new List
29 | {
30 | new DictionaryMatcher("passwords", "passwords.lst"),
31 | new DictionaryMatcher("english", "english.lst"),
32 | new DictionaryMatcher("male_names", "male_names.lst"),
33 | new DictionaryMatcher("female_names", "female_names.lst"),
34 | new DictionaryMatcher("surnames", "surnames.lst"),
35 | new DictionaryMatcher("us_tv_and_film", "us_tv_and_film.lst"),
36 | new ReverseDictionaryMatcher("passwords", "passwords.lst"),
37 | new ReverseDictionaryMatcher("english", "english.lst"),
38 | new ReverseDictionaryMatcher("male_names", "male_names.lst"),
39 | new ReverseDictionaryMatcher("female_names", "female_names.lst"),
40 | new ReverseDictionaryMatcher("surnames", "surnames.lst"),
41 | new ReverseDictionaryMatcher("us_tv_and_film", "us_tv_and_film.lst"),
42 | };
43 |
44 | return new List(dictionaryMatchers)
45 | {
46 | new RepeatMatcher(),
47 | new SequenceMatcher(),
48 | new RegexMatcher("19\\d\\d|200\\d|201\\d", "recent_year"),
49 | new DateMatcher(),
50 | new SpatialMatcher(),
51 | new L33tMatcher(dictionaryMatchers),
52 | };
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/zxcvbn-core/Dictionaries/male_names.lst:
--------------------------------------------------------------------------------
1 | james
2 | john
3 | robert
4 | michael
5 | william
6 | david
7 | richard
8 | charles
9 | joseph
10 | thomas
11 | christopher
12 | daniel
13 | paul
14 | mark
15 | donald
16 | george
17 | kenneth
18 | steven
19 | edward
20 | brian
21 | ronald
22 | anthony
23 | kevin
24 | jason
25 | matthew
26 | gary
27 | timothy
28 | jose
29 | larry
30 | jeffrey
31 | frank
32 | scott
33 | eric
34 | stephen
35 | andrew
36 | raymond
37 | gregory
38 | joshua
39 | jerry
40 | dennis
41 | walter
42 | patrick
43 | peter
44 | harold
45 | douglas
46 | henry
47 | carl
48 | arthur
49 | ryan
50 | roger
51 | joe
52 | juan
53 | jack
54 | albert
55 | jonathan
56 | justin
57 | terry
58 | gerald
59 | keith
60 | samuel
61 | willie
62 | ralph
63 | lawrence
64 | nicholas
65 | roy
66 | benjamin
67 | bruce
68 | brandon
69 | adam
70 | harry
71 | fred
72 | wayne
73 | billy
74 | steve
75 | louis
76 | jeremy
77 | aaron
78 | randy
79 | eugene
80 | carlos
81 | russell
82 | bobby
83 | victor
84 | ernest
85 | phillip
86 | todd
87 | jesse
88 | craig
89 | alan
90 | shawn
91 | clarence
92 | sean
93 | philip
94 | chris
95 | johnny
96 | earl
97 | jimmy
98 | antonio
99 | danny
100 | bryan
101 | tony
102 | luis
103 | mike
104 | stanley
105 | leonard
106 | nathan
107 | dale
108 | manuel
109 | rodney
110 | curtis
111 | norman
112 | marvin
113 | vincent
114 | glenn
115 | jeffery
116 | travis
117 | jeff
118 | chad
119 | jacob
120 | melvin
121 | alfred
122 | kyle
123 | francis
124 | bradley
125 | jesus
126 | herbert
127 | frederick
128 | ray
129 | joel
130 | edwin
131 | don
132 | eddie
133 | ricky
134 | troy
135 | randall
136 | barry
137 | bernard
138 | mario
139 | leroy
140 | francisco
141 | marcus
142 | micheal
143 | theodore
144 | clifford
145 | miguel
146 | oscar
147 | jay
148 | jim
149 | tom
150 | calvin
151 | alex
152 | jon
153 | ronnie
154 | bill
155 | lloyd
156 | tommy
157 | leon
158 | derek
159 | darrell
160 | jerome
161 | floyd
162 | leo
163 | alvin
164 | tim
165 | wesley
166 | dean
167 | greg
168 | jorge
169 | dustin
170 | pedro
171 | derrick
172 | dan
173 | zachary
174 | corey
175 | herman
176 | maurice
177 | vernon
178 | roberto
179 | clyde
180 | glen
181 | hector
182 | shane
183 | ricardo
184 | sam
185 | rick
186 | lester
187 | brent
188 | ramon
189 | tyler
190 | gilbert
191 | gene
192 | marc
193 | reginald
194 | ruben
195 | brett
196 | nathaniel
197 | rafael
198 | edgar
199 | milton
200 | raul
201 | ben
202 | cecil
203 | duane
204 | andre
205 | elmer
206 | brad
207 | gabriel
208 | ron
209 | roland
210 | harvey
211 | jared
212 | adrian
213 | karl
214 | cory
215 | claude
216 | erik
217 | darryl
218 | neil
219 | christian
220 | javier
221 | fernando
222 | clinton
223 | ted
224 | mathew
225 | tyrone
226 | darren
227 | lonnie
228 | lance
229 | cody
230 | julio
231 | kurt
232 | allan
233 | clayton
234 | hugh
235 | max
236 | dwayne
237 | dwight
238 | armando
239 | felix
240 | jimmie
241 | everett
242 | ian
243 | ken
244 | bob
245 | jaime
246 | casey
247 | alfredo
248 | alberto
249 | dave
250 | ivan
251 | johnnie
252 | sidney
253 | byron
254 | julian
255 | isaac
256 | clifton
257 | willard
258 | daryl
259 | virgil
260 | andy
261 | salvador
262 | kirk
263 | sergio
264 | seth
265 | kent
266 | terrance
267 | rene
268 | eduardo
269 | terrence
270 | enrique
271 | freddie
272 | stuart
273 | fredrick
274 | arturo
275 | alejandro
276 | joey
277 | nick
278 | luther
279 | wendell
280 | jeremiah
281 | evan
282 | julius
283 | donnie
284 | otis
285 | trevor
286 | luke
287 | homer
288 | gerard
289 | doug
290 | kenny
291 | hubert
292 | angelo
293 | shaun
294 | lyle
295 | matt
296 | alfonso
297 | orlando
298 | rex
299 | carlton
300 | ernesto
301 | pablo
302 | lorenzo
303 | omar
304 | wilbur
305 | blake
306 | horace
307 | roderick
308 | kerry
309 | abraham
310 | rickey
311 | ira
312 | andres
313 | cesar
314 | johnathan
315 | malcolm
316 | rudolph
317 | damon
318 | kelvin
319 | rudy
320 | preston
321 | alton
322 | archie
323 | marco
324 | pete
325 | randolph
326 | garry
327 | geoffrey
328 | jonathon
329 | felipe
330 | bennie
331 | gerardo
332 | dominic
333 | loren
334 | delbert
335 | colin
336 | guillermo
337 | earnest
338 | benny
339 | noel
340 | rodolfo
341 | myron
342 | edmund
343 | salvatore
344 | cedric
345 | lowell
346 | gregg
347 | sherman
348 | devin
349 | sylvester
350 | roosevelt
351 | israel
352 | jermaine
353 | forrest
354 | wilbert
355 | leland
356 | simon
357 | irving
358 | owen
359 | rufus
360 | woodrow
361 | sammy
362 | kristopher
363 | levi
364 | marcos
365 | gustavo
366 | jake
367 | lionel
368 | marty
369 | gilberto
370 | clint
371 | nicolas
372 | laurence
373 | ismael
374 | orville
375 | drew
376 | ervin
377 | dewey
378 | wilfred
379 | josh
380 | hugo
381 | ignacio
382 | caleb
383 | tomas
384 | sheldon
385 | erick
386 | frankie
387 | darrel
388 | rogelio
389 | terence
390 | alonzo
391 | elias
392 | bert
393 | elbert
394 | ramiro
395 | conrad
396 | noah
397 | grady
398 | phil
399 | cornelius
400 | lamar
401 | rolando
402 | clay
403 | percy
404 | bradford
405 | merle
406 | darin
407 | amos
408 | terrell
409 | moses
410 | irvin
411 | saul
412 | roman
413 | darnell
414 | randal
415 | tommie
416 | timmy
417 | darrin
418 | brendan
419 | toby
420 | van
421 | abel
422 | dominick
423 | emilio
424 | elijah
425 | cary
426 | domingo
427 | aubrey
428 | emmett
429 | marlon
430 | emanuel
431 | jerald
432 | edmond
433 | emil
434 | dewayne
435 | otto
436 | teddy
437 | reynaldo
438 | bret
439 | jess
440 | trent
441 | humberto
442 | emmanuel
443 | stephan
444 | louie
445 | vicente
446 | lamont
447 | garland
448 | micah
449 | efrain
450 | heath
451 | rodger
452 | demetrius
453 | ethan
454 | eldon
455 | rocky
456 | pierre
457 | eli
458 | bryce
459 | antoine
460 | robbie
461 | kendall
462 | royce
463 | sterling
464 | grover
465 | elton
466 | cleveland
467 | dylan
468 | chuck
469 | damian
470 | reuben
471 | stan
472 | leonardo
473 | russel
474 | erwin
475 | benito
476 | hans
477 | monte
478 | blaine
479 | ernie
480 | curt
481 | quentin
482 | agustin
483 | jamal
484 | devon
485 | adolfo
486 | tyson
487 | wilfredo
488 | bart
489 | jarrod
490 | vance
491 | denis
492 | damien
493 | joaquin
494 | harlan
495 | desmond
496 | elliot
497 | darwin
498 | gregorio
499 | kermit
500 | roscoe
501 | esteban
502 | anton
503 | solomon
504 | norbert
505 | elvin
506 | nolan
507 | carey
508 | rod
509 | quinton
510 | hal
511 | brain
512 | rob
513 | elwood
514 | kendrick
515 | darius
516 | moises
517 | marlin
518 | fidel
519 | thaddeus
520 | cliff
521 | marcel
522 | ali
523 | raphael
524 | bryon
525 | armand
526 | alvaro
527 | jeffry
528 | dane
529 | joesph
530 | thurman
531 | ned
532 | sammie
533 | rusty
534 | michel
535 | monty
536 | rory
537 | fabian
538 | reggie
539 | kris
540 | isaiah
541 | gus
542 | avery
543 | loyd
544 | diego
545 | adolph
546 | millard
547 | rocco
548 | gonzalo
549 | derick
550 | rodrigo
551 | gerry
552 | rigoberto
553 | alphonso
554 | rickie
555 | noe
556 | vern
557 | elvis
558 | bernardo
559 | mauricio
560 | hiram
561 | donovan
562 | basil
563 | nickolas
564 | scot
565 | vince
566 | quincy
567 | eddy
568 | sebastian
569 | federico
570 | ulysses
571 | heriberto
572 | donnell
573 | denny
574 | gavin
575 | emery
576 | romeo
577 | jayson
578 | dion
579 | dante
580 | clement
581 | coy
582 | odell
583 | jarvis
584 | bruno
585 | issac
586 | dudley
587 | sanford
588 | colby
589 | carmelo
590 | nestor
591 | hollis
592 | stefan
593 | donny
594 | linwood
595 | beau
596 | weldon
597 | galen
598 | isidro
599 | truman
600 | delmar
601 | johnathon
602 | silas
603 | frederic
604 | irwin
605 | merrill
606 | charley
607 | marcelino
608 | carlo
609 | trenton
610 | kurtis
611 | aurelio
612 | winfred
613 | vito
614 | collin
615 | denver
616 | leonel
617 | emory
618 | pasquale
619 | mohammad
620 | mariano
621 | danial
622 | landon
623 | dirk
624 | branden
625 | adan
626 | numbers
627 | clair
628 | buford
629 | bernie
630 | wilmer
631 | emerson
632 | zachery
633 | jacques
634 | errol
635 | josue
636 | edwardo
637 | wilford
638 | theron
639 | raymundo
640 | daren
641 | tristan
642 | robby
643 | lincoln
644 | jame
645 | genaro
646 | octavio
647 | cornell
648 | hung
649 | arron
650 | antony
651 | herschel
652 | alva
653 | giovanni
654 | garth
655 | cyrus
656 | cyril
657 | ronny
658 | stevie
659 | lon
660 | kennith
661 | carmine
662 | augustine
663 | erich
664 | chadwick
665 | wilburn
666 | russ
667 | myles
668 | jonas
669 | mitchel
670 | mervin
671 | zane
672 | jamel
673 | lazaro
674 | alphonse
675 | randell
676 | johnie
677 | jarrett
678 | ariel
679 | abdul
680 | dusty
681 | luciano
682 | seymour
683 | scottie
684 | eugenio
685 | mohammed
686 | arnulfo
687 | lucien
688 | ferdinand
689 | thad
690 | ezra
691 | aldo
692 | rubin
693 | mitch
694 | earle
695 | abe
696 | marquis
697 | lanny
698 | kareem
699 | jamar
700 | boris
701 | isiah
702 | emile
703 | elmo
704 | aron
705 | leopoldo
706 | everette
707 | josef
708 | eloy
709 | dorian
710 | rodrick
711 | reinaldo
712 | lucio
713 | jerrod
714 | weston
715 | hershel
716 | lemuel
717 | lavern
718 | burt
719 | jules
720 | gil
721 | eliseo
722 | ahmad
723 | nigel
724 | efren
725 | antwan
726 | alden
727 | margarito
728 | refugio
729 | dino
730 | osvaldo
731 | les
732 | deandre
733 | normand
734 | kieth
735 | ivory
736 | trey
737 | norberto
738 | napoleon
739 | jerold
740 | fritz
741 | rosendo
742 | milford
743 | sang
744 | deon
745 | christoper
746 | alfonzo
747 | lyman
748 | josiah
749 | brant
750 | wilton
751 | rico
752 | jamaal
753 | dewitt
754 | brenton
755 | yong
756 | olin
757 | faustino
758 | claudio
759 | judson
760 | gino
761 | edgardo
762 | alec
763 | jarred
764 | donn
765 | trinidad
766 | tad
767 | porfirio
768 | odis
769 | lenard
770 | chauncey
771 | tod
772 | mel
773 | marcelo
774 | kory
775 | augustus
776 | keven
777 | hilario
778 | bud
779 | sal
780 | orval
781 | mauro
782 | dannie
783 | zachariah
784 | olen
785 | anibal
786 | milo
787 | jed
788 | thanh
789 | amado
790 | lenny
791 | tory
792 | richie
793 | horacio
794 | brice
795 | mohamed
796 | delmer
797 | dario
798 | mac
799 | jonah
800 | jerrold
801 | robt
802 | hank
803 | sung
804 | rupert
805 | rolland
806 | kenton
807 | damion
808 | chi
809 | antone
810 | waldo
811 | fredric
812 | bradly
813 | kip
814 | burl
815 | tyree
816 | jefferey
817 | ahmed
818 | willy
819 | stanford
820 | oren
821 | moshe
822 | mikel
823 | enoch
824 | brendon
825 | quintin
826 | jamison
827 | florencio
828 | darrick
829 | tobias
830 | minh
831 | hassan
832 | giuseppe
833 | demarcus
834 | cletus
835 | tyrell
836 | lyndon
837 | keenan
838 | werner
839 | theo
840 | geraldo
841 | columbus
842 | chet
843 | bertram
844 | markus
845 | huey
846 | hilton
847 | dwain
848 | donte
849 | tyron
850 | omer
851 | isaias
852 | hipolito
853 | fermin
854 | chung
855 | adalberto
856 | jamey
857 | teodoro
858 | mckinley
859 | maximo
860 | raleigh
861 | lawerence
862 | abram
863 | rashad
864 | emmitt
865 | daron
866 | chong
867 | samual
868 | otha
869 | miquel
870 | eusebio
871 | dong
872 | domenic
873 | darron
874 | wilber
875 | renato
876 | hoyt
877 | haywood
878 | ezekiel
879 | chas
880 | florentino
881 | elroy
882 | clemente
883 | arden
884 | neville
885 | edison
886 | deshawn
887 | carrol
888 | shayne
889 | nathanial
890 | jordon
891 | danilo
892 | claud
893 | sherwood
894 | raymon
895 | rayford
896 | cristobal
897 | ambrose
898 | titus
899 | hyman
900 | felton
901 | ezequiel
902 | erasmo
903 | lonny
904 | milan
905 | lino
906 | jarod
907 | herb
908 | andreas
909 | rhett
910 | jude
911 | douglass
912 | cordell
913 | oswaldo
914 | ellsworth
915 | virgilio
916 | toney
917 | nathanael
918 | benedict
919 | mose
920 | hong
921 | isreal
922 | garret
923 | fausto
924 | arlen
925 | zack
926 | modesto
927 | francesco
928 | manual
929 | gaylord
930 | gaston
931 | filiberto
932 | deangelo
933 | michale
934 | granville
935 | malik
936 | zackary
937 | tuan
938 | nicky
939 | cristopher
940 | antione
941 | malcom
942 | korey
943 | jospeh
944 | colton
945 | waylon
946 | hosea
947 | shad
948 | santo
949 | rudolf
950 | rolf
951 | renaldo
952 | marcellus
953 | lucius
954 | kristofer
955 | harland
956 | arnoldo
957 | rueben
958 | leandro
959 | kraig
960 | jerrell
961 | jeromy
962 | hobert
963 | cedrick
964 | arlie
965 | winford
966 | wally
967 | luigi
968 | keneth
969 | jacinto
970 | graig
971 | franklyn
972 | edmundo
973 | leif
974 | jeramy
975 | willian
976 | vincenzo
977 | shon
978 | michal
979 | lynwood
980 | jere
981 | elden
982 | darell
983 | broderick
984 | alonso
985 |
--------------------------------------------------------------------------------
/zxcvbn-core/Feedback.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn
6 | {
7 | ///
8 | /// Generates feedback based on the match results.
9 | ///
10 | internal static class Feedback
11 | {
12 | private static readonly FeedbackItem DefaultFeedback = new FeedbackItem
13 | {
14 | Warning = string.Empty,
15 | Suggestions = new[]
16 | {
17 | "Use a few words, avoid common phrases",
18 | "No need for symbols, digits, or uppercase letters",
19 | },
20 | };
21 |
22 | ///
23 | /// Gets feedback based on the provided score and matches.
24 | ///
25 | /// The score to assess.
26 | /// The sequence of matches to assess.
27 | /// Any warnings and suggestiongs about the password matches.
28 | public static FeedbackItem GetFeedback(int score, IEnumerable sequence)
29 | {
30 | if (!sequence.Any())
31 | return DefaultFeedback;
32 |
33 | if (score > 2)
34 | {
35 | return new FeedbackItem
36 | {
37 | Warning = string.Empty,
38 | Suggestions = new List(),
39 | };
40 | }
41 |
42 | var longestMatch = sequence.OrderBy(c => c.Token.Length).Last();
43 |
44 | var feedback = GetMatchFeedback(longestMatch, sequence.Count() == 1);
45 | var extraFeedback = "Add another word or two. Uncommon words are better.";
46 |
47 | if (feedback != null)
48 | {
49 | feedback.Suggestions.Insert(0, extraFeedback);
50 | }
51 | else
52 | {
53 | feedback = new FeedbackItem
54 | {
55 | Warning = string.Empty,
56 | Suggestions = new List { extraFeedback },
57 | };
58 | }
59 |
60 | return feedback;
61 | }
62 |
63 | private static FeedbackItem GetDictionaryMatchFeedback(DictionaryMatch match, bool isSoleMatch)
64 | {
65 | var warning = string.Empty;
66 |
67 | if (match.DictionaryName == "passwords")
68 | {
69 | if (isSoleMatch && !match.L33t && !match.Reversed)
70 | {
71 | if (match.Rank <= 10)
72 | warning = "This is a top-10 common password";
73 | else if (match.Rank <= 100)
74 | warning = "This is a top-100 common password";
75 | else
76 | warning = "This is a very common password";
77 | }
78 | else if (match.GuessesLog10 <= 4)
79 | {
80 | warning = "This is similar to a commonly used password";
81 | }
82 | }
83 | else if (match.DictionaryName == "english" && isSoleMatch)
84 | {
85 | warning = "A word by itself is easy to guess";
86 | }
87 | else if (match.DictionaryName == "surnames" || match.DictionaryName == "male_names" || match.DictionaryName == "female_names")
88 | {
89 | if (isSoleMatch)
90 | warning = "Names and surnames by themselves are easy to guess";
91 | else
92 | warning = "Common names and surnames are easy to guess";
93 | }
94 |
95 | var suggestions = new List();
96 | var word = match.Token;
97 | if (char.IsUpper(word[0]))
98 | suggestions.Add("Capitalization doesn't help very much");
99 | else if (word.All(c => char.IsUpper(c)) && word.ToLower() != word)
100 | suggestions.Add("All-uppercase is almost as easy to guess as all-lowercase");
101 |
102 | if (match.Reversed && match.Token.Length >= 4)
103 | suggestions.Add("Reversed words aren't much harder to guess");
104 | if (match.L33t)
105 | suggestions.Add("Predictable substitutions like '@' instead of 'a' don't help very much");
106 |
107 | return new FeedbackItem
108 | {
109 | Suggestions = suggestions,
110 | Warning = warning,
111 | };
112 | }
113 |
114 | private static FeedbackItem GetMatchFeedback(Match match, bool isSoleMatch)
115 | {
116 | switch (match.Pattern)
117 | {
118 | case "dictionary":
119 | return GetDictionaryMatchFeedback(match as DictionaryMatch, isSoleMatch);
120 |
121 | case "spatial":
122 | return new FeedbackItem
123 | {
124 | Warning = (match as SpatialMatch).Turns == 1 ? "Straight rows of keys are easy to guess" : "Short keyboard patterns are easy to guess",
125 | Suggestions = new List
126 | {
127 | "Use a longer keyboard pattern with more turns",
128 | },
129 | };
130 |
131 | case "repeat":
132 | return new FeedbackItem
133 | {
134 | Warning = (match as RepeatMatch).BaseToken.Length == 1 ? "Repeats like 'aaa' are easy to guess" : "Repeats like 'abcabcabc' are only slightly harder to guess than 'abc'",
135 | Suggestions = new List
136 | {
137 | "Avoid repeated words and characters",
138 | },
139 | };
140 |
141 | case "regex":
142 | if ((match as RegexMatch).RegexName == "recent_year")
143 | {
144 | return new FeedbackItem
145 | {
146 | Warning = "Recent years are easy to guess",
147 | Suggestions = new List
148 | {
149 | "Avoid recent years",
150 | "Avoid years that are associated with you",
151 | },
152 | };
153 | }
154 |
155 | break;
156 |
157 | case "date":
158 | return new FeedbackItem
159 | {
160 | Warning = "Dates are often easy to guess",
161 | Suggestions = new List
162 | {
163 | "Avoid dates and years that are associated with you",
164 | },
165 | };
166 | }
167 |
168 | return null;
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/zxcvbn-core/FeedbackItem.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Zxcvbn
4 | {
5 | ///
6 | /// Represents feedback that can be presented to the user.
7 | ///
8 | public class FeedbackItem
9 | {
10 | ///
11 | /// Gets the list of suggestions that can be presented.
12 | ///
13 | public IList Suggestions { get; internal set; }
14 |
15 | ///
16 | /// Gets the warning that should be the headline for the user.
17 | ///
18 | public string Warning { get; internal set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/zxcvbn-core/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "To match this project's coding style.")]
9 | [assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:Using directives should be placed correctly", Justification = "To match this project's coding style.")]
10 | [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "To match this project's coding style.")]
11 | [assembly: SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "This normalization isn't security critical")]
12 | [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "No header required.")]
13 | [assembly: SuppressMessage("Globalization", "CA1304:Specify CultureInfo", Justification = "Not supported in all the desired target libraries.")]
14 | [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not supported in all the desired target libraries.")]
15 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/DateMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Globalization;
5 | using System.Linq;
6 | using System.Text.RegularExpressions;
7 | using Zxcvbn.Matcher.Matches;
8 |
9 | namespace Zxcvbn.Matcher
10 | {
11 | ///
12 | /// Attempts to match a string with a date.
13 | ///
14 | ///
15 | /// A date is recognised if it is:
16 | ///
17 | /// - Any 3 tuple that starts or ends with a 2- or 4- digit year
18 | /// - With 2 or 0 separator characters
19 | /// - May be zero padded
20 | /// - Has a month between 1 and 12
21 | /// - Has a day between 1 and 31
22 | ///
23 | ///
24 | /// This isn't true date parsing. Invalid dates like 31 February will be allowed.
25 | ///
26 | internal class DateMatcher : IMatcher
27 | {
28 | private const int MaxYear = 2050;
29 | private const int MinYear = 1000;
30 |
31 | private static readonly ReadOnlyDictionary DateSplits = new ReadOnlyDictionary(new Dictionary
32 | {
33 | [4] = new[]
34 | {
35 | new[] { 1, 2 }, // 1 1 91
36 | new[] { 2, 3 }, // 91 1 1
37 | },
38 | [5] = new[]
39 | {
40 | new[] { 1, 3 }, // 1 11 91
41 | new[] { 2, 3 }, // 11 1 91
42 | },
43 | [6] = new[]
44 | {
45 | new[] { 1, 2 }, // 1 1 1991
46 | new[] { 2, 4 }, // 11 11 91
47 | new[] { 4, 5 }, // 1991 1 1
48 | },
49 | [7] = new[]
50 | {
51 | new[] { 1, 3 }, // 1 11 1991
52 | new[] { 2, 3 }, // 11 1 1991
53 | new[] { 4, 5 }, // 1991 1 11
54 | new[] { 4, 6 }, // 1991 11 1
55 | },
56 | [8] = new[]
57 | {
58 | new[] { 2, 4 }, // 11 11 1991
59 | new[] { 4, 6 }, // 1991 11 11
60 | },
61 | });
62 |
63 | private readonly Regex dateWithNoSeperater = new Regex("^\\d{4,8}$", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
64 |
65 | private readonly Regex dateWithSeperator = new Regex(
66 | @"^( \d{1,4} ) # day or month
67 | ( [\s/\\_.-] ) # separator
68 | ( \d{1,2} ) # month or day
69 | \2 # same separator
70 | ( \d{1,4} ) # year
71 | $", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
72 |
73 | ///
74 | /// Gets the reference year used to check for recent dates.
75 | ///
76 | public static int ReferenceYear { get; } = DateTime.Now.Year;
77 |
78 | ///
79 | /// Find date matches in .
80 | ///
81 | /// The passsord to check.
82 | /// An enumerable of date matches.
83 | public IEnumerable MatchPassword(string password)
84 | {
85 | var matches = new List();
86 |
87 | for (var i = 0; i <= password.Length - 4; i++)
88 | {
89 | for (var j = 4; i + j <= password.Length; j++)
90 | {
91 | var dateMatch = dateWithNoSeperater.Match(password); // Slashless dates
92 | if (!dateMatch.Success)
93 | continue;
94 |
95 | var candidates = new List();
96 |
97 | foreach (var split in DateSplits[dateMatch.Length])
98 | {
99 | var l = split[0];
100 | var m = split[1];
101 | var kLength = l;
102 | var lLength = m - l;
103 |
104 | var date = MapIntsToDate(new[]
105 | {
106 | int.Parse(dateMatch.Value.Substring(0, kLength), CultureInfo.InvariantCulture),
107 | int.Parse(dateMatch.Value.Substring(l, lLength), CultureInfo.InvariantCulture),
108 | int.Parse(dateMatch.Value.Substring(m), CultureInfo.InvariantCulture),
109 | });
110 |
111 | if (date != null)
112 | candidates.Add(date.Value);
113 | }
114 |
115 | if (candidates.Count == 0)
116 | continue;
117 |
118 | var bestCandidate = candidates[0];
119 |
120 | int Metric(LooseDate c) => Math.Abs(c.Year - ReferenceYear);
121 |
122 | var minDistance = Metric(bestCandidate);
123 |
124 | foreach (var candidate in candidates.Skip(1))
125 | {
126 | var distance = Metric(candidate);
127 | if (distance < minDistance)
128 | {
129 | minDistance = distance;
130 | bestCandidate = candidate;
131 | }
132 | }
133 |
134 | matches.Add(new DateMatch
135 | {
136 | Token = dateMatch.Value,
137 | i = i,
138 | j = j + i - 1,
139 | Separator = string.Empty,
140 | Year = bestCandidate.Year,
141 | Month = bestCandidate.Month,
142 | Day = bestCandidate.Day,
143 | });
144 | }
145 | }
146 |
147 | for (var i = 0; i <= password.Length - 6; i++)
148 | {
149 | for (var j = 6; i + j <= password.Length; j++)
150 | {
151 | var token = password.Substring(i, j);
152 | var match = dateWithSeperator.Match(token);
153 |
154 | if (!match.Success)
155 | continue;
156 |
157 | var date = MapIntsToDate(new[]
158 | {
159 | int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture),
160 | int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture),
161 | int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture),
162 | });
163 |
164 | if (date == null)
165 | continue;
166 |
167 | var m = new DateMatch
168 | {
169 | Token = token,
170 | i = i,
171 | j = j + i - 1,
172 | Separator = match.Groups[2].Value,
173 | Year = date.Value.Year,
174 | Month = date.Value.Month,
175 | Day = date.Value.Day,
176 | };
177 |
178 | matches.Add(m);
179 | }
180 | }
181 |
182 | var filteredMatches = matches.Where(m =>
183 | {
184 | foreach (var n in matches)
185 | {
186 | if (m == n)
187 | continue;
188 | if (n.i <= m.i && n.j >= m.j)
189 | return false;
190 | }
191 |
192 | return true;
193 | });
194 |
195 | return filteredMatches;
196 | }
197 |
198 | private static LooseDate? MapIntsToDate(IList vals)
199 | {
200 | if (vals[1] > 31 || vals[1] < 1)
201 | return null;
202 |
203 | var over12 = 0;
204 | var over31 = 0;
205 | var under1 = 0;
206 |
207 | foreach (var i in vals)
208 | {
209 | if ((i > 99 && i < MinYear) || i > MaxYear)
210 | return null;
211 |
212 | if (i > 31)
213 | over31++;
214 | if (i > 12)
215 | over12++;
216 | if (i < 1)
217 | under1++;
218 | }
219 |
220 | if (over31 >= 2 || over12 == 3 || under1 >= 2)
221 | return null;
222 |
223 | var possibleSplits = new[]
224 | {
225 | new[] { vals[2], vals[0], vals[1] },
226 | new[] { vals[0], vals[1], vals[2] },
227 | };
228 |
229 | foreach (var possibleSplit in possibleSplits)
230 | {
231 | if (possibleSplit[0] < MinYear || possibleSplit[0] > MaxYear)
232 | continue;
233 |
234 | var dayMonth = MapIntsToDayMonth(new[] { possibleSplit[1], possibleSplit[2] });
235 | if (dayMonth != null)
236 | return new LooseDate(possibleSplit[0], dayMonth.Value.Month, dayMonth.Value.Day);
237 | return null;
238 | }
239 |
240 | foreach (var possibleSplit in possibleSplits)
241 | {
242 | var dayMonth = MapIntsToDayMonth(new[] { possibleSplit[1], possibleSplit[2] });
243 | if (dayMonth == null) continue;
244 | var year = TwoToFourDigitYear(possibleSplit[0]);
245 | return new LooseDate(year, dayMonth.Value.Month, dayMonth.Value.Day);
246 | }
247 |
248 | return null;
249 | }
250 |
251 | private static LooseDate? MapIntsToDayMonth(IList vals)
252 | {
253 | var day = vals[0];
254 | var month = vals[1];
255 |
256 | if (day >= 1 && day <= 31 && month >= 1 && month <= 12)
257 | return new LooseDate(0, month, day);
258 |
259 | day = vals[1];
260 | month = vals[0];
261 |
262 | if (day >= 1 && day <= 31 && month >= 1 && month <= 12)
263 | return new LooseDate(0, month, day);
264 |
265 | return null;
266 | }
267 |
268 | private static int TwoToFourDigitYear(int year)
269 | {
270 | if (year > 99)
271 | return year;
272 | if (year > 50)
273 | return year + 1900;
274 | return year + 2000;
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/DictionaryMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Matcher
8 | {
9 | ///
10 | /// Attempts to match a string with a list of words.
11 | ///
12 | ///
13 | /// This matcher reads in a list of words (in frequency order) either from a built in resource, an external file
14 | /// or a list of strings. These must be in decreasing frequency order and contain one word per line with no additional information.
15 | ///
16 | internal class DictionaryMatcher : IMatcher
17 | {
18 | private readonly string dictionaryName;
19 | private readonly Dictionary rankedDictionary;
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The name provided to the dictionary used.
25 | /// The filename of the dictionary (full or relative path) or name of built-in dictionary.
26 | public DictionaryMatcher(string name, string wordListPath)
27 | {
28 | dictionaryName = name;
29 | rankedDictionary = BuildRankedDictionary(wordListPath);
30 | }
31 |
32 | ///
33 | /// Initializes a new instance of the class.
34 | /// Creates a new dictionary matcher from the passed in word list. If there is any frequency order then they should be in
35 | /// decreasing frequency order.
36 | ///
37 | /// The name provided to the dictionary used.
38 | /// The words to add to the dictionary.
39 | public DictionaryMatcher(string name, IEnumerable wordList)
40 | {
41 | dictionaryName = name;
42 |
43 | // Must ensure that the dictionary is using lowercase words only
44 | rankedDictionary = BuildRankedDictionary(wordList.Select(w => w.ToLower()));
45 | }
46 |
47 | ///
48 | /// Find dictionary matches in .
49 | ///
50 | /// The password to check.
51 | /// An enumerable of dictionary matches.
52 | public virtual IEnumerable MatchPassword(string password)
53 | {
54 | var passwordLower = password.ToLower();
55 | var length = passwordLower.Length;
56 | var matches = new List();
57 |
58 | for (var i = 0; i < length; i++)
59 | {
60 | for (var j = i; j < length; j++)
61 | {
62 | var passwordSub = passwordLower.Substring(i, j - i + 1);
63 | if (rankedDictionary.ContainsKey(passwordSub))
64 | {
65 | var match = new DictionaryMatch
66 | {
67 | i = i,
68 | j = j,
69 | Token = password.Substring(i, j - i + 1),
70 | MatchedWord = passwordSub,
71 | Rank = rankedDictionary[passwordSub],
72 | DictionaryName = dictionaryName,
73 | Reversed = false,
74 | L33t = false,
75 | };
76 |
77 | matches.Add(match);
78 | }
79 | }
80 | }
81 |
82 | return matches.OrderBy(m => m.i).ThenBy(m => m.j);
83 | }
84 |
85 | ///
86 | /// Loads the file .
87 | ///
88 | ///
89 | /// If the file is embedded in the assembly, this is loaded, otherwise is treated as a file path.
90 | /// Path to word list.
91 | /// A dictionary of the words and their associated rank.
92 | private static Dictionary BuildRankedDictionary(string wordListFile)
93 | {
94 | var lines = Utility.GetEmbeddedResourceLines($"Zxcvbn.Dictionaries.{wordListFile}") ?? File.ReadAllLines(wordListFile);
95 |
96 | return BuildRankedDictionary(lines);
97 | }
98 |
99 | private static Dictionary BuildRankedDictionary(IEnumerable wordList)
100 | {
101 | var dict = new Dictionary();
102 |
103 | var i = 1;
104 | foreach (var word in wordList)
105 | {
106 | var actualWord = word;
107 | if (actualWord.Contains(" "))
108 | actualWord = actualWord.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)[0];
109 |
110 | // The word list is assumed to be in increasing frequency order
111 | dict[actualWord] = i++;
112 | }
113 |
114 | return dict;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/IMatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Zxcvbn.Matcher.Matches;
3 |
4 | namespace Zxcvbn.Matcher
5 | {
6 | ///
7 | /// Represents a class that can look for matches in a password.
8 | ///
9 | internal interface IMatcher
10 | {
11 | ///
12 | /// Find matches in .
13 | ///
14 | /// The password to check.
15 | /// An enumerable of the matches.
16 | IEnumerable MatchPassword(string password);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/L33tMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Matcher
8 | {
9 | ///
10 | /// Attempts to match a string with a list of words, considering common l33t substitutions.
11 | ///
12 | internal class L33tMatcher : IMatcher
13 | {
14 | private readonly IEnumerable dictionaryMatchers;
15 |
16 | ///
17 | /// Initializes a new instance of the class.
18 | /// Create a l33t matcher that applies substitutions and then matches agains the passed in list of dictionary matchers.
19 | ///
20 | /// The list of dictionary matchers to check transformed passwords against.
21 | public L33tMatcher(IEnumerable dictionaryMatchers)
22 | {
23 | this.dictionaryMatchers = dictionaryMatchers;
24 | }
25 |
26 | ///
27 | /// Initializes a new instance of the class.
28 | /// Create a l33t matcher that applies substitutions and then matches agains a single dictionary matcher.
29 | ///
30 | /// The dictionary matcher to check transformed passwords against.
31 | public L33tMatcher(DictionaryMatcher dictionaryMatcher)
32 | : this(new List { dictionaryMatcher })
33 | {
34 | }
35 |
36 | ///
37 | /// Gets or sets the table of l33t transforms.
38 | ///
39 | internal static ReadOnlyDictionary L33tTable { get; set; } = new ReadOnlyDictionary(new Dictionary
40 | {
41 | ['a'] = new[] { '4', '@' },
42 | ['b'] = new[] { '8' },
43 | ['c'] = new[] { '(', '{', '[', '<' },
44 | ['e'] = new[] { '3' },
45 | ['g'] = new[] { '6', '9' },
46 | ['i'] = new[] { '1', '!', '|' },
47 | ['l'] = new[] { '1', '|', '7' },
48 | ['o'] = new[] { '0' },
49 | ['s'] = new[] { '$', '5' },
50 | ['t'] = new[] { '+', '7' },
51 | ['x'] = new[] { '%' },
52 | ['z'] = new[] { '2' },
53 | });
54 |
55 | ///
56 | /// Find l33t dictionary matches in .
57 | ///
58 | /// The password to check.
59 | /// An enumerable of dictionary matches.
60 | public IEnumerable MatchPassword(string password)
61 | {
62 | var result = new List();
63 |
64 | foreach (var sub in EnumerateSubtitutions(RelevantL33tSubtable(password)))
65 | {
66 | if (!sub.Any())
67 | break;
68 |
69 | var subbedPassword = TranslateString(sub, password);
70 |
71 | foreach (var matcher in dictionaryMatchers)
72 | {
73 | foreach (DictionaryMatch match in matcher.MatchPassword(subbedPassword))
74 | {
75 | var token = password.Substring(match.i, match.j - match.i + 1);
76 | if (token.ToLower().Equals(match.MatchedWord.ToLower()))
77 | continue;
78 |
79 | var matchSub = new Dictionary();
80 |
81 | foreach (var subbedChar in sub.Keys)
82 | {
83 | var chr = sub[subbedChar];
84 | if (token.Contains(subbedChar))
85 | matchSub[subbedChar] = chr;
86 | }
87 |
88 | match.L33t = true;
89 | match.Token = token;
90 | match.L33tSubs.Clear();
91 | foreach (var key in matchSub.Keys)
92 | match.L33tSubs[key] = matchSub[key];
93 |
94 | result.Add(match);
95 | }
96 | }
97 | }
98 |
99 | return result.Where(m => m.Token.Length > 1).OrderBy(m => m.i).ThenBy(m => m.j);
100 | }
101 |
102 | ///
103 | /// Enumerates the subtitutions in the provided table.
104 | ///
105 | /// The table to get the enumerations from.
106 | /// The enumeration of possible substitutions.
107 | internal static IEnumerable> EnumerateSubtitutions(ReadOnlyDictionary table)
108 | {
109 | return Helper(table.Keys, table).Select(s =>
110 | {
111 | var subDictionary = new Dictionary();
112 | foreach (var item in s)
113 | {
114 | var l33tChar = item.Item1;
115 | var chr = item.Item2;
116 | subDictionary[l33tChar] = chr;
117 | }
118 |
119 | return subDictionary;
120 | });
121 | }
122 |
123 | ///
124 | /// Prunes the L33T subtable to only the relevant bits.
125 | ///
126 | /// The password to consider.
127 | /// The pruned l33t table.
128 | internal static ReadOnlyDictionary RelevantL33tSubtable(string password)
129 | {
130 | var subtable = new Dictionary();
131 |
132 | foreach (var c in L33tTable.Keys)
133 | {
134 | var relevantSubs = L33tTable[c].Where(s => password.Contains(s));
135 | if (relevantSubs.Any())
136 | subtable[c] = relevantSubs.ToArray();
137 | }
138 |
139 | return new ReadOnlyDictionary(subtable);
140 | }
141 |
142 | private static List>> Deduplicate(List>> subs)
143 | {
144 | var result = new List>>();
145 | var members = new HashSet();
146 |
147 | foreach (var sub in subs)
148 | {
149 | var label = string.Join("-", sub
150 | .Select((kvp, i) => new Tuple, int>(kvp, i))
151 | .OrderBy(i => i.ToString())
152 | .Select((kvp, i) => kvp.ToString()));
153 |
154 | if (!members.Contains(label))
155 | {
156 | members.Add(label);
157 | result.Add(sub);
158 | }
159 | }
160 |
161 | return result;
162 | }
163 |
164 | private static List>> Helper(IEnumerable keys, ReadOnlyDictionary table, List>> subs = null)
165 | {
166 | if (subs == null)
167 | {
168 | subs = new List>>
169 | {
170 | new List>(),
171 | };
172 | }
173 |
174 | if (!keys.Any())
175 | return subs;
176 |
177 | var firstKey = keys.First();
178 | var restOfKeys = keys.Skip(1);
179 |
180 | var nextSubs = new List>>();
181 |
182 | foreach (var l33tChr in table[firstKey])
183 | {
184 | foreach (var sub in subs)
185 | {
186 | var dupL33tIndex = sub.FindIndex(s => s.Item1 == l33tChr);
187 |
188 | if (dupL33tIndex != -1)
189 | nextSubs.Add(sub);
190 |
191 | nextSubs.Add(new List>(sub)
192 | {
193 | new Tuple(l33tChr, firstKey),
194 | });
195 | }
196 | }
197 |
198 | subs = Deduplicate(nextSubs);
199 | return Helper(restOfKeys, table, subs);
200 | }
201 |
202 | private static string TranslateString(IReadOnlyDictionary charMap, string str)
203 | {
204 | // Make substitutions from the character map wherever possible
205 | return new string(str.Select(c => charMap.ContainsKey(c) ? charMap[c] : c).ToArray());
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/LooseDate.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher
2 | {
3 | ///
4 | /// Represents a possible date without the limitations of DateTime.
5 | ///
6 | internal struct LooseDate
7 | {
8 | ///
9 | /// Initializes a new instance of the struct.
10 | ///
11 | /// The year.
12 | /// The month.
13 | /// The day.
14 | public LooseDate(int year, int month, int day)
15 | {
16 | Year = year;
17 | Month = month;
18 | Day = day;
19 | }
20 |
21 | ///
22 | /// Gets the day in the date.
23 | ///
24 | public int Day { get; }
25 |
26 | ///
27 | /// Gets the month in the date.
28 | ///
29 | public int Month { get; }
30 |
31 | ///
32 | /// Gets the year in the date.
33 | ///
34 | public int Year { get; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/BruteForceMatch.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher.Matches
2 | {
3 | ///
4 | /// A match used to evaluate the brute-force strength of a password.
5 | ///
6 | ///
7 | public class BruteForceMatch : Match
8 | {
9 | ///
10 | /// Gets the name of the pattern matcher used to generate this match.
11 | ///
12 | public override string Pattern => "bruteforce";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/DateMatch.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher.Matches
2 | {
3 | ///
4 | /// A match identified as a possible date.
5 | ///
6 | ///
7 | public class DateMatch : Match
8 | {
9 | ///
10 | /// Gets the detected day.
11 | ///
12 | public int Day { get; internal set; }
13 |
14 | ///
15 | /// Gets the detected month.
16 | ///
17 | public int Month { get; internal set; }
18 |
19 | ///
20 | /// Gets the name of the pattern matcher used to generate this match.
21 | ///
22 | public override string Pattern => "date";
23 |
24 | ///
25 | /// Gets the separator detected in the date.
26 | ///
27 | ///
28 | /// If there is no separator then this will be an empty string.
29 | ///
30 | public string Separator { get; internal set; }
31 |
32 | ///
33 | /// Gets the detected year.
34 | ///
35 | public int Year { get; internal set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/DictionaryMatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 |
4 | namespace Zxcvbn.Matcher.Matches
5 | {
6 | ///
7 | /// A match identified as being in one of the provided dictionaries.
8 | ///
9 | ///
10 | public class DictionaryMatch : Match
11 | {
12 | ///
13 | /// Gets the base guesses associated with the matched word.
14 | ///
15 | public double BaseGuesses { get; internal set; }
16 |
17 | ///
18 | /// Gets the name of the dictionary containing the matched word.
19 | ///
20 | public string DictionaryName { get; internal set; }
21 |
22 | ///
23 | /// Gets a value indicating whether the matched word was found with l33t spelling.
24 | ///
25 | public bool L33t { get; internal set; }
26 |
27 | ///
28 | /// Gets the number of L33T variations associated with this match.
29 | ///
30 | public double L33tVariations { get; internal set; }
31 |
32 | ///
33 | /// Gets the dictionary word matched to.
34 | ///
35 | public string MatchedWord { get; internal set; }
36 |
37 | ///
38 | /// Gets the name of the pattern matcher used to generate this match.
39 | ///
40 | public override string Pattern => "dictionary";
41 |
42 | ///
43 | /// Gets the rank of the matched word in the dictionary.
44 | ///
45 | ///
46 | /// The most frequent word is has a rank of 1, with less frequent words having higher ranks.
47 | ///
48 | public int Rank { get; internal set; }
49 |
50 | ///
51 | /// Gets a value indicating whether the matched word was reversed.
52 | ///
53 | public bool Reversed { get; internal set; }
54 |
55 | ///
56 | /// Gets the l33t character mappings that are in use for this match.
57 | ///
58 | public IReadOnlyDictionary Sub => new ReadOnlyDictionary(L33tSubs);
59 |
60 | ///
61 | /// Gets the number of uppercase variations associated with this match.
62 | ///
63 | public double UppercaseVariations { get; internal set; }
64 |
65 | ///
66 | /// Gets or sets the l33t character mappings that are in use for this match.
67 | ///
68 | ///
69 | /// Modifiable version of Sub.
70 | ///
71 | internal Dictionary L33tSubs { get; set; } = new Dictionary();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/Match.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Zxcvbn.Matcher.Matches
4 | {
5 | ///
6 | /// A match identified by zxcvbn.
7 | ///
8 | public abstract class Match
9 | {
10 | ///
11 | /// Gets the number of guesses associated with this match.
12 | ///
13 | public double Guesses { get; internal set; }
14 |
15 | ///
16 | /// Gets log10(number of guesses) associated with this match.
17 | ///
18 | public double GuessesLog10 => Math.Log10(Guesses);
19 |
20 | #pragma warning disable IDE1006 // Naming Styles
21 | #pragma warning disable SA1300 // Element should begin with upper-case letter
22 |
23 | ///
24 | /// Gets the start index in the password string of the matched token.
25 | ///
26 | public int i { get; internal set; }
27 |
28 | ///
29 | /// Gets the end index in the password string of the matched token.
30 | ///
31 | public int j { get; internal set; }
32 |
33 | #pragma warning restore SA1300 // Element should begin with upper-case letter
34 | #pragma warning restore IDE1006 // Naming Styles
35 |
36 | ///
37 | /// Gets the name of the pattern matcher used to generate this match.
38 | ///
39 | public abstract string Pattern { get; }
40 |
41 | ///
42 | /// Gets the portion of the password that was matched.
43 | ///
44 | public string Token { get; internal set; }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/RegexMatch.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher.Matches
2 | {
3 | ///
4 | /// A match identified as matching one of the provided regular expressions.
5 | ///
6 | public class RegexMatch : Match
7 | {
8 | ///
9 | /// Gets the name of the pattern matcher used to generate this match.
10 | ///
11 | public override string Pattern => "regex";
12 |
13 | ///
14 | /// Gets the name of the regex that matched.
15 | ///
16 | public string RegexName { get; internal set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/RepeatMatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Zxcvbn.Matcher.Matches
4 | {
5 | ///
6 | /// A match identified as containing some repeated details.
7 | ///
8 | public class RepeatMatch : Match
9 | {
10 | ///
11 | /// Gets the base number guesses associated with the base matches.
12 | ///
13 | public double BaseGuesses { get; internal set; }
14 |
15 | ///
16 | /// Gets the base matches that are repeated.
17 | ///
18 | public IReadOnlyList BaseMatches => BaseMatchItems.AsReadOnly();
19 |
20 | ///
21 | /// Gets the base repeated token.
22 | ///
23 | public string BaseToken { get; internal set; }
24 |
25 | ///
26 | /// Gets the name of the pattern matcher used to generate this match.
27 | ///
28 | public override string Pattern => "repeat";
29 |
30 | ///
31 | /// Gets the number of times the base token is repeated.
32 | ///
33 | public int RepeatCount { get; internal set; }
34 |
35 | ///
36 | /// Gets or sets the base matches that are repeated.
37 | ///
38 | ///
39 | /// The editable backing of BaseMatches.
40 | ///
41 | internal List BaseMatchItems { get; set; } = new List();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/SequenceMatch.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher.Matches
2 | {
3 | ///
4 | /// A match identified as a predictable sequence of characters.
5 | ///
6 | public class SequenceMatch : Match
7 | {
8 | ///
9 | /// Gets a value indicating whether the match was found in ascending order (cdefg) or not (zyxw).
10 | ///
11 | public bool Ascending { get; internal set; }
12 |
13 | ///
14 | /// Gets the name of the pattern matcher used to generate this match.
15 | ///
16 | public override string Pattern => "sequence";
17 |
18 | ///
19 | /// Gets the name of the sequence that the match was found in.
20 | ///
21 | public string SequenceName { get; internal set; }
22 |
23 | ///
24 | /// Gets the size of the sequence the pattern was found in.
25 | ///
26 | public int SequenceSpace { get; internal set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Matches/SpatialMatch.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher.Matches
2 | {
3 | ///
4 | /// A match identified as a sequence of keys on a recognised keyboard.
5 | ///
6 | public class SpatialMatch : Match
7 | {
8 | ///
9 | /// Gets The name of the keyboard layout used to make the spatial match.
10 | ///
11 | public string Graph { get; internal set; }
12 |
13 | ///
14 | /// Gets the name of the pattern matcher used to generate this match.
15 | ///
16 | public override string Pattern => "spatial";
17 |
18 | ///
19 | /// Gets the number of shifted characters matched in the pattern.
20 | ///
21 | public int ShiftedCount { get; internal set; }
22 |
23 | ///
24 | /// Gets the number of turns made.
25 | ///
26 | public int Turns { get; internal set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/Point.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn.Matcher
2 | {
3 | ///
4 | /// A local implementation of Point to avoid referring to the graphical libraries.
5 | ///
6 | internal struct Point
7 | {
8 | ///
9 | /// Initializes a new instance of the struct.
10 | ///
11 | /// The x coordinate.
12 | /// The y coordinate.
13 | public Point(int x, int y)
14 | {
15 | X = x;
16 | Y = y;
17 | }
18 |
19 | ///
20 | /// Gets the x coordinate of the point.
21 | ///
22 | public int X { get; }
23 |
24 | ///
25 | /// Gets the y coordinate of the point.
26 | ///
27 | public int Y { get; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/RegexMatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.RegularExpressions;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Matcher
6 | {
7 | ///
8 | /// Attempts to match a string with a pre-defined regular expressions.
9 | ///
10 | internal class RegexMatcher : IMatcher
11 | {
12 | private readonly string matcherName;
13 | private readonly Regex matchRegex;
14 |
15 | ///
16 | /// Initializes a new instance of the class.
17 | /// Create a new regex pattern matcher.
18 | ///
19 | /// The regex pattern to match.
20 | /// The name to give this matcher ('pattern' in resulting matches).
21 | public RegexMatcher(string pattern, string matcherName = "regex")
22 | : this(new Regex(pattern), matcherName)
23 | {
24 | }
25 |
26 | ///
27 | /// Initializes a new instance of the class.
28 | /// Create a new regex pattern matcher.
29 | ///
30 | /// The regex object used to perform matching.
31 | /// The name to give this matcher ('pattern' in resulting matches).
32 | public RegexMatcher(Regex matchRegex, string matcherName = "regex")
33 | {
34 | this.matchRegex = matchRegex;
35 | this.matcherName = matcherName;
36 | }
37 |
38 | ///
39 | /// Find regex matches in .
40 | ///
41 | /// The password to check.
42 | /// An enumerable of regex matches.
43 | public IEnumerable MatchPassword(string password)
44 | {
45 | var reMatches = matchRegex.Matches(password);
46 |
47 | var pwMatches = new List();
48 |
49 | foreach (System.Text.RegularExpressions.Match rem in reMatches)
50 | {
51 | pwMatches.Add(new RegexMatch
52 | {
53 | RegexName = matcherName,
54 | i = rem.Index,
55 | j = rem.Index + rem.Length - 1,
56 | Token = password.Substring(rem.Index, rem.Length),
57 | });
58 | }
59 |
60 | return pwMatches;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/RepeatMatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text.RegularExpressions;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Matcher
6 | {
7 | ///
8 | /// Attempts to match repeated characters in the string.
9 | ///
10 | ///
11 | /// Repeats must be more than two characters long to count.
12 | ///
13 | internal class RepeatMatcher : IMatcher
14 | {
15 | ///
16 | /// Find repeat matches in .
17 | ///
18 | /// The password to check.
19 | /// An enumerable of repeat matches.
20 | public IEnumerable MatchPassword(string password)
21 | {
22 | var matches = new List();
23 | var greedy = "(.+)\\1+";
24 | var lazy = "(.+?)\\1+";
25 | var lazyAnchored = "^(.+?)\\1+$";
26 | var lastIndex = 0;
27 |
28 | while (lastIndex < password.Length)
29 | {
30 | var greedyMatch = Regex.Match(password.Substring(lastIndex), greedy);
31 | var lazyMatch = Regex.Match(password.Substring(lastIndex), lazy);
32 |
33 | if (!greedyMatch.Success) break;
34 |
35 | System.Text.RegularExpressions.Match match;
36 | string baseToken;
37 |
38 | if (greedyMatch.Length > lazyMatch.Length)
39 | {
40 | match = greedyMatch;
41 | baseToken = Regex.Match(match.Value, lazyAnchored).Groups[1].Value;
42 | }
43 | else
44 | {
45 | match = lazyMatch;
46 | baseToken = match.Groups[1].Value;
47 | }
48 |
49 | var i = lastIndex + match.Index;
50 | var j = lastIndex + match.Index + match.Length - 1;
51 |
52 | var baseAnalysis =
53 | PasswordScoring.MostGuessableMatchSequence(baseToken, Core.GetAllMatches(baseToken));
54 |
55 | var baseMatches = baseAnalysis.Sequence;
56 | var baseGuesses = baseAnalysis.Guesses;
57 |
58 | var m = new RepeatMatch
59 | {
60 | i = i,
61 | j = j,
62 | Token = match.Value,
63 | BaseToken = baseToken,
64 | BaseGuesses = baseGuesses,
65 | RepeatCount = match.Length / baseToken.Length,
66 | };
67 | m.BaseMatchItems.AddRange(baseMatches);
68 |
69 | matches.Add(m);
70 |
71 | lastIndex = j + 1;
72 | }
73 |
74 | return matches;
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/ReverseDictionaryMatcher.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Matcher
6 | {
7 | ///
8 | /// Attempts to match a reversed string with a list of words.
9 | ///
10 | ///
11 | internal class ReverseDictionaryMatcher : DictionaryMatcher
12 | {
13 | ///
14 | /// Initializes a new instance of the class.
15 | ///
16 | /// The name provided to the dictionary used.
17 | /// The filename of the dictionary (full or relative path) or name of built-in dictionary.
18 | public ReverseDictionaryMatcher(string name, string wordListPath)
19 | : base(name, wordListPath)
20 | {
21 | }
22 |
23 | ///
24 | /// Find reversed dictionary matches in .
25 | ///
26 | /// The password to check.
27 | /// An enumerable of dictionary matches.
28 | public override IEnumerable MatchPassword(string password)
29 | {
30 | var matches = base.MatchPassword(password.StringReverse()).ToList();
31 |
32 | foreach (var m in matches)
33 | {
34 | if (!(m is DictionaryMatch ma))
35 | continue;
36 |
37 | var i = ma.i;
38 | var j = ma.j;
39 |
40 | ma.Token = m.Token.StringReverse();
41 | ma.Reversed = true;
42 | ma.i = password.Length - 1 - j;
43 | ma.j = password.Length - 1 - i;
44 | }
45 |
46 | return matches.OrderBy(m => m.i).ThenBy(m => m.j);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/SequenceMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 | using Zxcvbn.Matcher.Matches;
6 |
7 | namespace Zxcvbn.Matcher
8 | {
9 | ///
10 | /// Attempts to match a string with a sequence of characters.
11 | ///
12 | internal class SequenceMatcher : IMatcher
13 | {
14 | private const int MaxDelta = 5;
15 |
16 | ///
17 | /// Find sequence matches in .
18 | ///
19 | /// The password to check.
20 | /// An enumerable of sequence matches.
21 | public IEnumerable MatchPassword(string password)
22 | {
23 | if (password.Length <= 1)
24 | return Enumerable.Empty();
25 |
26 | var result = new List();
27 |
28 | void Update(int i, int j, int delta)
29 | {
30 | if (j - i > 1 || Math.Abs(delta) == 1)
31 | {
32 | if (Math.Abs(delta) > 0 && Math.Abs(delta) <= MaxDelta)
33 | {
34 | var token = password.Substring(i, j - i + 1);
35 | string sequenceName;
36 | int sequenceSpace;
37 |
38 | if (Regex.IsMatch(token, "^[a-z]+$"))
39 | {
40 | sequenceName = "lower";
41 | sequenceSpace = 26;
42 | }
43 | else if (Regex.IsMatch(token, "^[A-Z]+$"))
44 | {
45 | sequenceName = "upper";
46 | sequenceSpace = 26;
47 | }
48 | else if (Regex.IsMatch(token, "^\\d+$"))
49 | {
50 | sequenceName = "digits";
51 | sequenceSpace = 10;
52 | }
53 | else
54 | {
55 | sequenceName = "unicode";
56 | sequenceSpace = 26;
57 | }
58 |
59 | result.Add(new SequenceMatch
60 | {
61 | i = i,
62 | j = j,
63 | Token = token,
64 | SequenceName = sequenceName,
65 | SequenceSpace = sequenceSpace,
66 | Ascending = delta > 0,
67 | });
68 | }
69 | }
70 | }
71 |
72 | var iIn = 0;
73 | int? lastDelta = null;
74 |
75 | for (var k = 1; k < password.Length; k++)
76 | {
77 | var deltaIn = password[k] - password[k - 1];
78 | if (lastDelta == null)
79 | lastDelta = deltaIn;
80 | if (deltaIn == lastDelta)
81 | continue;
82 |
83 | var jIn = k - 1;
84 | Update(iIn, jIn, lastDelta.Value);
85 | iIn = jIn;
86 | lastDelta = deltaIn;
87 | }
88 |
89 | Update(iIn, password.Length - 1, lastDelta.Value);
90 | return result;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/SpatialGraph.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// A graph detailing how keys are arranged on a typical keyboard.
10 | ///
11 | internal class SpatialGraph
12 | {
13 | ///
14 | /// Initializes a new instance of the class with the given name
15 | /// and based on the given layout.
16 | ///
17 | /// The name.
18 | /// The layout.
19 | /// if set to true the keys are slanted.
20 | public SpatialGraph(string name, string layout, bool slanted)
21 | {
22 | Name = name;
23 | BuildGraph(layout, slanted);
24 | }
25 |
26 | ///
27 | /// Gets the generated adjacency graph.
28 | ///
29 | public ReadOnlyDictionary> AdjacencyGraph { get; private set; }
30 |
31 | ///
32 | /// Gets the name of the generated graph.
33 | ///
34 | public string Name { get; }
35 |
36 | private static Point[] GetAlignedAdjacent(Point c)
37 | {
38 | var x = c.X;
39 | var y = c.Y;
40 |
41 | return new[] { new Point(x - 1, y), new Point(x - 1, y - 1), new Point(x, y - 1), new Point(x + 1, y - 1), new Point(x + 1, y), new Point(x + 1, y + 1), new Point(x, y + 1), new Point(x - 1, y + 1) };
42 | }
43 |
44 | private static Point[] GetSlantedAdjacent(Point c)
45 | {
46 | var x = c.X;
47 | var y = c.Y;
48 |
49 | return new[] { new Point(x - 1, y), new Point(x, y - 1), new Point(x + 1, y - 1), new Point(x + 1, y), new Point(x, y + 1), new Point(x - 1, y + 1) };
50 | }
51 |
52 | private void BuildGraph(string layout, bool slanted)
53 | {
54 | var tokens = layout.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
55 | var tokenSize = tokens[0].Length;
56 |
57 | // Put the characters in each keyboard cell into the map agains t their coordinates
58 | var positionTable = new Dictionary();
59 | var lines = layout.Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
60 | for (var y = 0; y < lines.Length; ++y)
61 | {
62 | var line = lines[y];
63 | var slant = slanted ? y - 1 : 0;
64 |
65 | foreach (var token in line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries))
66 | {
67 | var x = (line.IndexOf(token, StringComparison.Ordinal) - slant) / (tokenSize + 1);
68 | var p = new Point(x, y);
69 | positionTable[p] = token;
70 | }
71 | }
72 |
73 | var adjacencyGraph = new Dictionary>();
74 | foreach (var pair in positionTable)
75 | {
76 | var p = pair.Key;
77 | foreach (var c in pair.Value)
78 | {
79 | adjacencyGraph[c] = new List();
80 | var adjacentPoints = slanted ? GetSlantedAdjacent(p) : GetAlignedAdjacent(p);
81 |
82 | foreach (var adjacent in adjacentPoints)
83 | {
84 | // We want to include nulls so that direction is correspondent with index in the list
85 | adjacencyGraph[c].Add(positionTable.ContainsKey(adjacent) ? positionTable[adjacent] : null);
86 | }
87 | }
88 | }
89 |
90 | AdjacencyGraph = new ReadOnlyDictionary>(adjacencyGraph.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.AsReadOnly()));
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/zxcvbn-core/Matcher/SpatialMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text.RegularExpressions;
6 | using Zxcvbn.Matcher.Matches;
7 |
8 | namespace Zxcvbn.Matcher
9 | {
10 | ///
11 | /// Attempts to match a string against common keyboard layout patterns, considering shifted characters and changes in direction.
12 | ///
13 | internal class SpatialMatcher : IMatcher
14 | {
15 | private const string ShiftedRegex = "[~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?]";
16 |
17 | ///
18 | /// Gets the spatial graphs to match against.
19 | ///
20 | internal static ReadOnlyCollection SpatialGraphs { get; } = GenerateSpatialGraphs();
21 |
22 | ///
23 | /// Find spatial matches in .
24 | ///
25 | /// The password to check.
26 | /// An enumerable of spatial matches.
27 | public IEnumerable MatchPassword(string password)
28 | {
29 | return SpatialGraphs.SelectMany(g => SpatialMatch(g, password));
30 | }
31 |
32 | private static ReadOnlyCollection GenerateSpatialGraphs()
33 | {
34 | const string qwerty = @"
35 | `~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
36 | qQ wW eE rR tT yY uU iI oO pP [{ ]} \|
37 | aA sS dD fF gG hH jJ kK lL ;: '""
38 | zZ xX cC vV bB nN mM ,< .> /?
39 | ";
40 |
41 | const string dvorak = @"
42 | `~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) [{ ]}
43 | '"" ,< .> pP yY fF gG cC rR lL /? =+ \|
44 | aA oO eE uU iI dD hH tT nN sS -_
45 | ;: qQ jJ kK xX bB mM wW vV zZ
46 | ";
47 |
48 | const string keypad = @"
49 | / * -
50 | 7 8 9 +
51 | 4 5 6
52 | 1 2 3
53 | 0 .
54 | ";
55 |
56 | const string macKeypad = @"
57 | = / *
58 | 7 8 9 -
59 | 4 5 6 +
60 | 1 2 3
61 | 0 .
62 | ";
63 |
64 | return new List
65 | {
66 | new SpatialGraph("qwerty", qwerty, true),
67 | new SpatialGraph("dvorak", dvorak, true),
68 | new SpatialGraph("keypad", keypad, false),
69 | new SpatialGraph("mac_keypad", macKeypad, false),
70 | }.AsReadOnly();
71 | }
72 |
73 | private static IEnumerable SpatialMatch(SpatialGraph graph, string password)
74 | {
75 | var matches = new List();
76 |
77 | var i = 0;
78 | while (i < password.Length - 1)
79 | {
80 | var turns = 0;
81 | var shiftedCount = 0;
82 | int? lastDirection = null;
83 |
84 | var j = i + 1;
85 |
86 | if ((graph.Name == "qwerty" || graph.Name == "dvorak") && Regex.IsMatch(password[i].ToString(), ShiftedRegex))
87 | {
88 | shiftedCount = 1;
89 | }
90 |
91 | while (true)
92 | {
93 | var prevChar = password[j - 1];
94 | var found = false;
95 | var currentDirection = -1;
96 | var adjacents = graph.AdjacencyGraph.ContainsKey(prevChar) ? graph.AdjacencyGraph[prevChar] : Enumerable.Empty();
97 |
98 | if (j < password.Length)
99 | {
100 | var curChar = password[j].ToString();
101 | foreach (var adjacent in adjacents)
102 | {
103 | currentDirection++;
104 |
105 | if (adjacent == null)
106 | continue;
107 |
108 | if (adjacent.Contains(curChar))
109 | {
110 | found = true;
111 | var foundDirection = currentDirection;
112 | if (adjacent.IndexOf(curChar, StringComparison.Ordinal) == 1)
113 | {
114 | shiftedCount++;
115 | }
116 |
117 | if (lastDirection != foundDirection)
118 | {
119 | turns++;
120 | lastDirection = foundDirection;
121 | }
122 |
123 | break;
124 | }
125 | }
126 | }
127 |
128 | if (found)
129 | {
130 | j++;
131 | }
132 | else
133 | {
134 | if (j - i > 2)
135 | {
136 | matches.Add(new SpatialMatch()
137 | {
138 | i = i,
139 | j = j - 1,
140 | Token = password.Substring(i, j - i),
141 | Graph = graph.Name,
142 | Turns = turns,
143 | ShiftedCount = shiftedCount,
144 | });
145 | }
146 |
147 | i = j;
148 | break;
149 | }
150 | }
151 | }
152 |
153 | return matches;
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/zxcvbn-core/MostGuessableMatchResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Zxcvbn.Matcher.Matches;
3 |
4 | namespace Zxcvbn
5 | {
6 | ///
7 | /// Represents the most guessable match provided.
8 | ///
9 | internal class MostGuessableMatchResult
10 | {
11 | ///
12 | /// Gets or sets the guesses the match is estimated to need.
13 | ///
14 | public double Guesses { get; set; }
15 |
16 | ///
17 | /// Gets or sets the password being assessed.
18 | ///
19 | public string Password { get; set; }
20 |
21 | ///
22 | /// Gets or sets the score associated with the most guessable match.
23 | ///
24 | public int Score { get; set; }
25 |
26 | ///
27 | /// Gets or sets the matches identified.
28 | ///
29 | public IEnumerable Sequence { get; set; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/zxcvbn-core/OptimalValues.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Zxcvbn.Matcher.Matches;
3 |
4 | namespace Zxcvbn
5 | {
6 | ///
7 | /// Represents the optimal value when assessing the most guessible.
8 | ///
9 | internal class OptimalValues
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// The length of the password being assessed.
15 | public OptimalValues(int length)
16 | {
17 | for (var i = 0; i < length; i++)
18 | {
19 | G.Add(new Dictionary());
20 | Pi.Add(new Dictionary());
21 | M.Add(new Dictionary());
22 | }
23 | }
24 |
25 | ///
26 | /// Gets or sets the overall metric for the best guess.
27 | ///
28 | public List> G { get; set; } = new List>();
29 |
30 | ///
31 | /// Gets or sets the best match at a given length.
32 | ///
33 | public List> M { get; set; } = new List>();
34 |
35 | ///
36 | /// Gets or sets the the product term of the metric for the best guess.
37 | ///
38 | public List> Pi { get; set; } = new List>();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/zxcvbn-core/PasswordScoring.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Zxcvbn.Matcher.Matches;
5 | using Zxcvbn.Scoring;
6 |
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// Some useful shared functions used for evaluating passwords.
11 | ///
12 | internal static class PasswordScoring
13 | {
14 | private const int MinimumGuessesBeforeGrowingSequence = 10000;
15 |
16 | ///
17 | /// Caclulate binomial coefficient (i.e. nCk).
18 | ///
19 | /// n.
20 | /// k.
21 | /// Binomial coefficient.
22 | public static double Binomial(int n, int k)
23 | {
24 | if (k > n) return 0;
25 | if (k == 0) return 1;
26 |
27 | var r = 1.0;
28 | for (var d = 1; d <= k; ++d)
29 | {
30 | r *= n;
31 | r /= d;
32 | n -= 1;
33 | }
34 |
35 | return r;
36 | }
37 |
38 | ///
39 | /// Estimates the attempts required to guess the password.
40 | ///
41 | /// The match.
42 | /// The actual password.
43 | /// The guesses estimate.
44 | public static double EstimateGuesses(Match match, string password)
45 | {
46 | if (match.Guesses != 0)
47 | return match.Guesses;
48 |
49 | var minGuesses = 1.0;
50 | if (match.Token.Length < password.Length)
51 | {
52 | minGuesses = match.Token.Length == 1 ? BruteForceGuessesCalculator.MinSubmatchGuessesSingleCharacter : BruteForceGuessesCalculator.MinSubmatchGuessesMultiCharacter;
53 | }
54 |
55 | var guesses = 0.0;
56 |
57 | switch (match.Pattern)
58 | {
59 | case "bruteforce":
60 | guesses = BruteForceGuessesCalculator.CalculateGuesses(match as BruteForceMatch);
61 | break;
62 |
63 | case "date":
64 | guesses = DateGuessesCalculator.CalculateGuesses(match as DateMatch);
65 | break;
66 |
67 | case "dictionary":
68 | guesses = DictionaryGuessesCalculator.CalculateGuesses(match as DictionaryMatch);
69 | break;
70 |
71 | case "regex":
72 | guesses = RegexGuessesCalculator.CalculateGuesses(match as RegexMatch);
73 | break;
74 |
75 | case "repeat":
76 | guesses = RepeatGuessesCalculator.CalculateGuesses(match as RepeatMatch);
77 | break;
78 |
79 | case "sequence":
80 | guesses = SequenceGuessesCalculator.CalculateGuesses(match as SequenceMatch);
81 | break;
82 |
83 | case "spatial":
84 | guesses = SpatialGuessesCalculator.CalculateGuesses(match as SpatialMatch);
85 | break;
86 | }
87 |
88 | match.Guesses = Math.Max(guesses, minGuesses);
89 | return match.Guesses;
90 | }
91 |
92 | ///
93 | /// Identifies the most guessable match in the sequence.
94 | ///
95 | /// The password.
96 | /// The matches.
97 | /// if set to true, will exclude additive matches (for unit testing only).
98 | /// A summary on the most testable match.
99 | public static MostGuessableMatchResult MostGuessableMatchSequence(string password, IEnumerable matches, bool excludeAdditive = false)
100 | {
101 | var matchesByJ = Enumerable.Range(0, password.Length).Select(i => new List()).ToList();
102 | foreach (var m in matches)
103 | matchesByJ[m.j].Add(m);
104 |
105 | var optimal = new OptimalValues(password.Length);
106 |
107 | for (var k = 0; k < password.Length; k++)
108 | {
109 | foreach (var m in matchesByJ[k])
110 | {
111 | if (m.i > 0)
112 | {
113 | foreach (var l in optimal.M[m.i - 1].Keys)
114 | {
115 | Update(password, optimal, m, l + 1, excludeAdditive);
116 | }
117 | }
118 | else
119 | {
120 | Update(password, optimal, m, 1, excludeAdditive);
121 | }
122 | }
123 |
124 | BruteforceUpdate(password, optimal, k, excludeAdditive);
125 | }
126 |
127 | var optimalMatchSequence = Unwind(optimal, password.Length);
128 | var optimalL = optimalMatchSequence.Count;
129 |
130 | double guesses;
131 |
132 | if (password.Length == 0)
133 | guesses = 1;
134 | else
135 | guesses = optimal.G[password.Length - 1][optimalL];
136 |
137 | return new MostGuessableMatchResult
138 | {
139 | Guesses = guesses,
140 | Password = password,
141 | Sequence = optimalMatchSequence,
142 | Score = 0,
143 | };
144 | }
145 |
146 | private static void BruteforceUpdate(string password, OptimalValues optimal, int k, bool excludeAdditive)
147 | {
148 | Update(password, optimal, MakeBruteforceMatch(password, 0, k), 1, excludeAdditive);
149 |
150 | for (var i = 1; i <= k; i++)
151 | {
152 | var m = MakeBruteforceMatch(password, i, k);
153 | var obj = optimal.M[i - 1];
154 |
155 | foreach (var l in obj.Keys)
156 | {
157 | var lastM = obj[l];
158 | if (lastM.Pattern == "bruteforce")
159 | continue;
160 | Update(password, optimal, m, l + 1, excludeAdditive);
161 | }
162 | }
163 | }
164 |
165 | private static double Factorial(double n)
166 | {
167 | if (n < 2)
168 | return 1;
169 | var f = 1.0;
170 |
171 | for (var i = 2; i <= n; i++)
172 | f *= i;
173 |
174 | return f;
175 | }
176 |
177 | private static BruteForceMatch MakeBruteforceMatch(string password, int i, int j)
178 | {
179 | return new BruteForceMatch
180 | {
181 | Token = password.Substring(i, j - i + 1),
182 | i = i,
183 | j = j,
184 | };
185 | }
186 |
187 | private static List Unwind(OptimalValues optimal, int n)
188 | {
189 | var optimalMatchSequence = new List();
190 | var k = n - 1;
191 | var l = -1;
192 | var g = double.PositiveInfinity;
193 | if (k < 0)
194 | {
195 | return optimalMatchSequence;
196 | }
197 |
198 | foreach (var candidateL in optimal.G[k].Keys)
199 | {
200 | var candidateG = optimal.G[k][candidateL];
201 |
202 | if (candidateG < g)
203 | {
204 | l = candidateL;
205 | g = candidateG;
206 | }
207 | }
208 |
209 | while (k >= 0)
210 | {
211 | var m = optimal.M[k][l];
212 | optimalMatchSequence.Insert(0, m);
213 | k = m.i - 1;
214 | l--;
215 | }
216 |
217 | return optimalMatchSequence;
218 | }
219 |
220 | private static void Update(string password, OptimalValues optimal, Match m, int l, bool excludeAdditive)
221 | {
222 | var k = m.j;
223 | var pi = EstimateGuesses(m, password);
224 | if (l > 1)
225 | pi *= optimal.Pi[m.i - 1][l - 1];
226 |
227 | var g = Factorial(l) * pi;
228 | if (!excludeAdditive)
229 | g += Math.Pow(MinimumGuessesBeforeGrowingSequence, l - 1);
230 |
231 | foreach (var competingL in optimal.G[k].Keys)
232 | {
233 | var competingG = optimal.G[k][competingL];
234 | if (competingL > l)
235 | continue;
236 | if (competingG <= g)
237 | return;
238 | }
239 |
240 | optimal.G[k][l] = g;
241 | optimal.M[k][l] = m;
242 | optimal.Pi[k][l] = pi;
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/zxcvbn-core/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("zxcvbn-core-test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000d095cca1c805b0a5d66873a225ccc3d721948e40f123ba80ecf6b89e3be35dacea2a1341505eb45fb10f549f7b0c8f9e2000e01e2105929949bdcf23170010103c02d087fecfc67bb7e6d0be6a861ea3390a2bd3a7f3a09409e81a5378463b54fec11263ddeafc6b2685d94e93cf249dd02be648470aac661bd4a115b3d7cd7")]
4 |
--------------------------------------------------------------------------------
/zxcvbn-core/Result.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn
6 | {
7 | ///
8 | /// The results of zxcvbn's password analysis.
9 | ///
10 | public class Result
11 | {
12 | ///
13 | /// Gets the number of milliseconds that zxcvbn took to calculate results for this password.
14 | ///
15 | public long CalcTime { get; internal set; }
16 |
17 | ///
18 | /// Gets An estimation of the crack times for this password in seconds.
19 | ///
20 | public CrackTimes CrackTime { get; internal set; }
21 |
22 | ///
23 | /// Gets a set of string for the crack times.
24 | ///
25 | public CrackTimesDisplay CrackTimeDisplay { get; internal set; }
26 |
27 | ///
28 | /// Gets the feedback for the user about their password.
29 | ///
30 | public FeedbackItem Feedback { get; internal set; }
31 |
32 | ///
33 | /// Gets the number of guesses the password is estimated to need.
34 | ///
35 | public double Guesses { get; internal set; }
36 |
37 | ///
38 | /// Gets log10(the number of guesses) the password is estimated to need.
39 | ///
40 | public double GuessesLog10 => Math.Log10(Guesses);
41 |
42 | ///
43 | /// Gets the sequence of matches that were used to assess the password.
44 | ///
45 | public IEnumerable MatchSequence { get; internal set; }
46 |
47 | ///
48 | /// Gets the password that was used to generate these results.
49 | ///
50 | public string Password { get; internal set; }
51 |
52 | ///
53 | /// Gets a score from 0 to 4 (inclusive), with 0 being least secure and 4 being most secure, calculated from the nubmer of guesses estimated to be needed.
54 | ///
55 | public int Score { get; internal set; }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/BruteForceGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Zxcvbn.Matcher.Matches;
3 |
4 | namespace Zxcvbn.Scoring
5 | {
6 | ///
7 | /// Estimates the number of attempts needed to brute-force the password.
8 | ///
9 | internal class BruteForceGuessesCalculator
10 | {
11 | ///
12 | /// The minimum submatch guesses for a multi character string.
13 | ///
14 | public const double MinSubmatchGuessesMultiCharacter = 50;
15 |
16 | ///
17 | /// The minimum submatch guesses for a single character.
18 | ///
19 | public const double MinSubmatchGuessesSingleCharacter = 10;
20 |
21 | private const double BruteforceCardinality = 10;
22 |
23 | ///
24 | /// Estimates the attempts required to guess the password.
25 | ///
26 | /// The match.
27 | /// The guesses estimate.
28 | public static double CalculateGuesses(BruteForceMatch match)
29 | {
30 | var guesses = Math.Pow(BruteforceCardinality, match.Token.Length);
31 | if (double.IsPositiveInfinity(guesses))
32 | guesses = double.MaxValue;
33 |
34 | var minGuesses = match.Token.Length == 1 ? MinSubmatchGuessesSingleCharacter + 1 : MinSubmatchGuessesMultiCharacter + 1;
35 |
36 | return Math.Max(guesses, minGuesses);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/DateGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Zxcvbn.Matcher;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Scoring
6 | {
7 | ///
8 | /// Estimates the number of attempts needed to guess the date.
9 | ///
10 | internal class DateGuessesCalculator
11 | {
12 | ///
13 | /// The minimum distance between the reference date and the provided date.
14 | ///
15 | public const int MinimumYearSpace = 20;
16 |
17 | ///
18 | /// Estimates the attempts required to guess the password.
19 | ///
20 | /// The match.
21 | /// The guesses estimate.
22 | public static double CalculateGuesses(DateMatch match)
23 | {
24 | var yearSpace = Math.Max(Math.Abs(match.Year - DateMatcher.ReferenceYear), MinimumYearSpace);
25 |
26 | var guesses = yearSpace * 365.0;
27 | if (!string.IsNullOrEmpty(match.Separator))
28 | guesses *= 4;
29 |
30 | return guesses;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/DictionaryGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Zxcvbn.Matcher.Matches;
4 |
5 | namespace Zxcvbn.Scoring
6 | {
7 | ///
8 | /// Estimates the number of attempts needed to dictionary search for the password.
9 | ///
10 | internal class DictionaryGuessesCalculator
11 | {
12 | ///
13 | /// Estimates the attempts required to guess the password.
14 | ///
15 | /// The match.
16 | /// The guesses estimate.
17 | public static double CalculateGuesses(DictionaryMatch match)
18 | {
19 | match.BaseGuesses = match.Rank;
20 | match.UppercaseVariations = UppercaseVariations(match.Token);
21 | match.L33tVariations = L33tVariations(match);
22 | var reversedVariations = match.Reversed ? 2 : 1;
23 |
24 | return match.BaseGuesses * match.UppercaseVariations * match.L33tVariations * reversedVariations;
25 | }
26 |
27 | ///
28 | /// Calculates the number of l33t variations in the word.
29 | ///
30 | /// The match.
31 | /// The number of possible variations.
32 | internal static double L33tVariations(DictionaryMatch match)
33 | {
34 | if (!match.L33t)
35 | return 1;
36 |
37 | var variations = 1.0;
38 |
39 | foreach (var subbed in match.Sub.Keys)
40 | {
41 | var unsubbed = match.Sub[subbed];
42 | var s = match.Token.ToLower().Count(c => c == subbed);
43 | var u = match.Token.ToLower().Count(c => c == unsubbed);
44 |
45 | if (s == 0 || u == 0)
46 | {
47 | variations *= 2;
48 | }
49 | else
50 | {
51 | var p = Math.Min(u, s);
52 | var possibilities = 0.0;
53 | for (var i = 1; i <= p; i++)
54 | possibilities += PasswordScoring.Binomial(u + s, i);
55 | variations *= possibilities;
56 | }
57 | }
58 |
59 | return variations;
60 | }
61 |
62 | ///
63 | /// Calculates the number of uppercase variations in the word.
64 | ///
65 | /// The token.
66 | /// The number of possible variations.
67 | internal static double UppercaseVariations(string token)
68 | {
69 | if (token.All(c => char.IsLower(c)) || token.ToLower() == token)
70 | return 1;
71 |
72 | if ((char.IsUpper(token.First()) && token.Skip(1).All(c => char.IsLower(c)))
73 | || token.All(c => char.IsUpper(c))
74 | || (char.IsUpper(token.Last()) && token.Take(token.Length - 1).All(c => char.IsLower(c))))
75 | return 2;
76 |
77 | var u = token.Count(c => char.IsUpper(c));
78 | var l = token.Count(c => char.IsLower(c));
79 | var variations = 0.0;
80 |
81 | for (var i = 1; i <= Math.Min(u, l); i++)
82 | variations += PasswordScoring.Binomial(u + l, i);
83 |
84 | return variations;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/RegexGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using Zxcvbn.Matcher;
4 | using Zxcvbn.Matcher.Matches;
5 |
6 | namespace Zxcvbn.Scoring
7 | {
8 | ///
9 | /// Estimates the number of attempts needed to guess the value picked out by regular expression.
10 | ///
11 | internal class RegexGuessesCalculator
12 | {
13 | ///
14 | /// Estimates the attempts required to guess the password.
15 | ///
16 | /// The match.
17 | /// The guesses estimate.
18 | public static double CalculateGuesses(RegexMatch match)
19 | {
20 | switch (match.RegexName)
21 | {
22 | case "recent_year":
23 | var yearSpace = Math.Abs(int.Parse(match.Token, CultureInfo.InvariantCulture) - DateMatcher.ReferenceYear);
24 | yearSpace = Math.Max(yearSpace, DateGuessesCalculator.MinimumYearSpace);
25 | return yearSpace;
26 |
27 | default:
28 | throw new InvalidOperationException();
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/RepeatGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using Zxcvbn.Matcher.Matches;
2 |
3 | namespace Zxcvbn.Scoring
4 | {
5 | ///
6 | /// Estimates the number of attempts needed to guesses the number of repeats.
7 | ///
8 | internal class RepeatGuessesCalculator
9 | {
10 | ///
11 | /// Estimates the attempts required to guess the password.
12 | ///
13 | /// The match.
14 | /// The guesses estimate.
15 | public static double CalculateGuesses(RepeatMatch match)
16 | {
17 | return match.BaseGuesses * match.RepeatCount;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/SequenceGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Zxcvbn.Matcher.Matches;
3 |
4 | namespace Zxcvbn.Scoring
5 | {
6 | ///
7 | /// Estimates the number of attempts needed to guess the sequence.
8 | ///
9 | internal class SequenceGuessesCalculator
10 | {
11 | private static readonly List ObviousStartCharacters = new List
12 | {
13 | 'a', 'A', 'z', 'Z', '0', '1', '9',
14 | };
15 |
16 | ///
17 | /// Estimates the attempts required to guess the password.
18 | ///
19 | /// The match.
20 | /// The guesses estimate.
21 | public static double CalculateGuesses(SequenceMatch match)
22 | {
23 | double baseGuesses;
24 |
25 | if (ObviousStartCharacters.Contains(match.Token[0]))
26 | {
27 | baseGuesses = 4;
28 | }
29 | else
30 | {
31 | if (char.IsDigit(match.Token[0]))
32 | baseGuesses = 10;
33 | else
34 | baseGuesses = 26;
35 | }
36 |
37 | if (!match.Ascending)
38 | baseGuesses *= 2;
39 |
40 | return baseGuesses * match.Token.Length;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/zxcvbn-core/Scoring/SpatialGuessesCalculator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Zxcvbn.Matcher;
4 | using Zxcvbn.Matcher.Matches;
5 |
6 | namespace Zxcvbn.Scoring
7 | {
8 | ///
9 | /// Estimates the number of attempts needed to guess the spatial pattern.
10 | ///
11 | internal static class SpatialGuessesCalculator
12 | {
13 | ///
14 | /// The average number of adjacent characters on a keyboard.
15 | ///
16 | internal static readonly double KeyboardAverageDegree = (int)Math.Round(CalculateAverageDegree(SpatialMatcher.SpatialGraphs.First(s => s.Name == "qwerty")));
17 |
18 | ///
19 | /// The number of starting positions on a keyboard.
20 | ///
21 | internal static readonly int KeyboardStartingPositions = SpatialMatcher.SpatialGraphs.First(s => s.Name == "qwerty").AdjacencyGraph.Keys.Count;
22 |
23 | ///
24 | /// The average number of adjacent characters on a keypad.
25 | ///
26 | internal static readonly double KeypadAverageDegree = (int)Math.Round(CalculateAverageDegree(SpatialMatcher.SpatialGraphs.First(s => s.Name == "keypad")));
27 |
28 | ///
29 | /// The number of starting positions on a keypad.
30 | ///
31 | internal static readonly int KeypadStartingPositions = SpatialMatcher.SpatialGraphs.First(s => s.Name == "keypad").AdjacencyGraph.Keys.Count;
32 |
33 | ///
34 | /// Estimates the attempts required to guess the password.
35 | ///
36 | /// The match.
37 | /// The guesses estimate.
38 | public static double CalculateGuesses(SpatialMatch match)
39 | {
40 | int s;
41 | double d;
42 | if (match.Graph == "qwerty" || match.Graph == "dvorak")
43 | {
44 | s = KeyboardStartingPositions;
45 | d = KeyboardAverageDegree;
46 | }
47 | else
48 | {
49 | s = KeypadStartingPositions;
50 | d = KeypadAverageDegree;
51 | }
52 |
53 | double guesses = 0;
54 | var l = match.Token.Length;
55 | var t = match.Turns;
56 |
57 | for (var i = 2; i <= l; i++)
58 | {
59 | var possibleTurns = Math.Min(t, i - 1);
60 | for (var j = 1; j <= possibleTurns; j++)
61 | {
62 | guesses += PasswordScoring.Binomial(i - 1, j - 1) * s * Math.Pow(d, j);
63 | }
64 | }
65 |
66 | if (match.ShiftedCount > 0)
67 | {
68 | var shifted = match.ShiftedCount;
69 | var unshifted = match.Token.Length - match.ShiftedCount;
70 | if (shifted == 0 || unshifted == 0)
71 | {
72 | guesses *= 2;
73 | }
74 | else
75 | {
76 | double variations = 0;
77 | for (var i = 1; i <= Math.Min(shifted, unshifted); i++)
78 | {
79 | variations += PasswordScoring.Binomial(shifted + unshifted, i);
80 | }
81 |
82 | guesses *= variations;
83 | }
84 | }
85 |
86 | return guesses;
87 | }
88 |
89 | private static double CalculateAverageDegree(SpatialGraph graph)
90 | {
91 | var average = 0.0;
92 | foreach (var key in graph.AdjacencyGraph.Keys)
93 | {
94 | average += graph.AdjacencyGraph[key].Count(s => s != null);
95 | }
96 |
97 | average /= graph.AdjacencyGraph.Keys.Count;
98 | return average;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/zxcvbn-core/TimeEstimates.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Zxcvbn
4 | {
5 | ///
6 | /// Estimates how long a password will take to crack under various conditions.
7 | ///
8 | internal static class TimeEstimates
9 | {
10 | ///
11 | /// Calculates the estimated attack times.
12 | ///
13 | /// The number of guesses required.
14 | /// A summary of the estimated attack times.
15 | public static AttackTimes EstimateAttackTimes(double guesses)
16 | {
17 | var crackTimesSeconds = new CrackTimes
18 | {
19 | OfflineFastHashing1e10PerSecond = guesses / 1e10,
20 | OfflineSlowHashing1e4PerSecond = guesses / 1e4,
21 | OnlineNoThrottling10PerSecond = guesses / 10,
22 | OnlineThrottling100PerHour = guesses / (100.0 / 3600),
23 | };
24 | var crackTimesDisplay = new CrackTimesDisplay
25 | {
26 | OfflineFastHashing1e10PerSecond = DisplayTime(crackTimesSeconds.OfflineFastHashing1e10PerSecond),
27 | OfflineSlowHashing1e4PerSecond = DisplayTime(crackTimesSeconds.OfflineSlowHashing1e4PerSecond),
28 | OnlineNoThrottling10PerSecond = DisplayTime(crackTimesSeconds.OnlineNoThrottling10PerSecond),
29 | OnlineThrottling100PerHour = DisplayTime(crackTimesSeconds.OnlineThrottling100PerHour),
30 | };
31 |
32 | return new AttackTimes
33 | {
34 | CrackTimesDisplay = crackTimesDisplay,
35 | CrackTimesSeconds = crackTimesSeconds,
36 | Score = GuessesToScore(guesses),
37 | };
38 | }
39 |
40 | private static string DisplayTime(double seconds)
41 | {
42 | const double minute = 60;
43 | const double hour = minute * 60;
44 | const double day = hour * 24;
45 | const double month = day * 31;
46 | const double year = month * 12;
47 | const double century = year * 100;
48 |
49 | int? displayNumber = null;
50 | string displayString;
51 |
52 | if (seconds < 1)
53 | return "less than a second";
54 | if (seconds < minute)
55 | {
56 | displayNumber = (int)Math.Round(seconds);
57 | displayString = $"{displayNumber} second";
58 | }
59 | else if (seconds < hour)
60 | {
61 | displayNumber = (int)Math.Round(seconds / minute);
62 | displayString = $"{displayNumber} minute";
63 | }
64 | else if (seconds < day)
65 | {
66 | displayNumber = (int)Math.Round(seconds / hour);
67 | displayString = $"{displayNumber} hour";
68 | }
69 | else if (seconds < month)
70 | {
71 | displayNumber = (int)Math.Round(seconds / day);
72 | displayString = $"{displayNumber} day";
73 | }
74 | else if (seconds < year)
75 | {
76 | displayNumber = (int)Math.Round(seconds / month);
77 | displayString = $"{displayNumber} month";
78 | }
79 | else if (seconds < century)
80 | {
81 | displayNumber = (int)Math.Round(seconds / year);
82 | displayString = $"{displayNumber} year";
83 | }
84 | else
85 | {
86 | displayString = "centuries";
87 | }
88 |
89 | if (displayNumber.HasValue && displayNumber != 1)
90 | displayString += "s";
91 |
92 | return displayString;
93 | }
94 |
95 | private static int GuessesToScore(double guesses)
96 | {
97 | const int delta = 5;
98 | if (guesses < 1e3 + delta)
99 | return 0;
100 | if (guesses < 1e6 + delta)
101 | return 1;
102 | if (guesses < 1e8 + delta)
103 | return 2;
104 | if (guesses < 1e10 + delta)
105 | return 3;
106 | return 4;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/zxcvbn-core/Utility.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace Zxcvbn
7 | {
8 | ///
9 | /// A few useful extension methods used through the Zxcvbn project.
10 | ///
11 | internal static class Utility
12 | {
13 | ///
14 | /// Returns a list of the lines of text from an embedded resource in the assembly.
15 | ///
16 | /// The name of the resource to get the contents from.
17 | /// An enumerable of lines of text in the resource or null if the resource does not exist.
18 | public static IEnumerable GetEmbeddedResourceLines(string resourceName)
19 | {
20 | var asm = typeof(Utility).GetTypeInfo().Assembly;
21 | if (!asm.GetManifestResourceNames().Contains(resourceName)) return null; // Not an embedded resource
22 |
23 | var lines = new List();
24 |
25 | using (var stream = asm.GetManifestResourceStream(resourceName))
26 | using (var text = new StreamReader(stream))
27 | {
28 | while (!text.EndOfStream)
29 | {
30 | lines.Add(text.ReadLine());
31 | }
32 | }
33 |
34 | return lines;
35 | }
36 |
37 | ///
38 | /// Reverse a string in one call.
39 | ///
40 | /// String to reverse.
41 | /// String in reverse.
42 | public static string StringReverse(this string str)
43 | {
44 | return new string(str.Reverse().ToArray());
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/zxcvbn-core/zxcvbn-core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0;net461;netstandard1.3
4 | zxcvbn-core
5 | mickford;Tony Richards (trichards57);Dan Wheeler;DropBox
6 | zxcvbn-core
7 | C#/.NET port of Dan Wheeler/DropBox's Zxcvbn JS password strength estimation library. Updated for .Net Core.
8 | https://github.com/trichards57/zxcvbn-cs
9 | https://github.com/trichards57/zxcvbn-cs.git
10 | password;strength;validation;zxcvbn
11 | Copyright (c) 2012 Dropbox, Inc. Copyright (c) 2020-2021 Tony Richards
12 | Zxcvbn
13 | $(TargetDir)zxcvbn-core.xml
14 | true
15 | zxcvbn-strong-name-key.snk
16 | true
17 | true
18 | snupkg
19 | true
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 | True
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/zxcvbn-core/zxcvbn-strong-name-key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trichards57/zxcvbn-cs/2e680287ca64bde8ee5366a17ac0d2af7a11d8ea/zxcvbn-core/zxcvbn-strong-name-key.snk
--------------------------------------------------------------------------------
/zxcvbn-cs.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32014.148
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{71211407-F148-4CD8-A83E-2E0399B8D97D}"
7 | ProjectSection(SolutionItems) = preProject
8 | .editorconfig = .editorconfig
9 | .gitignore = .gitignore
10 | azure-pipelines.yml = azure-pipelines.yml
11 | GitVersion.yml = GitVersion.yml
12 | LICENSE = LICENSE
13 | nuget.exe = nuget.exe
14 | README.md = README.md
15 | tests.bat = tests.bat
16 | EndProjectSection
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core", "zxcvbn-core\zxcvbn-core.csproj", "{5D3F6D54-EB79-4980-B09A-5C72136E8B4A}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core-test", "zxcvbn-core-test\zxcvbn-core-test.csproj", "{65B256F9-4874-4D6F-9A46-D881FAB0215B}"
21 | EndProject
22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "zxcvbn-core-list-builder", "zxcvbn-core-list-builder\zxcvbn-core-list-builder.csproj", "{80BA1964-B98A-4D34-95B8-DFA51CD3A378}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Release|Any CPU = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {5D3F6D54-EB79-4980-B09A-5C72136E8B4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {5D3F6D54-EB79-4980-B09A-5C72136E8B4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {5D3F6D54-EB79-4980-B09A-5C72136E8B4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {5D3F6D54-EB79-4980-B09A-5C72136E8B4A}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {65B256F9-4874-4D6F-9A46-D881FAB0215B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {65B256F9-4874-4D6F-9A46-D881FAB0215B}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {65B256F9-4874-4D6F-9A46-D881FAB0215B}.Release|Any CPU.ActiveCfg = Release|Any CPU
37 | {65B256F9-4874-4D6F-9A46-D881FAB0215B}.Release|Any CPU.Build.0 = Release|Any CPU
38 | {80BA1964-B98A-4D34-95B8-DFA51CD3A378}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {80BA1964-B98A-4D34-95B8-DFA51CD3A378}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {80BA1964-B98A-4D34-95B8-DFA51CD3A378}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {80BA1964-B98A-4D34-95B8-DFA51CD3A378}.Release|Any CPU.Build.0 = Release|Any CPU
42 | EndGlobalSection
43 | GlobalSection(SolutionProperties) = preSolution
44 | HideSolutionNode = FALSE
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {FF2D379C-4018-4EF8-B032-89FCE8B2A37D}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------