1 /**********************************************************************************
2 * C implementation of the zxcvbn password strength estimation method.
3 * Copyright (c) 2015, Tony Evans
6 * Redistribution and use in source and binary forms, with or without modification, are
7 * permitted provided that the following conditions are met:
9 * 1. Redistributions of source code must retain the above copyright notice, this list
10 * of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright notice, this
13 * list of conditions and the following disclaimer in the documentation and/or other
14 * materials provided with the distribution.
16 * 3. Neither the name of the copyright holder nor the names of its contributors may be
17 * used to endorse or promote products derived from this software without specific
18 * prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
23 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
26 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31 **********************************************************************************/
41 #if defined(USE_FILE_IO) || !defined(__cplusplus)
48 /* For pre-compiled headers under windows */
55 /* Minimum number of characters in a incrementing/decrementing sequence match */
56 #define MIN_SEQUENCE_LEN 3
58 /* Year range for data matching */
62 /* Minimum number of characters in a spatial matching sequence */
63 #define MIN_SPATIAL_LEN 3
65 /* Minimum number of characters in a repeat sequence match */
66 #define MIN_REPEAT_LEN 2
68 /* Additional entropy to add when password is made of multiple matches. Use different
69 * amounts depending on whether the match is at the end of the password, or in the
70 * middle. If the match is at the begining then there is no additional entropy.
72 #define MULTI_END_ADDITION 1.0
73 #define MULTI_MID_ADDITION 1.75
75 /*################################################################################*
76 *################################################################################*
77 * Begin utility function code
78 *################################################################################*
79 *################################################################################*/
81 /**********************************************************************************
82 * Binomial coefficient function. Uses method described at
83 * http://blog.plover.com/math/choose.html
85 static double nCk(int n
, int k
)
94 for(d
= 1; d
<= k
; ++d
)
102 /**********************************************************************************
103 * Binary search function to find a character in a string.
105 * Ch The character to find
106 * Ents The string to search
107 * NumEnts The number character groups in the string Ents
108 * SizeEnt The size of each character group.
109 * Returns a pointer to the found character, or null if not found.
111 static const uint8_t *CharBinSearch(uint8_t Ch
, const uint8_t *Ents
, unsigned int NumEnts
, unsigned int SizeEnt
)
115 const uint8_t *Mid
= Ents
+ (NumEnts
>> 1) * SizeEnt
;
123 Ents
= Mid
+ SizeEnt
;
131 /**********************************************************************************
132 * Calculate potential number of different characters in the passed string.
134 * Str The string of characters
135 * Len The maximum number of characters to scan
136 * Returns the potential number of different characters in the string.
138 static int Cardinality(const uint8_t *Str
, int Len
)
147 if (islower(c
)) Types
|= 1; /* Lowercase letter */
148 else if (isupper(c
)) Types
|= 2; /* Uppercase letter */
149 else if (isdigit(c
)) Types
|= 4; /* Numeric digit */
150 else if (c
<= 0x7F) Types
|= 8; /* Punctuation character */
151 else Types
|= 16; /* Other (Unicode?) */
154 if (Types
& 1) Card
+= 26;
155 if (Types
& 2) Card
+= 26;
156 if (Types
& 4) Card
+= 10;
157 if (Types
& 8) Card
+= 33;
158 if (Types
& 16) Card
+= 100;
162 /**********************************************************************************
163 * Allocate a ZxcMatch_t struct, clear it to zero
165 static ZxcMatch_t
*AllocMatch()
167 ZxcMatch_t
*p
= MallocFn(ZxcMatch_t
, 1);
168 memset(p
, 0, sizeof *p
);
172 /**********************************************************************************
173 * Add new match struct to linked list of matches. List ordered with shortest at
174 * head of list. Note: passed new match struct in parameter Nu may be de allocated.
176 static void AddResult(ZxcMatch_t
**HeadRef
, ZxcMatch_t
*Nu
, int MaxLen
)
178 /* Adjust the entropy to be used for calculations depending on whether the passed match is
179 * at the begining, middle or end of the password
183 if (Nu
->Length
>= MaxLen
)
184 Nu
->MltEnpy
= Nu
->Entrpy
+ MULTI_END_ADDITION
* log(2.0);
186 Nu
->MltEnpy
= Nu
->Entrpy
+ MULTI_MID_ADDITION
* log(2.0);
189 Nu
->MltEnpy
= Nu
->Entrpy
;
191 /* Find the correct insert point */
192 while(*HeadRef
&& ((*HeadRef
)->Length
< Nu
->Length
))
193 HeadRef
= &((*HeadRef
)->Next
);
195 /* Add new entry or replace existing */
196 if (*HeadRef
&& ((*HeadRef
)->Length
== Nu
->Length
))
198 /* New entry has same length as existing, so one of them needs discarding */
199 if ((*HeadRef
)->MltEnpy
<= Nu
->MltEnpy
)
201 /* Existing entry has lower entropy - keep it, discard new entry */
206 /* New entry has lower entropy - replace existing entry */
207 Nu
->Next
= (*HeadRef
)->Next
;
214 /* New entry has different length, so add it */
220 /**********************************************************************************
221 * See if the match is repeated. If it is then add a new repeated match to the results.
223 static void AddMatchRepeats(ZxcMatch_t
**Result
, ZxcMatch_t
*Match
, const uint8_t *Passwd
, int MaxLen
)
225 int Len
= Match
->Length
;
226 const uint8_t *Rpt
= Passwd
+ Len
;
229 while(MaxLen
>= (Len
* RepeatCount
))
231 if (strncmp((const char *)Passwd
, (const char *)Rpt
, Len
) == 0)
234 ZxcMatch_t
*p
= AllocMatch();
235 p
->Entrpy
= Match
->Entrpy
+ log(RepeatCount
);
236 p
->Type
= (ZxcTypeMatch_t
)(Match
->Type
+ MULTIPLE_MATCH
);
237 p
->Length
= Len
* RepeatCount
;
238 p
->Begin
= Match
->Begin
;
239 AddResult(Result
, p
, MaxLen
);
248 /*################################################################################*
249 *################################################################################*
250 * Begin dictionary matching code
251 *################################################################################*
252 *################################################################################*/
255 /* Use dictionary data from file */
257 #if defined(USE_FILE_IO) || !defined(__cplusplus)
258 /* Use the FILE streams from stdio.h */
260 typedef FILE *FileHandle
;
262 #define MyOpenFile(f, name) (f = fopen(name, "rb"))
263 #define MyReadFile(f, buf, bytes) (fread(buf, 1, bytes, f) == (bytes))
264 #define MyCloseFile(f) fclose(f)
268 /* Use the C++ iostreams */
269 typedef std::ifstream FileHandle
;
271 static inline void MyOpenFile(FileHandle
& f
, const char *Name
)
273 f
.open(Name
, std::ifstream::in
| std::ifstream::binary
);
275 static inline bool MyReadFile(FileHandle
& f
, void *Buf
, unsigned int Num
)
277 return (bool)f
.read((char *)Buf
, Num
);
279 static inline void MyCloseFile(FileHandle
& f
)
286 /* Include file contains the CRC of the dictionary data file. Used to detect corruption */
288 #include "dict-crc.h"
290 #define MAX_DICT_FILE_SIZE (100+WORD_FILE_SIZE)
291 #define CHK_INIT 0xffffffffffffffffULL
293 /* Static table used for the crc implementation. */
294 static const uint64_t CrcTable
[16] =
296 0x0000000000000000ULL
, 0x7d08ff3b88be6f81ULL
, 0xfa11fe77117cdf02ULL
, 0x8719014c99c2b083ULL
,
297 0xdf7adabd7a6e2d6fULL
, 0xa2722586f2d042eeULL
, 0x256b24ca6b12f26dULL
, 0x5863dbf1e3ac9decULL
,
298 0x95ac9329ac4bc9b5ULL
, 0xe8a46c1224f5a634ULL
, 0x6fbd6d5ebd3716b7ULL
, 0x12b5926535897936ULL
,
299 0x4ad64994d625e4daULL
, 0x37deb6af5e9b8b5bULL
, 0xb0c7b7e3c7593bd8ULL
, 0xcdcf48d84fe75459ULL
302 static const unsigned int MAGIC
= 'z' + ('x'<< 8) + ('c' << 16) + ('v' << 24);
304 static unsigned int NumNodes
, NumChildLocs
, NumRanks
, NumWordEnd
, NumChildMaps
;
305 static unsigned int SizeChildMapEntry
, NumLargeCounts
, NumSmallCounts
, SizeCharSet
;
307 static unsigned int *DictNodes
;
308 static uint8_t *WordEndBits
;
309 static unsigned int *ChildLocs
;
310 static unsigned short *Ranks
;
311 static uint8_t *ChildMap
;
312 static uint8_t *EndCountLge
;
313 static uint8_t *EndCountSml
;
314 static char *CharSet
;
316 /**********************************************************************************
317 * Calculate the CRC-64 of passed data.
319 * Crc The initial or previous CRC value
320 * v Pointer to the data to add to CRC calculation
321 * Len Length of the passed data
322 * Returns the updated CRC value.
324 static uint64_t CalcCrc64(uint64_t Crc
, const void *v
, unsigned int Len
)
326 const uint8_t *Data
= (const unsigned char *)v
;
329 Crc
= CrcTable
[(Crc
^ (*Data
>> 0)) & 0x0f] ^ (Crc
>> 4);
330 Crc
= CrcTable
[(Crc
^ (*Data
>> 4)) & 0x0f] ^ (Crc
>> 4);
336 /**********************************************************************************
337 * Read the dictionary data from file.
339 * Filename Name of the file to read.
340 * Returns 1 on success, 0 on error
342 int ZxcvbnInit(const char *Filename
)
345 uint64_t Crc
= CHK_INIT
;
348 MyOpenFile(f
, Filename
);
351 unsigned int i
, DictSize
;
353 /* Get magic number */
354 if (!MyReadFile(f
, &i
, sizeof i
))
357 /* Get header data */
358 if (!MyReadFile(f
, &NumNodes
, sizeof NumNodes
))
360 if (!MyReadFile(f
, &NumChildLocs
, sizeof NumChildLocs
))
362 if (!MyReadFile(f
, &NumRanks
, sizeof NumRanks
))
364 if (!MyReadFile(f
, &NumWordEnd
, sizeof NumWordEnd
))
366 if (!MyReadFile(f
, &NumChildMaps
, sizeof NumChildMaps
))
368 if (!MyReadFile(f
, &SizeChildMapEntry
, sizeof SizeChildMapEntry
))
370 if (!MyReadFile(f
, &NumLargeCounts
, sizeof NumLargeCounts
))
372 if (!MyReadFile(f
, &NumSmallCounts
, sizeof NumSmallCounts
))
374 if (!MyReadFile(f
, &SizeCharSet
, sizeof SizeCharSet
))
377 /* Validate the header data */
378 if (NumNodes
>= (1<<17))
380 if (NumChildLocs
>= (1<<BITS_CHILD_MAP_INDEX
))
382 if (NumChildMaps
>= (1<<BITS_CHILD_PATT_INDEX
))
384 if ((SizeChildMapEntry
*8) < SizeCharSet
)
386 if (NumLargeCounts
>= (1<<9))
388 if (NumSmallCounts
!= NumNodes
)
396 Crc
= CalcCrc64(Crc
, &i
, sizeof i
);
397 Crc
= CalcCrc64(Crc
, &NumNodes
, sizeof NumNodes
);
398 Crc
= CalcCrc64(Crc
, &NumChildLocs
, sizeof NumChildLocs
);
399 Crc
= CalcCrc64(Crc
, &NumRanks
, sizeof NumRanks
);
400 Crc
= CalcCrc64(Crc
, &NumWordEnd
, sizeof NumWordEnd
);
401 Crc
= CalcCrc64(Crc
, &NumChildMaps
, sizeof NumChildMaps
);
402 Crc
= CalcCrc64(Crc
, &SizeChildMapEntry
, sizeof SizeChildMapEntry
);
403 Crc
= CalcCrc64(Crc
, &NumLargeCounts
, sizeof NumLargeCounts
);
404 Crc
= CalcCrc64(Crc
, &NumSmallCounts
, sizeof NumSmallCounts
);
405 Crc
= CalcCrc64(Crc
, &SizeCharSet
, sizeof SizeCharSet
);
407 DictSize
= NumNodes
*sizeof(*DictNodes
) + NumChildLocs
*sizeof(*ChildLocs
) + NumRanks
*sizeof(*Ranks
) +
408 NumWordEnd
+ NumChildMaps
*SizeChildMapEntry
+ NumLargeCounts
+ NumSmallCounts
+ SizeCharSet
;
409 if (DictSize
< MAX_DICT_FILE_SIZE
)
411 DictNodes
= MallocFn(unsigned int, DictSize
/ sizeof(unsigned int) + 1);
412 if (!MyReadFile(f
, DictNodes
, DictSize
))
423 Crc
= CalcCrc64(Crc
, DictNodes
, DictSize
);
424 if (memcmp(&Crc
, WordCheck
, sizeof Crc
))
432 /* Set pointers to the data */
433 ChildLocs
= DictNodes
+ NumNodes
;
434 Ranks
= (unsigned short *)(ChildLocs
+ NumChildLocs
);
435 WordEndBits
= (unsigned char *)(Ranks
+ NumRanks
);
436 ChildMap
= (unsigned char*)(WordEndBits
+ NumWordEnd
);
437 EndCountLge
= ChildMap
+ NumChildMaps
*SizeChildMapEntry
;
438 EndCountSml
= EndCountLge
+ NumLargeCounts
;
439 CharSet
= (char *)EndCountSml
+ NumSmallCounts
;
440 CharSet
[SizeCharSet
] = 0;
445 /**********************************************************************************
446 * Free the data allocated by ZxcvbnInit().
457 /* Include the source file containing the dictionary data */
458 #include "dict-src.h"
462 /**********************************************************************************
463 * Leet conversion strings
465 /* String of normal chars that could be given as leet chars in the password */
466 static const uint8_t L33TChr
[] = "abcegilostxz";
468 /* String of leet,normal,normal char triples. Used to convert supplied leet char to normal. */
469 static const uint8_t L33TCnv
[] = "!i $s %x (c +t 0o 1il2z 3e 4a 5s 6g 7lt8b 9g <c @a [c {c |il";
470 #define LEET_NORM_MAP_SIZE 3
472 /* Struct holding additional data on the word match */
475 int Rank
; /* Rank of word in dictionary */
476 int Caps
; /* Number of capital letters */
477 int Lower
; /* Number of lower case letters */
478 int NumLeet
; /* Total number of leeted characters */
479 uint8_t Leeted
[sizeof L33TChr
]; /* Number of leeted chars for each char found in L33TChr */
480 uint8_t UnLeet
[sizeof L33TChr
]; /* Number of normal chars for each char found in L33TChr */
483 /* Struct holding working data for the word match */
493 uint8_t Leeted
[sizeof L33TChr
];
494 uint8_t UnLeet
[sizeof L33TChr
];
495 uint8_t LeetCnv
[sizeof L33TCnv
/ LEET_NORM_MAP_SIZE
+ 1];
497 uint8_t PossChars
[CHARSET_SIZE
];
500 /**********************************************************************************
501 * Given a map entry create a string of all possible characters for following to
504 static int ListPossibleChars(uint8_t *List
, const uint8_t *Map
)
506 unsigned int i
, j
, k
;
508 for(k
= i
= 0; i
< SizeChildMapEntry
; ++i
, ++Map
)
515 for(j
= 0; j
< 8; ++j
)
519 *List
++ = CharSet
[k
];
529 /**********************************************************************************
530 * Increment count of each char that could be leeted.
532 static void AddLeetChr(uint8_t c
, int IsLeet
, uint8_t *Leeted
, uint8_t *UnLeet
)
534 const uint8_t *p
= CharBinSearch(c
, L33TChr
, sizeof L33TChr
- 1, 1);
554 /**********************************************************************************
555 * Given details of a word match, update it with the entropy (as natural log of
556 * number of possiblities)
558 static void DictionaryEntropy(ZxcMatch_t
*m
, DictMatchInfo_t
*Extra
, const uint8_t *Pwd
)
561 /* Add allowance for uppercase letters */
564 if (Extra
->Caps
== m
->Length
)
566 /* All uppercase, common case so only 1 bit */
569 else if ((Extra
->Caps
== 1) && (isupper(*Pwd
) || isupper(Pwd
[m
->Length
- 1])))
571 /* Only first or last uppercase, also common so only 1 bit */
576 /* Get number of combinations of lowercase, uppercase letters */
577 int Up
= Extra
->Caps
;
578 int Lo
= Extra
->Lower
;
582 for(Lo
+= Up
; i
>= 0; --i
)
588 /* Add allowance for using leet substitution */
593 for(i
= sizeof Extra
->Leeted
- 1; i
>= 0; --i
)
595 int Sb
= Extra
->Leeted
[i
];
598 int Un
= Extra
->UnLeet
[i
];
599 int j
= m
->Length
- Extra
->NumLeet
;
600 if ((j
>= 0) && (Un
> j
))
605 for(Un
+= Sb
; j
>= 0; --j
)
607 double z
= nCk(Un
, j
);
618 /* Add entropy due to word's rank */
619 e
+= log((double)Extra
->Rank
);
623 /**********************************************************************************
624 * Function that does the word matching
626 static void DoDictMatch(const uint8_t *Passwd
, int Start
, int MaxLen
, DictWork_t
*Wrk
, ZxcMatch_t
**Result
, DictMatchInfo_t
*Extra
, int Lev
)
629 uint8_t TempLeet
[LEET_NORM_MAP_SIZE
];
630 int Ord
= Wrk
->Ordinal
;
631 int Caps
= Wrk
->Caps
;
632 int Lower
= Wrk
->Lower
;
633 unsigned int NodeLoc
= Wrk
->StartLoc
;
634 uint8_t *PossChars
= Wrk
->PossChars
;
635 int NumPossChrs
= Wrk
->NumPossChrs
;
636 const uint8_t *Pwd
= Passwd
;
637 uint32_t NodeData
= DictNodes
[NodeLoc
];
639 for(Len
= 0; *Passwd
&& (Len
< MaxLen
); ++Len
, ++Passwd
)
645 if (!Len
&& Wrk
->First
)
651 /* Get char and set of possible chars at current point in word. */
654 Bmap
= ChildMap
+ (NodeData
& ((1<<BITS_CHILD_PATT_INDEX
)-1)) * SizeChildMapEntry
;
655 NumPossChrs
= ListPossibleChars(PossChars
, Bmap
);
657 /* Make it lowercase and update lowercase, uppercase counts */
667 /* See if current char is a leet and can be converted */
668 q
= CharBinSearch(c
, L33TCnv
, sizeof L33TCnv
/ LEET_NORM_MAP_SIZE
, LEET_NORM_MAP_SIZE
);
671 /* Found, see if used before */
673 unsigned int i
= (q
- L33TCnv
) / LEET_NORM_MAP_SIZE
;
676 /* Used before, so limit characters to try */
678 TempLeet
[1] = Wrk
->LeetCnv
[i
];
682 for(j
= 0; (*q
> ' ') && (j
< LEET_NORM_MAP_SIZE
); ++j
, ++q
)
684 const uint8_t *r
= CharBinSearch(*q
, PossChars
, NumPossChrs
, 1);
687 /* valid conversion from leet */
690 w
.StartLoc
= NodeLoc
;
696 w
.NumPossChrs
= NumPossChrs
;
697 memcpy(w
.PossChars
, PossChars
, sizeof w
.PossChars
);
701 AddLeetChr(*r
, -1, w
.Leeted
, w
.UnLeet
);
703 DoDictMatch(Pwd
, Passwd
- Pwd
, MaxLen
- Len
, &w
, Result
, Extra
, Lev
+1);
709 q
= CharBinSearch(c
, PossChars
, NumPossChrs
, 1);
712 /* Found the char as a normal char */
713 if (CharBinSearch(c
, L33TChr
, sizeof L33TChr
- 1, 1))
715 /* Char matches, but also a normal equivalent to a leet char */
716 AddLeetChr(c
, 0, Wrk
->Leeted
, Wrk
->UnLeet
);
721 /* No match for char - return */
724 /* Add all the end counts of the child nodes before the one that matches */
725 x
= (q
- Wrk
->PossChars
);
726 y
= (NodeData
>> BITS_CHILD_PATT_INDEX
) & ((1 << BITS_CHILD_MAP_INDEX
) - 1);
727 NodeLoc
= ChildLocs
[x
+y
];
730 unsigned int Cloc
= ChildLocs
[w
+y
];
731 z
= EndCountSml
[Cloc
];
732 if (Cloc
< NumLargeCounts
)
733 z
+= EndCountLge
[Cloc
]*256;
737 /* Move to next node */
738 NodeData
= DictNodes
[NodeLoc
];
739 if (WordEndBits
[NodeLoc
>> 3] & (1<<(NodeLoc
& 7)))
741 /* Word matches, save result */
746 v
= (v
& ((1 << 15) - 1)) * 4 + (1 << 15);
749 Extra
->Lower
= Lower
;
750 for(x
= 0, y
= sizeof Extra
->Leeted
- 1; y
>= 0; --y
)
754 memcpy(Extra
->UnLeet
, Wrk
->UnLeet
, sizeof Extra
->UnLeet
);
755 memcpy(Extra
->Leeted
, Wrk
->Leeted
, sizeof Extra
->Leeted
);
759 p
->Type
= DICT_LEET_MATCH
;
761 p
->Type
= DICTIONARY_MATCH
;
762 p
->Length
= Wrk
->PwdLength
+ Len
+ 1;
763 p
->Begin
= Wrk
->Begin
;
764 DictionaryEntropy(p
, Extra
, Pwd
);
765 AddMatchRepeats(Result
, p
, Pwd
, MaxLen
);
766 AddResult(Result
, p
, MaxLen
);
772 /**********************************************************************************
773 * Try to match password part with user supplied dictionary words
775 * Result Pointer head of linked list used to store results
776 * Words Array of pointers to dictionary words
777 * Passwd The start of the password
778 * Start Where in the password to start attempting to match
779 * MaxLen Maximum number characters to consider
781 static void UserMatch(ZxcMatch_t
**Result
, const char *Words
[], const uint8_t *Passwd
, int Start
, int MaxLen
)
787 for(Rank
= 0; Words
[Rank
]; ++Rank
)
789 DictMatchInfo_t Extra
;
790 uint8_t LeetChr
[sizeof L33TCnv
/ LEET_NORM_MAP_SIZE
+ 1];
796 const uint8_t *Wrd
= (const uint8_t *)(Words
[Rank
]);
797 const uint8_t *Pwd
= Passwd
;
798 memset(Extra
.Leeted
, 0, sizeof Extra
.Leeted
);
799 memset(Extra
.UnLeet
, 0, sizeof Extra
.UnLeet
);
800 memset(LeetChr
, 0, sizeof LeetChr
);
804 uint8_t d
= tolower(*Wrd
++);
815 /* See if current char is a leet and can be converted */
816 q
= CharBinSearch(c
, L33TCnv
, sizeof L33TCnv
/ LEET_NORM_MAP_SIZE
, LEET_NORM_MAP_SIZE
);
819 /* Found, see if used before */
821 unsigned int i
= (q
- L33TCnv
) / LEET_NORM_MAP_SIZE
;
824 /* Used before, so limit characters to try */
826 TempLeet
[1] = LeetChr
[i
];
831 for(j
= 0; (*q
> ' ') && (j
< LEET_NORM_MAP_SIZE
); ++j
, ++q
)
839 AddLeetChr(c
, 1, Extra
.Leeted
, Extra
.UnLeet
);
853 /* Found the char as a normal char */
854 if (CharBinSearch(c
, L33TChr
, sizeof L33TChr
- 1, 1))
856 /* Char matches, but also a normal equivalent to a leet char */
857 AddLeetChr(c
, 0, Extra
.Leeted
, Extra
.UnLeet
);
874 ZxcMatch_t
*p
= AllocMatch();
876 p
->Type
= USER_MATCH
;
878 p
->Type
= USER_LEET_MATCH
;
883 Extra
.Lower
= Lowers
;
884 Extra
.NumLeet
= Leets
;
886 DictionaryEntropy(p
, &Extra
, Passwd
);
887 AddMatchRepeats(Result
, p
, Passwd
, MaxLen
);
888 AddResult(Result
, p
, MaxLen
);
893 /**********************************************************************************
894 * Try to match password part with the dictionary words
896 * Result Pointer head of linked list used to store results
897 * Passwd The start of the password
898 * Start Where in the password to start attempting to match
899 * MaxLen Maximum number characters to consider
901 static void DictionaryMatch(ZxcMatch_t
**Result
, const uint8_t *Passwd
, int Start
, int MaxLen
)
904 DictMatchInfo_t Extra
;
906 memset(&Extra
, 0, sizeof Extra
);
907 memset(&Wrk
, 0, sizeof Wrk
);
909 Wrk
.StartLoc
= ROOT_NODE_LOC
;
911 DoDictMatch(Passwd
+Start
, 0, MaxLen
, &Wrk
, Result
, &Extra
, 0);
915 /*################################################################################*
916 *################################################################################*
917 * Begin keyboard spatial sequence matching code
918 *################################################################################*
919 *################################################################################*/
921 /* Struct to hold information on a keyboard layout */
922 typedef struct Keyboard
925 const uint8_t *Shifts
;
932 /* Struct to hold information on the match */
938 } SpatialMatchInfo_t
;
940 /* Shift mapping, characters in pairs: first is shifted, second un-shifted. */
941 static const uint8_t UK_Shift
[] = "!1\"2$4%5&7(9)0*8:;<,>.?/@'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~#€4£3¬`";
942 static const uint8_t US_Shift
[] = "!1\"'#3$4%5&7(9)0*8:;<,>.?/@2AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~`";
945 /* Neighour tables */
946 static const uint8_t UK_Qwerty
[48*7] =
948 /* key, left, up-left, up-right, right, down-right, down-left */
949 '#', '\'',']', 0, 0, 0, 0, '\'',';', '[', ']', '#', 0, '/',
950 ',', 'm', 'k', 'l', '.', 0, 0, '-', '0', 0, 0, '-', 'p', 'o',
951 '.', ',', 'l', ';', '/', 0, 0, '/', '.', ';', '\'', 0, 0, 0,
952 '0', '9', 0, 0, '-', 'p', 'o', '1', '`', 0, 0, '2', 'q', 0,
953 '2', '1', 0, 0, '3', 'w', 'q', '3', '2', 0, 0, '4', 'e', 'w',
954 '4', '3', 0, 0, '5', 'r', 'e', '5', '4', 0, 0, '6', 't', 'r',
955 '6', '5', 0, 0, '7', 'y', 't', '7', '6', 0, 0, '8', 'u', 'y',
956 '8', '7', 0, 0, '9', 'i', 'u', '9', '8', 0, 0, '0', 'o', 'i',
957 ';', 'l', 'o', 'p','\'', '/', '.', '=', '-', 0, 0, 0, ']', '[',
958 '[', 'p', '-', '=', ']', '\'',';', '\\', 0, 0, 'a', 'z', 0, 0,
959 ']', '[', '=', 0, 0, '#','\'', '`', 0, 0, 0, '1', 0, 0,
960 'a', 0, 'q', 'w', 's', 'z','\\', 'b', 'v', 'g', 'h', 'n', 0, 0,
961 'c', 'x', 'd', 'f', 'v', 0, 0, 'd', 's', 'e', 'r', 'f', 'c', 'x',
962 'e', 'w', '3', '4', 'r', 'd', 's', 'f', 'd', 'r', 't', 'g', 'v', 'c',
963 'g', 'f', 't', 'y', 'h', 'b', 'v', 'h', 'g', 'y', 'u', 'j', 'n', 'b',
964 'i', 'u', '8', '9', 'o', 'k', 'j', 'j', 'h', 'u', 'i', 'k', 'm', 'n',
965 'k', 'j', 'i', 'o', 'l', ',', 'm', 'l', 'k', 'o', 'p', ';', '.', ',',
966 'm', 'n', 'j', 'k', ',', 0, 0, 'n', 'b', 'h', 'j', 'm', 0, 0,
967 'o', 'i', '9', '0', 'p', 'l', 'k', 'p', 'o', '0', '-', '[', ';', 'l',
968 'q', 0, '1', '2', 'w', 'a', 0, 'r', 'e', '4', '5', 't', 'f', 'd',
969 's', 'a', 'w', 'e', 'd', 'x', 'z', 't', 'r', '5', '6', 'y', 'g', 'f',
970 'u', 'y', '7', '8', 'i', 'j', 'h', 'v', 'c', 'f', 'g', 'b', 0, 0,
971 'w', 'q', '2', '3', 'e', 's', 'a', 'x', 'z', 's', 'd', 'c', 0, 0,
972 'y', 't', '6', '7', 'u', 'h', 'g', 'z', '\\','a', 's', 'x', 0, 0
975 static const uint8_t US_Qwerty
[47*7] =
977 /* key, left, up-left, up-right, right, down-right, down-left */
978 '\'',';', '[', ']', 0, 0, '/', ',', 'm', 'k', 'l', '.', 0, 0,
979 '-', '0', 0, 0, '=', '[', 'p', '.', ',', 'l', ';', '/', 0, 0,
980 '/', '.', ';','\'', 0, 0, 0, '0', '9', 0, 0, '-', 'p', 'o',
981 '1', '`', 0, 0, '2', 'q', 0, '2', '1', 0, 0, '3', 'w', 'q',
982 '3', '2', 0, 0, '4', 'e', 'w', '4', '3', 0, 0, '5', 'r', 'e',
983 '5', '4', 0, 0, '6', 't', 'r', '6', '5', 0, 0, '7', 'y', 't',
984 '7', '6', 0, 0, '8', 'u', 'y', '8', '7', 0, 0, '9', 'i', 'u',
985 '9', '8', 0, 0, '0', 'o', 'i', ';', 'l', 'p', '[','\'', '/', '.',
986 '=', '-', 0, 0, 0, ']', '[', '[', 'p', '-', '=', ']','\'', ';',
987 '\\',']', 0, 0, 0, 0, 0, ']', '[', '=', 0,'\\', 0,'\'',
988 '`', 0, 0, 0, '1', 0, 0, 'a', 0, 'q', 'w', 's', 'z', 0,
989 'b', 'v', 'g', 'h', 'n', 0, 0, 'c', 'x', 'd', 'f', 'v', 0, 0,
990 'd', 's', 'e', 'r', 'f', 'c', 'x', 'e', 'w', '3', '4', 'r', 'd', 's',
991 'f', 'd', 'r', 't', 'g', 'v', 'c', 'g', 'f', 't', 'y', 'h', 'b', 'v',
992 'h', 'g', 'y', 'u', 'j', 'n', 'b', 'i', 'u', '8', '9', 'o', 'k', 'j',
993 'j', 'h', 'u', 'i', 'k', 'm', 'n', 'k', 'j', 'i', 'o', 'l', ',', 'm',
994 'l', 'k', 'o', 'p', ';', '.', ',', 'm', 'n', 'j', 'k', ',', 0, 0,
995 'n', 'b', 'h', 'j', 'm', 0, 0, 'o', 'i', '9', '0', 'p', 'l', 'k',
996 'p', 'o', '0', '-', '[', ';', 'l', 'q', 0, '1', '2', 'w', 'a', 0,
997 'r', 'e', '4', '5', 't', 'f', 'd', 's', 'a', 'w', 'e', 'd', 'x', 'z',
998 't', 'r', '5', '6', 'y', 'g', 'f', 'u', 'y', '7', '8', 'i', 'j', 'h',
999 'v', 'c', 'f', 'g', 'b', 0, 0, 'w', 'q', '2', '3', 'e', 's', 'a',
1000 'x', 'z', 's', 'd', 'c', 0, 0, 'y', 't', '6', '7', 'u', 'h', 'g',
1001 'z', 0, 'a', 's', 'x', 0, 0,
1003 static const uint8_t Dvorak
[48*7] =
1005 '\'', 0, '1', '2', ',', 'a', 0, ',','\'', '2', '3', '.', 'o', 'a',
1006 '-', 's', '/', '=', 0, 0, 'z', '.', ',', '3', '4', 'p', 'e', 'o',
1007 '/', 'l', '[', ']', '=', '-', 's', '0', '9', 0, 0, '[', 'l', 'r',
1008 '1', '`', 0, 0, '2','\'', 0, '2', '1', 0, 0, '3', ',','\'',
1009 '3', '2', 0, 0, '4', '.', ',', '4', '3', 0, 0, '5', 'p', '.',
1010 '5', '4', 0, 0, '6', 'y', 'p', '6', '5', 0, 0, '7', 'f', 'y',
1011 '7', '6', 0, 0, '8', 'g', 'f', '8', '7', 0, 0, '9', 'c', 'g',
1012 '9', '8', 0, 0, '0', 'r', 'c', ';', 0, 'a', 'o', 'q', 0, 0,
1013 '=', '/', ']', 0,'\\', 0, '-', '[', '0', 0, 0, ']', '/', 'l',
1014 '\\','=', 0, 0, 0, 0, 0, ']', '[', 0, 0, 0, '=', '/',
1015 '`', 0, 0, 0, '1', 0, 0, 'a', 0,'\'', ',', 'o', ';', 0,
1016 'b', 'x', 'd', 'h', 'm', 0, 0, 'c', 'g', '8', '9', 'r', 't', 'h',
1017 'd', 'i', 'f', 'g', 'h', 'b', 'x', 'e', 'o', '.', 'p', 'u', 'j', 'q',
1018 'f', 'y', '6', '7', 'g', 'd', 'i', 'g', 'f', '7', '8', 'c', 'h', 'd',
1019 'h', 'd', 'g', 'c', 't', 'm', 'b', 'i', 'u', 'y', 'f', 'd', 'x', 'k',
1020 'j', 'q', 'e', 'u', 'k', 0, 0, 'k', 'j', 'u', 'i', 'x', 0, 0,
1021 'l', 'r', '0', '[', '/', 's', 'n', 'm', 'b', 'h', 't', 'w', 0, 0,
1022 'n', 't', 'r', 'l', 's', 'v', 'w', 'o', 'a', ',', '.', 'e', 'q', ';',
1023 'p', '.', '4', '5', 'y', 'u', 'e', 'q', ';', 'o', 'e', 'j', 0, 0,
1024 'r', 'c', '9', '0', 'l', 'n', 't', 's', 'n', 'l', '/', '-', 'z', 'v',
1025 't', 'h', 'c', 'r', 'n', 'w', 'm', 'u', 'e', 'p', 'y', 'i', 'k', 'j',
1026 'v', 'w', 'n', 's', 'z', 0, 0, 'w', 'm', 't', 'n', 'v', 0, 0,
1027 'x', 'k', 'i', 'd', 'b', 0, 0, 'y', 'p', '5', '6', 'f', 'i', 'u',
1028 'z', 'v', 's', '-', 0, 0, 0
1031 static const uint8_t PC_Keypad
[15*9] =
1033 /*Key, left, up-left, up, up-right, right, down-right, down, down-left */
1034 '*', '/', 0, 0, 0, '-', '+', '9', '8',
1035 '+', '9', '*', '-', 0, 0, 0, 0, '6',
1036 '-', '*', 0, 0, 0, 0, 0, '+', '9',
1037 '.', '0', '2', '3', 0, 0, 0, 0, 0,
1038 '/', 0, 0, 0, 0, '*', '9', '8', '7',
1039 '0', 0, '1', '2', '3', '.', 0, 0, 0,
1040 '1', 0, 0, '4', '5', '2', '0', 0, 0,
1041 '2', '1', '4', '5', '6', '3', '.', '0', 0,
1042 '3', '2', '5', '6', 0, 0, 0, '.', '0',
1043 '4', 0, 0, '7', '8', '5', '2', '1', 0,
1044 '5', '4', '7', '8', '9', '6', '3', '2', '1',
1045 '6', '5', '8', '9', '+', 0, 0, '3', '2',
1046 '7', 0, 0, 0, '/', '8', '5', '4', 0,
1047 '8', '7', 0, '/', '*', '9', '6', '5', '4',
1048 '9', '8', '/', '*', '-', '+', 0, '6', '5'
1051 static const uint8_t MacKeypad
[16*9] =
1053 '*', '/', 0, 0, 0, 0, 0, '-', '9',
1054 '+', '6', '9', '-', 0, 0, 0, 0, '3',
1055 '-', '9', '/', '*', 0, 0, 0, '+', '6',
1056 '.', '0', '2', '3', 0, 0, 0, 0, 0,
1057 '/', '=', 0, 0, 0, '*', '-', '9', '8',
1058 '0', 0, '1', '2', '3', '.', 0, 0, 0,
1059 '1', 0, 0, '4', '5', '2', '0', 0, 0,
1060 '2', '1', '4', '5', '6', '3', '.', '0', 0,
1061 '3', '2', '5', '6', '+', 0, 0, '.', '0',
1062 '4', 0, 0, '7', '8', '5', '2', '1', 0,
1063 '5', '4', '7', '8', '9', '6', '3', '2', '1',
1064 '6', '5', '8', '9', '-', '+', 0, '3', '2',
1065 '7', 0, 0, 0, '=', '8', '5', '4', 0,
1066 '8', '7', 0, '=', '/', '9', '6', '5', '4',
1067 '9', '8', '=', '/', '*', '-', '+', '6', '5',
1068 '=', 0, 0, 0, 0, '/', '9', '8', '7'
1071 static const Keyboard_t Keyboards
[] =
1073 { US_Qwerty
, US_Shift
, sizeof US_Qwerty
/ 7, 7, sizeof US_Shift
/ 2, 66 },
1074 { Dvorak
, US_Shift
, sizeof Dvorak
/ 7, 7, sizeof US_Shift
/ 2, 66 },
1075 { UK_Qwerty
, UK_Shift
, sizeof UK_Qwerty
/ 7, 7, sizeof UK_Shift
/ 2, 66 },
1076 { MacKeypad
, 0, sizeof MacKeypad
/ 9, 9, 0, 44 },
1077 { PC_Keypad
, 0, sizeof PC_Keypad
/ 9, 9, 0, 44 }
1080 /**********************************************************************************
1081 * Match password for the given keyboard layout
1083 static int DoSptlMatch(const uint8_t *Passwd
, int MaxLen
, const Keyboard_t
*Keyb
, SpatialMatchInfo_t
*Extra
)
1090 uint8_t PrevChar
= 0;
1091 for( ; *Passwd
&& (Len
< MaxLen
); ++Passwd
, ++Len
)
1095 uint8_t CurChar
= *Passwd
;
1096 /* Try to unshift the character */
1099 Key
= CharBinSearch(CurChar
, Keyb
->Shifts
, Keyb
->NumShift
, 2);
1109 /* See if the pattern can be extended */
1111 Key
= CharBinSearch(PrevChar
, Keyb
->Keys
, Keyb
->NumKeys
, Keyb
->NumNear
);
1114 for(i
= Keyb
->NumNear
- 1; i
> 0; --i
)
1116 if (Key
[i
] == CurChar
)
1122 Turns
+= (i
!= Dir
);
1133 if (Len
>= MIN_SPATIAL_LEN
)
1135 Extra
->Turns
= Turns
;
1136 Extra
->Shifts
= ShiftCount
;
1142 /**********************************************************************************
1143 * Try to match spatial patterns on the keyboard
1145 * Result Pointer head of linked list used to store results
1146 * Passwd The start of the password
1147 * Start Where in the password to start attempting to match
1148 * MaxLen Maximum number characters to consider
1150 static void SpatialMatch(ZxcMatch_t
**Result
, const uint8_t *Passwd
, int Start
, int MaxLen
)
1154 SpatialMatchInfo_t Extra
;
1155 const Keyboard_t
*k
;
1158 for(CurLen
= MaxLen
; CurLen
>= MIN_SPATIAL_LEN
;CurLen
= Len
- 1)
1161 memset(&Extra
, 0, sizeof Extra
);
1162 for(k
= Keyboards
, Indx
= 0; Indx
< (sizeof Keyboards
/ sizeof Keyboards
[0]); ++Indx
, ++k
)
1164 Len
= DoSptlMatch(Passwd
, CurLen
, k
, &Extra
);
1167 /* Got a sequence of required length so add to result list */
1169 double Degree
, Entropy
;
1171 Degree
= (k
->NumNear
-1) - (double)k
->NumBlank
/ (double)k
->NumKeys
;
1176 /* Estimate the number of possible patterns with length ranging 2 to match length and */
1177 /* with turns ranging from 0 to match turns */
1179 for(i
= 2; i
<= Len
; ++i
)
1181 int PossTurns
= Extra
.Turns
;
1184 for(j
= 1; j
<= PossTurns
; ++j
)
1185 Entropy
+= nCk(i
-1, j
-1) * pow(Degree
, j
) * s
;
1188 Entropy
= log(Entropy
);
1191 /* Add extra entropy for shifted keys. (% instead of 5, A instead of a etc.) */
1192 /* Math is similar to extra entropy from uppercase letters in dictionary matches. */
1193 int Shift
= Extra
.Shifts
;
1194 int Unshift
= Len
- Shift
;
1200 for(i
= 0; i
<= j
; ++i
)
1202 Degree
+= nCk(Len
, i
);
1205 Entropy
+= log(Degree
);
1208 p
->Type
= SPATIAL_MATCH
;
1210 p
->Entrpy
= Entropy
;
1212 AddMatchRepeats(Result
, p
, Passwd
, MaxLen
);
1213 AddResult(Result
, p
, MaxLen
);
1221 /*################################################################################*
1222 *################################################################################*
1223 * Begin date matching code
1224 *################################################################################*
1225 *################################################################################*/
1227 /* The possible date formats ordered by length (d for day, m for month, */
1228 /* y for year, ? for separator) */
1229 static const char *Formats
[] =
1254 /* Possible separator characters that could be used */
1255 static const char DateSeperators
[] = "/\\-_. ";
1257 /**********************************************************************************
1258 * Try to match the password with the formats above.
1260 static void DateMatch(ZxcMatch_t
**Result
, const uint8_t *Passwd
, int Start
, int MaxLen
)
1268 for(CurFmt
= 0; Formats
[CurFmt
]; ++CurFmt
)
1275 const uint8_t *p
= Passwd
;
1279 /* Scan along the format, trying to match the password */
1280 for(Fmt
= Formats
[CurFmt
]; *Fmt
&& !Fail
; ++Fmt
)
1284 if (!Sep
&& strchr(DateSeperators
, *p
))
1288 else if (isdigit(*p
))
1292 Day
= 10 * Day
+ *p
- '0';
1294 else if (*Fmt
== 'm')
1296 Mon
= 10 * Mon
+ *p
- '0';
1300 Year
= 10 * Year
+ *p
- '0';
1317 /* Character matching is OK, now check to see if valid date */
1318 if (((YrLen
> 3) || (Len
<= 4)) && ((Year
< MIN_YEAR
) || (Year
> MAX_YEAR
)))
1322 if ((Mon
> 12) && (Day
< 13))
1324 /* Swap day,month to try to make both in range */
1329 /* Check for valid day, month. Currently assumes all months have 31 days. */
1330 if ((Mon
< 1) || (Mon
> 12))
1332 else if ((Day
< 1) || (Day
> 31))
1336 if (!Fail
&& (Len
> PrevLen
))
1338 /* String matched the date, store result */
1340 ZxcMatch_t
*p
= AllocMatch();
1343 e
= log(MAX_YEAR
- MIN_YEAR
+ 1.0);
1345 e
= log(31 * 12 * (MAX_YEAR
- MIN_YEAR
+ 1.0));
1347 e
= log(31 * 12 * 100.0);
1349 e
+= log(4.0); /* Extra 2 bits for separator */
1351 p
->Type
= DATE_MATCH
;
1354 AddMatchRepeats(Result
, p
, Passwd
, MaxLen
);
1355 AddResult(Result
, p
, MaxLen
);
1362 /*################################################################################*
1363 *################################################################################*
1364 * Begin repeated character matching code
1365 *################################################################################*
1366 *################################################################################*/
1368 /**********************************************************************************
1369 * Try to match password part as a set of repeated characters.
1371 * Result Pointer head of linked list used to store results
1372 * Passwd The start of the password
1373 * Start Where in the password to start attempting to match
1374 * MaxLen Maximum number characters to consider
1376 static void RepeatMatch(ZxcMatch_t
**Result
, const uint8_t *Passwd
, int Start
, int MaxLen
)
1381 /* Remember first char and the count its occurances */
1383 for(Len
= 1; (Len
< MaxLen
) && (c
== Passwd
[Len
]); ++Len
)
1385 if (Len
>= MIN_REPEAT_LEN
)
1387 /* Enough repeated char, so create results from number found down to min acceptable repeats */
1388 double Card
= Cardinality(&c
, 1);
1389 for(i
= Len
; i
>= MIN_REPEAT_LEN
; --i
)
1391 ZxcMatch_t
*p
= AllocMatch();
1392 p
->Type
= REPEATS_MATCH
;
1395 p
->Entrpy
= log(Card
* i
);
1396 AddResult(Result
, p
, MaxLen
);
1400 /* Try to match a repeated sequence e.g. qxno6qxno6 */
1401 for(Len
= MaxLen
/2; Len
>= MIN_REPEAT_LEN
; --Len
)
1403 const uint8_t *Rpt
= Passwd
+ Len
;
1404 int RepeatCount
= 2;
1405 while(MaxLen
>= (Len
* RepeatCount
))
1407 if (strncmp((const char *)Passwd
, (const char *)Rpt
, Len
) == 0)
1409 /* Found a repeat */
1410 int c
= Cardinality(Passwd
, Len
);
1411 ZxcMatch_t
*p
= AllocMatch();
1412 p
->Entrpy
= log((double)c
) * Len
+ log(RepeatCount
);
1413 p
->Type
= (ZxcTypeMatch_t
)(BRUTE_MATCH
+ MULTIPLE_MATCH
);
1414 p
->Length
= Len
* RepeatCount
;
1416 AddResult(Result
, p
, MaxLen
);
1426 /**********************************************************************************
1427 **********************************************************************************
1428 * Begin character sequence matching code
1429 **********************************************************************************
1430 *********************************************************************************/
1432 #define MAX_SEQUENCE_STEP 5
1433 /**********************************************************************************
1434 * Try to match password part as a set of incrementing or decrementing characters.
1436 * Result Pointer head of linked list used to store results
1437 * Passwd The start of the password
1438 * Start Where in the password to start attempting to match
1439 * MaxLen Maximum number characters to consider
1441 static void SequenceMatch(ZxcMatch_t
**Result
, const uint8_t *Passwd
, int Start
, int MaxLen
)
1444 int SetLow
, SetHigh
, Dir
;
1445 uint8_t First
, Next
, IsDigits
;
1450 Dir
= Passwd
[1] - First
;
1453 /* Decide on min and max character code for sequence */
1454 if (islower(*Passwd
))
1459 else if (isupper(*Passwd
))
1464 else if (isdigit(*Passwd
))
1468 if ((First
== '0') && isdigit(Passwd
[1]) && (Dir
> MAX_SEQUENCE_STEP
))
1470 /* Special case for decrementing sequence of digits, treat '0 as a 'ten' character */
1471 Dir
= Passwd
[1] - ('9' + 1);
1478 /* Only consider it a sequence if the character increment is not too large */
1479 if (Dir
&& (Dir
<= MAX_SEQUENCE_STEP
) && (Dir
>= -MAX_SEQUENCE_STEP
))
1484 Next
= Passwd
[0] + Dir
;
1485 if (IsDigits
&& (Dir
> 0) && (Next
== ('9' + 1)) && (Passwd
[1] == '0'))
1487 /* Incrementing digits, consider '0' to be same as a 'ten' character */
1492 if (IsDigits
&& (Dir
< 0) && (Passwd
[0] == '0') && (Passwd
[1] == ('9'+1 + Dir
)))
1497 else if ((Next
> SetHigh
) || (Next
< SetLow
) || (Passwd
[1] != Next
))
1505 if (Len
>= MIN_SEQUENCE_LEN
)
1507 /* Enough repeated char, so create results from number found down to min acceptable length */
1510 if ((First
== 'a') || (First
== 'A') || (First
== 'z') || (First
== 'Z') ||
1511 (First
== '0') || (First
== '1') || (First
== '9'))
1515 else if (isupper(First
))
1522 for(i
= Len
; i
>= MIN_SEQUENCE_LEN
; --i
)
1524 ZxcMatch_t
*p
= AllocMatch();
1525 /* Add new result to head of list as it has lower entropy */
1526 p
->Type
= SEQUENCE_MATCH
;
1529 p
->Entrpy
= e
+ log((double)i
);
1530 AddMatchRepeats(Result
, p
, Pwd
, MaxLen
);
1531 AddResult(Result
, p
, MaxLen
);
1536 /**********************************************************************************
1537 **********************************************************************************
1538 * Begin top level zxcvbn code
1539 **********************************************************************************
1540 *********************************************************************************/
1542 /**********************************************************************************
1543 * Matching a password is treated as a problem of finding the minimum distance
1544 * between two vertexes in a graph. This is solved using Dijkstra's algorithm.
1546 * There are a series of nodes (or vertexes in graph terminology) which correspond
1547 * to points between each character of the password. Also there is a start node
1548 * before the first character and an end node after the last character.
1550 * The paths between the nodes (or edges in graph terminology) correspond to the
1551 * matched parts of the password (e.g. dictionary word, repeated characters etc).
1552 * The distance of the path is equal to the entropy of the matched part. A default
1553 * single character bruteforce match path is also added for all nodes except the
1556 * Dijkstra's algorithm finds the combination of these part matches (or paths)
1557 * which gives the lowest entropy (or smallest distance) from begining to end
1561 /* Struct to hold the data of a node (imaginary point between password characters) */
1564 ZxcMatch_t
*Paths
; /* Partial matches that lead to a following node */
1565 double Dist
; /* Distance (or entropy) from start of password to this node */
1566 ZxcMatch_t
*From
; /* Which path was used to get to this node with lowest distance/entropy */
1567 int Visit
; /* Non zero when node has been visited during Dijkstra evaluation */
1570 /**********************************************************************************
1571 * Main function of the zxcvbn password entropy estimation
1573 double ZxcvbnMatch(const char *Pwd
, const char *UserDict
[], ZxcMatch_t
**Info
)
1579 int Len
= strlen(Pwd
);
1580 const uint8_t *Passwd
= (const uint8_t *)Pwd
;
1582 /* Create the paths */
1583 Node_t
*Nodes
= MallocFn(Node_t
, Len
+1);
1584 memset(Nodes
, 0, (Len
+1) * sizeof *Nodes
);
1585 i
= Cardinality(Passwd
, Len
);
1588 /* Do matching for all parts of the password */
1589 for(i
= 0; i
< Len
; ++i
)
1591 int MaxLen
= Len
- i
;
1592 /* Add all the 'paths' between groups of chars in the password, for current starting char */
1593 UserMatch(&(Nodes
[i
].Paths
), UserDict
, Passwd
, i
, MaxLen
);
1594 DictionaryMatch(&(Nodes
[i
].Paths
), Passwd
, i
, MaxLen
);
1595 DateMatch(&(Nodes
[i
].Paths
), Passwd
, i
, MaxLen
);
1596 SpatialMatch(&(Nodes
[i
].Paths
), Passwd
, i
, MaxLen
);
1597 SequenceMatch(&(Nodes
[i
].Paths
), Passwd
, i
, MaxLen
);
1598 RepeatMatch(&(Nodes
[i
].Paths
), Passwd
, i
, MaxLen
);
1600 /* Initially set distance to nearly infinite */
1601 Nodes
[i
].Dist
= DBL_MAX
;
1604 /* Reverse dictionary words check */
1605 RevPwd
= MallocFn(uint8_t, Len
+1);
1606 for(i
= Len
-1, j
= 0; i
>= 0; --i
, ++j
)
1609 for(i
= 0; i
< Len
; ++i
)
1611 ZxcMatch_t
*Path
= 0;
1612 int MaxLen
= Len
- i
;
1613 DictionaryMatch(&Path
, RevPwd
, i
, MaxLen
);
1614 UserMatch(&Path
, UserDict
, RevPwd
, i
, MaxLen
);
1616 /* Now transfer any reverse matches to the normal results */
1619 ZxcMatch_t
*Nxt
= Path
->Next
;
1621 Path
->Begin
= Len
- (Path
->Begin
+ Path
->Length
);
1622 AddResult(&(Nodes
[Path
->Begin
].Paths
), Path
, MaxLen
);
1627 /* Add a set of brute force matches. Start by getting all the start points and all */
1628 /* points at character position after end of the matches. */
1629 memset(RevPwd
, 0, Len
+1);
1630 for(i
= 0; i
< Len
; ++i
)
1632 ZxcMatch_t
*Path
= Nodes
[i
].Paths
;
1635 RevPwd
[Path
->Begin
] |= 1;
1636 RevPwd
[Path
->Begin
+ Path
->Length
] |= 2;
1643 /* Add the brute force matches */
1644 for(i
= 0; i
< Len
; ++i
)
1646 int MaxLen
= Len
- i
;
1650 for(j
= i
+1; j
<= Len
; ++j
)
1655 Zp
->Type
= BRUTE_MATCH
;
1658 Zp
->Entrpy
= e
* (j
- i
);
1659 AddResult(&(Nodes
[i
].Paths
), Zp
, MaxLen
);
1664 /* End node has infinite distance/entropy, start node has 0 distance */
1665 Nodes
[i
].Dist
= DBL_MAX
;
1666 Nodes
[0].Dist
= 0.0;
1668 /* Reduce the paths using Dijkstra's algorithm */
1669 for(i
= 0; i
< Len
; ++i
)
1672 double MinDist
= DBL_MAX
;
1674 /* Find the unvisited node with minimum distance or entropy */
1675 for(Np
= Nodes
, j
= 0; j
< Len
; ++j
, ++Np
)
1677 if (!Np
->Visit
&& (Np
->Dist
< MinDist
))
1683 /* Mark the minimum distance node as visited */
1684 Np
= Nodes
+ MinIdx
;
1687 /* Update all unvisited neighbouring nodes with their new distance. A node is a */
1688 /* neighbour if there is a path/match from current node Np to it. The neighbour */
1689 /* distance is the current node distance plus the path distance/entropy. Only */
1690 /* update if the new distance is smaller. */
1691 for(Zp
= Np
->Paths
; Zp
; Zp
= Zp
->Next
)
1693 Node_t
*Ep
= Np
+ Zp
->Length
;
1694 double d
= e
+ Zp
->MltEnpy
;
1695 if (!Ep
->Visit
&& (d
< Ep
->Dist
))
1697 /* Update as lower dist, also remember the 'from' node */
1702 /* If we got to the end node stop early */
1703 /*if (Nodes[Len].Dist < DBL_MAX/2.0) */
1706 /* Make e hold entropy result and adjust to log base 2 */
1707 e
= Nodes
[Len
].Dist
/ log(2.0);
1711 /* Construct info on password parts */
1713 for(Zp
= Nodes
[Len
].From
; Zp
; )
1718 /* Remove all but required path */
1719 Xp
= Nodes
[i
].Paths
;
1723 ZxcMatch_t
*p
= Xp
->Next
;
1726 /* Adjust the entropy to log to base 2 */
1727 Xp
->Entrpy
/= log(2.0);
1728 Xp
->MltEnpy
/= log(2.0);
1730 /* Put previous part at head of info list */
1736 /* Not going on info list, so free it */
1744 /* Free all paths. Any being returned to caller have already been freed */
1745 for(i
= 0; i
<= Len
; ++i
)
1747 Zp
= Nodes
[i
].Paths
;
1750 ZxcMatch_t
*p
= Zp
->Next
;
1759 /**********************************************************************************
1760 * Free the path info returned by ZxcvbnMatch().
1762 void ZxcvbnFreeInfo(ZxcMatch_t
*Info
)