├── .gitignore
├── .gitmodules
├── DefaultMatcherFactory.cs
├── Dictionaries
├── Readme.txt
├── english.lst
├── female_names.lst
├── male_names.lst
├── passwords.lst
└── surnames.lst
├── IMatcherFactory.cs
├── LICENSE
├── LinqExtensions.cs
├── Matcher
├── DateMatcher.cs
├── DictionaryMatcher.cs
├── IMatcher.cs
├── L33tMatcher.cs
├── RegexMatcher.cs
├── RepeatMatcher.cs
├── SequenceMatcher.cs
└── SpatialMatcher.cs
├── PasswordScoring.cs
├── Properties
└── AssemblyInfo.cs
├── README.md
├── Result.cs
├── TODO-Global.txt
├── Translation.cs
├── Utility.cs
├── Zxcvbn.cs
├── scripts
└── build_dictionaries.py
├── zxcvbn-cs.csproj
├── zxcvbn-cs.sln
└── zxcvbn-test
├── Properties
└── AssemblyInfo.cs
├── ZxcvbnTest.cs
├── test_dictionary.txt
└── zxcvbn-test.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | *.suo
2 | bin/
3 | obj/
4 | /*.user
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "zxcvbn"]
2 | path = zxcvbn
3 | url = git://github.com/lowe/zxcvbn.git
4 |
--------------------------------------------------------------------------------
/DefaultMatcherFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Zxcvbn.Matcher;
6 |
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// This matcher factory will use all of the default password matchers.
11 | ///
12 | /// Default dictionary matchers use the built-in word lists: passwords, english, male_names, female_names, surnames
13 | /// Also matching against: user data, all dictionaries with l33t substitutions
14 | /// Other default matchers: repeats, sequences, digits, years, dates, spatial
15 | ///
16 | /// See and the classes that implement it for more information on each kind of pattern matcher.
17 | ///
18 | class DefaultMatcherFactory : IMatcherFactory
19 | {
20 | List matchers;
21 |
22 | ///
23 | /// Create a matcher factory that uses the default list of pattern matchers
24 | ///
25 | public DefaultMatcherFactory()
26 | {
27 | var dictionaryMatchers = new List() {
28 | new DictionaryMatcher("passwords", "passwords.lst"),
29 | new DictionaryMatcher("english", "english.lst"),
30 | new DictionaryMatcher("male_names", "male_names.lst"),
31 | new DictionaryMatcher("female_names", "female_names.lst"),
32 | new DictionaryMatcher("surnames", "surnames.lst"),
33 | };
34 |
35 | matchers = new List {
36 | new RepeatMatcher(),
37 | new SequenceMatcher(),
38 | new RegexMatcher("\\d{3,}", 10, true, "digits"),
39 | new RegexMatcher("19\\d\\d|200\\d|201\\d", 119, false, "year"),
40 | new DateMatcher(),
41 | new SpatialMatcher()
42 | };
43 |
44 | matchers.AddRange(dictionaryMatchers);
45 | matchers.Add(new L33tMatcher(dictionaryMatchers));
46 | }
47 |
48 | ///
49 | /// Get instances of pattern matchers, adding in per-password matchers on userInputs (and userInputs with l33t substitutions)
50 | ///
51 | /// Enumerable of user information
52 | /// Enumerable of matchers to use
53 | public IEnumerable CreateMatchers(IEnumerable userInputs)
54 | {
55 | var userInputDict = new DictionaryMatcher("user_inputs", userInputs);
56 | var leetUser = new L33tMatcher(userInputDict);
57 |
58 | return matchers.Union(new List { userInputDict, leetUser });
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Dictionaries/Readme.txt:
--------------------------------------------------------------------------------
1 | The dictionaries here have been produced with the python script
2 | build_dictionaries.py in the scripts directory.
3 |
4 | They are included here so that these default dictionaries may be embedded in
5 | the assembly to reduce external dependencies.
--------------------------------------------------------------------------------
/Dictionaries/female_names.lst:
--------------------------------------------------------------------------------
1 | mary
2 | patricia
3 | linda
4 | barbara
5 | elizabeth
6 | jennifer
7 | maria
8 | susan
9 | margaret
10 | dorothy
11 | lisa
12 | nancy
13 | karen
14 | betty
15 | helen
16 | sandra
17 | donna
18 | carol
19 | ruth
20 | sharon
21 | michelle
22 | laura
23 | sarah
24 | kimberly
25 | deborah
26 | jessica
27 | shirley
28 | cynthia
29 | angela
30 | melissa
31 | brenda
32 | amy
33 | anna
34 | rebecca
35 | virginia
36 | kathleen
37 | pamela
38 | martha
39 | debra
40 | amanda
41 | stephanie
42 | carolyn
43 | christine
44 | marie
45 | janet
46 | catherine
47 | frances
48 | ann
49 | joyce
50 | diane
51 | alice
52 | julie
53 | heather
54 | teresa
55 | doris
56 | gloria
57 | evelyn
58 | jean
59 | cheryl
60 | mildred
61 | katherine
62 | joan
63 | ashley
64 | judith
65 | rose
66 | janice
67 | kelly
68 | nicole
69 | judy
70 | christina
71 | kathy
72 | theresa
73 | beverly
74 | denise
75 | tammy
76 | irene
77 | jane
78 | lori
79 | rachel
80 | marilyn
81 | andrea
82 | kathryn
83 | louise
84 | sara
85 | anne
86 | jacqueline
87 | wanda
88 | bonnie
89 | julia
90 | ruby
91 | lois
92 | tina
93 | phyllis
94 | norma
95 | paula
96 | diana
97 | annie
98 | lillian
99 | emily
100 | robin
101 | peggy
102 | crystal
103 | gladys
104 | rita
105 | dawn
106 | connie
107 | florence
108 | tracy
109 | edna
110 | tiffany
111 | carmen
112 | rosa
113 | cindy
114 | grace
115 | wendy
116 | victoria
117 | edith
118 | kim
119 | sherry
120 | sylvia
121 | josephine
122 | thelma
123 | shannon
124 | sheila
125 | ethel
126 | ellen
127 | elaine
128 | marjorie
129 | carrie
130 | charlotte
131 | monica
132 | esther
133 | pauline
134 | emma
135 | juanita
136 | anita
137 | rhonda
138 | hazel
139 | amber
140 | eva
141 | debbie
142 | april
143 | leslie
144 | clara
145 | lucille
146 | jamie
147 | joanne
148 | eleanor
149 | valerie
150 | danielle
151 | megan
152 | alicia
153 | suzanne
154 | michele
155 | gail
156 | bertha
157 | darlene
158 | veronica
159 | jill
160 | erin
161 | geraldine
162 | lauren
163 | cathy
164 | joann
165 | lorraine
166 | lynn
167 | sally
168 | regina
169 | erica
170 | beatrice
171 | dolores
172 | bernice
173 | audrey
174 | yvonne
175 | annette
176 | june
177 | marion
178 | dana
179 | stacy
180 | ana
181 | renee
182 | ida
183 | vivian
184 | roberta
185 | holly
186 | brittany
187 | melanie
188 | loretta
189 | yolanda
190 | jeanette
191 | laurie
192 | katie
193 | kristen
194 | vanessa
195 | alma
196 | sue
197 | elsie
198 | beth
199 | jeanne
200 | vicki
201 | carla
202 | tara
203 | rosemary
204 | eileen
205 | terri
206 | gertrude
207 | lucy
208 | tonya
209 | ella
210 | stacey
211 | wilma
212 | gina
213 | kristin
214 | jessie
215 | natalie
216 | agnes
217 | vera
218 | charlene
219 | bessie
220 | delores
221 | melinda
222 | pearl
223 | arlene
224 | maureen
225 | colleen
226 | allison
227 | tamara
228 | joy
229 | georgia
230 | constance
231 | lillie
232 | claudia
233 | jackie
234 | marcia
235 | tanya
236 | nellie
237 | minnie
238 | marlene
239 | heidi
240 | glenda
241 | lydia
242 | viola
243 | courtney
244 | marian
245 | stella
246 | caroline
247 | dora
248 | jo
249 | vickie
250 | mattie
251 | maxine
252 | irma
253 | mabel
254 | marsha
255 | myrtle
256 | lena
257 | christy
258 | deanna
259 | patsy
260 | hilda
261 | gwendolyn
262 | jennie
263 | nora
264 | margie
265 | nina
266 | cassandra
267 | leah
268 | penny
269 | kay
270 | priscilla
271 | naomi
272 | carole
273 | olga
274 | billie
275 | dianne
276 | tracey
277 | leona
278 | jenny
279 | felicia
280 | sonia
281 | miriam
282 | velma
283 | becky
284 | bobbie
285 | violet
286 | kristina
287 | toni
288 | misty
289 | mae
290 | shelly
291 | daisy
292 | ramona
293 | sherri
294 | erika
295 | katrina
296 | claire
297 | lindsey
298 | lindsay
299 | geneva
300 | guadalupe
301 | belinda
302 | margarita
303 | sheryl
304 | cora
305 | faye
306 | ada
307 | natasha
308 | sabrina
309 | isabel
310 | marguerite
311 | hattie
312 | harriet
313 | molly
314 | cecilia
315 | kristi
316 | brandi
317 | blanche
318 | sandy
319 | rosie
320 | joanna
321 | iris
322 | eunice
323 | angie
324 | inez
325 | lynda
326 | madeline
327 | amelia
328 | alberta
329 | genevieve
330 | monique
331 | jodi
332 | janie
333 | kayla
334 | sonya
335 | jan
336 | kristine
337 | candace
338 | fannie
339 | maryann
340 | opal
341 | alison
342 | yvette
343 | melody
344 | luz
345 | susie
346 | olivia
347 | flora
348 | shelley
349 | kristy
350 | mamie
351 | lula
352 | lola
353 | verna
354 | beulah
355 | antoinette
356 | candice
357 | juana
358 | jeannette
359 | pam
360 | kelli
361 | whitney
362 | bridget
363 | karla
364 | celia
365 | latoya
366 | patty
367 | shelia
368 | gayle
369 | della
370 | vicky
371 | lynne
372 | sheri
373 | marianne
374 | kara
375 | jacquelyn
376 | erma
377 | blanca
378 | myra
379 | leticia
380 | pat
381 | krista
382 | roxanne
383 | angelica
384 | robyn
385 | adrienne
386 | rosalie
387 | alexandra
388 | brooke
389 | bethany
390 | sadie
391 | bernadette
392 | traci
393 | jody
394 | kendra
395 | nichole
396 | rachael
397 | mable
398 | ernestine
399 | muriel
400 | marcella
401 | elena
402 | krystal
403 | angelina
404 | nadine
405 | kari
406 | estelle
407 | dianna
408 | paulette
409 | lora
410 | mona
411 | doreen
412 | rosemarie
413 | desiree
414 | antonia
415 | janis
416 | betsy
417 | christie
418 | freda
419 | meredith
420 | lynette
421 | teri
422 | cristina
423 | eula
424 | leigh
425 | meghan
426 | sophia
427 | eloise
428 | rochelle
429 | gretchen
430 | cecelia
431 | raquel
432 | henrietta
433 | alyssa
434 | jana
435 | gwen
436 | jenna
437 | tricia
438 | laverne
439 | olive
440 | tasha
441 | silvia
442 | elvira
443 | delia
444 | kate
445 | patti
446 | lorena
447 | kellie
448 | sonja
449 | lila
450 | lana
451 | darla
452 | mindy
453 | essie
454 | mandy
455 | lorene
456 | elsa
457 | josefina
458 | jeannie
459 | miranda
460 | dixie
461 | lucia
462 | marta
463 | faith
464 | lela
465 | johanna
466 | shari
467 | camille
468 | tami
469 | shawna
470 | elisa
471 | ebony
472 | melba
473 | ora
474 | nettie
475 | tabitha
476 | ollie
477 | winifred
478 | kristie
479 | marina
480 | alisha
481 | aimee
482 | rena
483 | myrna
484 | marla
485 | tammie
486 | latasha
487 | bonita
488 | patrice
489 | ronda
490 | sherrie
491 | addie
492 | francine
493 | deloris
494 | stacie
495 | adriana
496 | cheri
497 | abigail
498 | celeste
499 | jewel
500 | cara
501 | adele
502 | rebekah
503 | lucinda
504 | dorthy
505 | effie
506 | trina
507 | reba
508 | sallie
509 | aurora
510 | lenora
511 | etta
512 | lottie
513 | kerri
514 | trisha
515 | nikki
516 | estella
517 | francisca
518 | josie
519 | tracie
520 | marissa
521 | karin
522 | brittney
523 | janelle
524 | lourdes
525 | laurel
526 | helene
527 | fern
528 | elva
529 | corinne
530 | kelsey
531 | ina
532 | bettie
533 | elisabeth
534 | aida
535 | caitlin
536 | ingrid
537 | iva
538 | eugenia
539 | christa
540 | goldie
541 | maude
542 | jenifer
543 | therese
544 | dena
545 | lorna
546 | janette
547 | latonya
548 | candy
549 | consuelo
550 | tamika
551 | rosetta
552 | debora
553 | cherie
554 | polly
555 | dina
556 | jewell
557 | fay
558 | jillian
559 | dorothea
560 | nell
561 | trudy
562 | esperanza
563 | patrica
564 | kimberley
565 | shanna
566 | helena
567 | cleo
568 | stefanie
569 | rosario
570 | ola
571 | janine
572 | mollie
573 | lupe
574 | alisa
575 | lou
576 | maribel
577 | susanne
578 | bette
579 | susana
580 | elise
581 | cecile
582 | isabelle
583 | lesley
584 | jocelyn
585 | paige
586 | joni
587 | rachelle
588 | leola
589 | daphne
590 | alta
591 | ester
592 | petra
593 | graciela
594 | imogene
595 | jolene
596 | keisha
597 | lacey
598 | glenna
599 | gabriela
600 | keri
601 | ursula
602 | lizzie
603 | kirsten
604 | shana
605 | adeline
606 | mayra
607 | jayne
608 | jaclyn
609 | gracie
610 | sondra
611 | carmela
612 | marisa
613 | rosalind
614 | charity
615 | tonia
616 | beatriz
617 | marisol
618 | clarice
619 | jeanine
620 | sheena
621 | angeline
622 | frieda
623 | lily
624 | shauna
625 | millie
626 | claudette
627 | cathleen
628 | angelia
629 | gabrielle
630 | autumn
631 | katharine
632 | jodie
633 | staci
634 | lea
635 | christi
636 | justine
637 | elma
638 | luella
639 | margret
640 | dominique
641 | socorro
642 | martina
643 | margo
644 | mavis
645 | callie
646 | bobbi
647 | maritza
648 | lucile
649 | leanne
650 | jeannine
651 | deana
652 | aileen
653 | lorie
654 | ladonna
655 | willa
656 | manuela
657 | gale
658 | selma
659 | dolly
660 | sybil
661 | abby
662 | ivy
663 | dee
664 | winnie
665 | marcy
666 | luisa
667 | jeri
668 | magdalena
669 | ofelia
670 | meagan
671 | audra
672 | matilda
673 | leila
674 | cornelia
675 | bianca
676 | simone
677 | bettye
678 | randi
679 | virgie
680 | latisha
681 | barbra
682 | georgina
683 | eliza
684 | leann
685 | bridgette
686 | rhoda
687 | haley
688 | adela
689 | nola
690 | bernadine
691 | flossie
692 | ila
693 | greta
694 | ruthie
695 | nelda
696 | minerva
697 | lilly
698 | terrie
699 | letha
700 | hilary
701 | estela
702 | valarie
703 | brianna
704 | rosalyn
705 | earline
706 | catalina
707 | ava
708 | mia
709 | clarissa
710 | lidia
711 | corrine
712 | alexandria
713 | concepcion
714 | tia
715 | sharron
716 | rae
717 | dona
718 | ericka
719 | jami
720 | elnora
721 | chandra
722 | lenore
723 | neva
724 | marylou
725 | melisa
726 | tabatha
727 | serena
728 | avis
729 | allie
730 | sofia
731 | jeanie
732 | odessa
733 | nannie
734 | harriett
735 | loraine
736 | penelope
737 | milagros
738 | emilia
739 | benita
740 | allyson
741 | ashlee
742 | tania
743 | esmeralda
744 | karina
745 | eve
746 | pearlie
747 | zelma
748 | malinda
749 | noreen
750 | tameka
751 | saundra
752 | hillary
753 | amie
754 | althea
755 | rosalinda
756 | lilia
757 | alana
758 | clare
759 | alejandra
760 | elinor
761 | lorrie
762 | jerri
763 | darcy
764 | earnestine
765 | carmella
766 | noemi
767 | marcie
768 | liza
769 | annabelle
770 | louisa
771 | earlene
772 | mallory
773 | carlene
774 | nita
775 | selena
776 | tanisha
777 | katy
778 | julianne
779 | lakisha
780 | edwina
781 | maricela
782 | margery
783 | kenya
784 | dollie
785 | roxie
786 | roslyn
787 | kathrine
788 | nanette
789 | charmaine
790 | lavonne
791 | ilene
792 | tammi
793 | suzette
794 | corine
795 | kaye
796 | chrystal
797 | lina
798 | deanne
799 | lilian
800 | juliana
801 | aline
802 | luann
803 | kasey
804 | maryanne
805 | evangeline
806 | colette
807 | melva
808 | lawanda
809 | yesenia
810 | nadia
811 | madge
812 | kathie
813 | ophelia
814 | valeria
815 | nona
816 | mitzi
817 | mari
818 | georgette
819 | claudine
820 | fran
821 | alissa
822 | roseann
823 | lakeisha
824 | susanna
825 | reva
826 | deidre
827 | chasity
828 | sheree
829 | elvia
830 | alyce
831 | deirdre
832 | gena
833 | briana
834 | araceli
835 | katelyn
836 | rosanne
837 | wendi
838 | tessa
839 | berta
840 | marva
841 | imelda
842 | marietta
843 | marci
844 | leonor
845 | arline
846 | sasha
847 | madelyn
848 | janna
849 | juliette
850 | deena
851 | aurelia
852 | josefa
853 | augusta
854 | liliana
855 | lessie
856 | amalia
857 | savannah
858 | anastasia
859 | vilma
860 | natalia
861 | rosella
862 | lynnette
863 | corina
864 | alfreda
865 | leanna
866 | amparo
867 | coleen
868 | tamra
869 | aisha
870 | wilda
871 | karyn
872 | queen
873 | maura
874 | mai
875 | evangelina
876 | rosanna
877 | hallie
878 | erna
879 | enid
880 | mariana
881 | lacy
882 | juliet
883 | jacklyn
884 | freida
885 | madeleine
886 | mara
887 | cathryn
888 | lelia
889 | casandra
890 | bridgett
891 | angelita
892 | jannie
893 | dionne
894 | annmarie
895 | katina
896 | beryl
897 | millicent
898 | katheryn
899 | diann
900 | carissa
901 | maryellen
902 | liz
903 | lauri
904 | helga
905 | gilda
906 | rhea
907 | marquita
908 | hollie
909 | tisha
910 | tamera
911 | angelique
912 | francesca
913 | kaitlin
914 | lolita
915 | florine
916 | rowena
917 | reyna
918 | twila
919 | fanny
920 | janell
921 | ines
922 | concetta
923 | bertie
924 | alba
925 | brigitte
926 | alyson
927 | vonda
928 | pansy
929 | elba
930 | noelle
931 | letitia
932 | deann
933 | brandie
934 | louella
935 | leta
936 | felecia
937 | sharlene
938 | lesa
939 | beverley
940 | isabella
941 | herminia
942 | terra
943 | celina
944 | tori
945 | octavia
946 | jade
947 | denice
948 | germaine
949 | michell
950 | cortney
951 | nelly
952 | doretha
953 | deidra
954 | monika
955 | lashonda
956 | judi
957 | chelsey
958 | antionette
959 | margot
960 | adelaide
961 | nan
962 | leeann
963 | elisha
964 | dessie
965 | libby
966 | kathi
967 | gayla
968 | latanya
969 | mina
970 | mellisa
971 | kimberlee
972 | jasmin
973 | renae
974 | zelda
975 | elda
976 | justina
977 | gussie
978 | emilie
979 | camilla
980 | abbie
981 | rocio
982 | kaitlyn
983 | edythe
984 | ashleigh
985 | selina
986 | lakesha
987 | geri
988 | allene
989 | pamala
990 | michaela
991 | dayna
992 | caryn
993 | rosalia
994 | sun
995 | jacquline
996 | rebeca
997 | marybeth
998 | krystle
999 | iola
1000 | dottie
1001 | belle
1002 | griselda
1003 | ernestina
1004 | elida
1005 | adrianne
1006 | demetria
1007 | delma
1008 | jaqueline
1009 | arleen
1010 | virgina
1011 | retha
1012 | fatima
1013 | tillie
1014 | eleanore
1015 | cari
1016 | treva
1017 | wilhelmina
1018 | rosalee
1019 | maurine
1020 | latrice
1021 | jena
1022 | taryn
1023 | elia
1024 | debby
1025 | maudie
1026 | jeanna
1027 | delilah
1028 | catrina
1029 | shonda
1030 | hortencia
1031 | theodora
1032 | teresita
1033 | robbin
1034 | danette
1035 | delphine
1036 | brianne
1037 | nilda
1038 | danna
1039 | cindi
1040 | bess
1041 | iona
1042 | winona
1043 | vida
1044 | rosita
1045 | marianna
1046 | racheal
1047 | guillermina
1048 | eloisa
1049 | celestine
1050 | caren
1051 | malissa
1052 | lona
1053 | chantel
1054 | shellie
1055 | marisela
1056 | leora
1057 | agatha
1058 | soledad
1059 | migdalia
1060 | ivette
1061 | christen
1062 | janel
1063 | veda
1064 | pattie
1065 | tessie
1066 | tera
1067 | marilynn
1068 | lucretia
1069 | karrie
1070 | dinah
1071 | daniela
1072 | alecia
1073 | adelina
1074 | vernice
1075 | shiela
1076 | portia
1077 | merry
1078 | lashawn
1079 | dara
1080 | tawana
1081 | oma
1082 | verda
1083 | alene
1084 | zella
1085 | sandi
1086 | rafaela
1087 | maya
1088 | kira
1089 | candida
1090 | alvina
1091 | suzan
1092 | shayla
1093 | lyn
1094 | lettie
1095 | samatha
1096 | oralia
1097 | matilde
1098 | larissa
1099 | vesta
1100 | renita
1101 | india
1102 | delois
1103 | shanda
1104 | phillis
1105 | lorri
1106 | erlinda
1107 | cathrine
1108 | barb
1109 | zoe
1110 | isabell
1111 | ione
1112 | gisela
1113 | roxanna
1114 | mayme
1115 | kisha
1116 | ellie
1117 | mellissa
1118 | dorris
1119 | dalia
1120 | bella
1121 | annetta
1122 | zoila
1123 | reta
1124 | reina
1125 | lauretta
1126 | kylie
1127 | christal
1128 | pilar
1129 | charla
1130 | elissa
1131 | tiffani
1132 | tana
1133 | paulina
1134 | leota
1135 | breanna
1136 | jayme
1137 | carmel
1138 | vernell
1139 | tomasa
1140 | mandi
1141 | dominga
1142 | santa
1143 | melodie
1144 | lura
1145 | alexa
1146 | tamela
1147 | mirna
1148 | kerrie
1149 | venus
1150 | felicita
1151 | cristy
1152 | carmelita
1153 | berniece
1154 | annemarie
1155 | tiara
1156 | roseanne
1157 | missy
1158 | cori
1159 | roxana
1160 | pricilla
1161 | kristal
1162 | jung
1163 | elyse
1164 | haydee
1165 | aletha
1166 | bettina
1167 | marge
1168 | gillian
1169 | filomena
1170 | zenaida
1171 | harriette
1172 | caridad
1173 | vada
1174 | una
1175 | aretha
1176 | pearline
1177 | marjory
1178 | marcela
1179 | flor
1180 | evette
1181 | elouise
1182 | alina
1183 | damaris
1184 | catharine
1185 | belva
1186 | nakia
1187 | marlena
1188 | luanne
1189 | lorine
1190 | karon
1191 | dorene
1192 | danita
1193 | brenna
1194 | tatiana
1195 | louann
1196 | julianna
1197 | andria
1198 | philomena
1199 | lucila
1200 | leonora
1201 | dovie
1202 | romona
1203 | mimi
1204 | jacquelin
1205 | gaye
1206 | tonja
1207 | misti
1208 | chastity
1209 | stacia
1210 | roxann
1211 | micaela
1212 | nikita
1213 | mei
1214 | velda
1215 | marlys
1216 | johnna
1217 | aura
1218 | ivonne
1219 | hayley
1220 | nicki
1221 | majorie
1222 | herlinda
1223 | yadira
1224 | perla
1225 | gregoria
1226 | antonette
1227 | shelli
1228 | mozelle
1229 | mariah
1230 | joelle
1231 | cordelia
1232 | josette
1233 | chiquita
1234 | trista
1235 | laquita
1236 | georgiana
1237 | candi
1238 | shanon
1239 | hildegard
1240 | valentina
1241 | stephany
1242 | magda
1243 | karol
1244 | gabriella
1245 | tiana
1246 | roma
1247 | richelle
1248 | oleta
1249 | jacque
1250 | idella
1251 | alaina
1252 | suzanna
1253 | jovita
1254 | tosha
1255 | nereida
1256 | marlyn
1257 | kyla
1258 | delfina
1259 | tena
1260 | stephenie
1261 | sabina
1262 | nathalie
1263 | marcelle
1264 | gertie
1265 | darleen
1266 | thea
1267 | sharonda
1268 | shantel
1269 | belen
1270 | venessa
1271 | rosalina
1272 | ona
1273 | genoveva
1274 | clementine
1275 | rosalba
1276 | renate
1277 | renata
1278 | georgianna
1279 | floy
1280 | dorcas
1281 | ariana
1282 | tyra
1283 | theda
1284 | mariam
1285 | juli
1286 | jesica
1287 | vikki
1288 | verla
1289 | roselyn
1290 | melvina
1291 | jannette
1292 | ginny
1293 | debrah
1294 | corrie
1295 | asia
1296 | violeta
1297 | myrtis
1298 | latricia
1299 | collette
1300 | charleen
1301 | anissa
1302 | viviana
1303 | twyla
1304 | nedra
1305 | latonia
1306 | lan
1307 | hellen
1308 | fabiola
1309 | annamarie
1310 | adell
1311 | sharyn
1312 | chantal
1313 | niki
1314 | maud
1315 | lizette
1316 | lindy
1317 | kia
1318 | kesha
1319 | jeana
1320 | danelle
1321 | charline
1322 | chanel
1323 | valorie
1324 | lia
1325 | dortha
1326 | cristal
1327 | leone
1328 | leilani
1329 | gerri
1330 | debi
1331 | andra
1332 | keshia
1333 | ima
1334 | eulalia
1335 | easter
1336 | dulce
1337 | natividad
1338 | linnie
1339 | kami
1340 | georgie
1341 | catina
1342 | brook
1343 | alda
1344 | winnifred
1345 | sharla
1346 | ruthann
1347 | meaghan
1348 | magdalene
1349 | lissette
1350 | adelaida
1351 | venita
1352 | trena
1353 | shirlene
1354 | shameka
1355 | elizebeth
1356 | dian
1357 | shanta
1358 | latosha
1359 | carlotta
1360 | windy
1361 | rosina
1362 | mariann
1363 | leisa
1364 | jonnie
1365 | dawna
1366 | cathie
1367 | astrid
1368 | laureen
1369 | janeen
1370 | holli
1371 | fawn
1372 | vickey
1373 | teressa
1374 | shante
1375 | rubye
1376 | marcelina
1377 | chanda
1378 | terese
1379 | scarlett
1380 | marnie
1381 | lulu
1382 | lisette
1383 | jeniffer
1384 | elenor
1385 | dorinda
1386 | donita
1387 | carman
1388 | bernita
1389 | altagracia
1390 | aleta
1391 | adrianna
1392 | zoraida
1393 | nicola
1394 | lyndsey
1395 | janina
1396 | ami
1397 | starla
1398 | phylis
1399 | phuong
1400 | kyra
1401 | charisse
1402 | blanch
1403 | sanjuanita
1404 | rona
1405 | nanci
1406 | marilee
1407 | maranda
1408 | brigette
1409 | sanjuana
1410 | marita
1411 | kassandra
1412 | joycelyn
1413 | felipa
1414 | chelsie
1415 | bonny
1416 | mireya
1417 | lorenza
1418 | kyong
1419 | ileana
1420 | candelaria
1421 | sherie
1422 | lucie
1423 | leatrice
1424 | lakeshia
1425 | gerda
1426 | edie
1427 | bambi
1428 | marylin
1429 | lavon
1430 | hortense
1431 | garnet
1432 | evie
1433 | tressa
1434 | shayna
1435 | lavina
1436 | kyung
1437 | jeanetta
1438 | sherrill
1439 | shara
1440 | phyliss
1441 | mittie
1442 | anabel
1443 | alesia
1444 | thuy
1445 | tawanda
1446 | joanie
1447 | tiffanie
1448 | lashanda
1449 | karissa
1450 | enriqueta
1451 | daria
1452 | daniella
1453 | corinna
1454 | alanna
1455 | abbey
1456 | roxane
1457 | roseanna
1458 | magnolia
1459 | lida
1460 | joellen
1461 | era
1462 | coral
1463 | carleen
1464 | tresa
1465 | peggie
1466 | novella
1467 | nila
1468 | maybelle
1469 | jenelle
1470 | carina
1471 | nova
1472 | melina
1473 | marquerite
1474 | margarette
1475 | josephina
1476 | evonne
1477 | cinthia
1478 | albina
1479 | toya
1480 | tawnya
1481 | sherita
1482 | myriam
1483 | lizabeth
1484 | lise
1485 | keely
1486 | jenni
1487 | giselle
1488 | cheryle
1489 | ardith
1490 | ardis
1491 | alesha
1492 | adriane
1493 | shaina
1494 | linnea
1495 | karolyn
1496 | felisha
1497 | dori
1498 | darci
1499 | artie
1500 | armida
1501 | zola
1502 | xiomara
1503 | vergie
1504 | shamika
1505 | nena
1506 | nannette
1507 | maxie
1508 | lovie
1509 | jeane
1510 | jaimie
1511 | inge
1512 | farrah
1513 | elaina
1514 | caitlyn
1515 | felicitas
1516 | cherly
1517 | caryl
1518 | yolonda
1519 | yasmin
1520 | teena
1521 | prudence
1522 | pennie
1523 | nydia
1524 | mackenzie
1525 | orpha
1526 | marvel
1527 | lizbeth
1528 | laurette
1529 | jerrie
1530 | hermelinda
1531 | carolee
1532 | tierra
1533 | mirian
1534 | meta
1535 | melony
1536 | kori
1537 | jennette
1538 | jamila
1539 | ena
1540 | anh
1541 | yoshiko
1542 | susannah
1543 | salina
1544 | rhiannon
1545 | joleen
1546 | cristine
1547 | ashton
1548 | aracely
1549 | tomeka
1550 | shalonda
1551 | marti
1552 | lacie
1553 | kala
1554 | jada
1555 | ilse
1556 | hailey
1557 | brittani
1558 | zona
1559 | syble
1560 | sherryl
1561 | nidia
1562 | marlo
1563 | kandice
1564 | kandi
1565 | deb
1566 | alycia
1567 | ronna
1568 | norene
1569 | mercy
1570 | ingeborg
1571 | giovanna
1572 | gemma
1573 | christel
1574 | audry
1575 | zora
1576 | vita
1577 | trish
1578 | stephaine
1579 | shirlee
1580 | shanika
1581 | melonie
1582 | mazie
1583 | jazmin
1584 | inga
1585 | hoa
1586 | hettie
1587 | geralyn
1588 | fonda
1589 | estrella
1590 | adella
1591 | sarita
1592 | rina
1593 | milissa
1594 | maribeth
1595 | golda
1596 | evon
1597 | ethelyn
1598 | enedina
1599 | cherise
1600 | chana
1601 | velva
1602 | tawanna
1603 | sade
1604 | mirta
1605 | karie
1606 | jacinta
1607 | elna
1608 | davina
1609 | cierra
1610 | ashlie
1611 | albertha
1612 | tanesha
1613 | nelle
1614 | mindi
1615 | lorinda
1616 | larue
1617 | florene
1618 | demetra
1619 | dedra
1620 | ciara
1621 | chantelle
1622 | ashly
1623 | suzy
1624 | rosalva
1625 | noelia
1626 | lyda
1627 | leatha
1628 | krystyna
1629 | kristan
1630 | karri
1631 | darline
1632 | darcie
1633 | cinda
1634 | cherrie
1635 | awilda
1636 | almeda
1637 | rolanda
1638 | lanette
1639 | jerilyn
1640 | gisele
1641 | evalyn
1642 | cyndi
1643 | cleta
1644 | carin
1645 | zina
1646 | zena
1647 | velia
1648 | tanika
1649 | charissa
1650 | talia
1651 | margarete
1652 | lavonda
1653 | kaylee
1654 | kathlene
1655 | jonna
1656 | irena
1657 | ilona
1658 | idalia
1659 | candis
1660 | candance
1661 | brandee
1662 | anitra
1663 | alida
1664 | sigrid
1665 | nicolette
1666 | maryjo
1667 | linette
1668 | hedwig
1669 | christiana
1670 | alexia
1671 | tressie
1672 | modesta
1673 | lupita
1674 | lita
1675 | gladis
1676 | evelia
1677 | davida
1678 | cherri
1679 | cecily
1680 | ashely
1681 | annabel
1682 | agustina
1683 | wanita
1684 | shirly
1685 | rosaura
1686 | hulda
1687 | eun
1688 | yetta
1689 | verona
1690 | thomasina
1691 | sibyl
1692 | shannan
1693 | mechelle
1694 | lue
1695 | leandra
1696 | lani
1697 | kylee
1698 | kandy
1699 | jolynn
1700 | ferne
1701 | eboni
1702 | corene
1703 | alysia
1704 | zula
1705 | nada
1706 | moira
1707 | lyndsay
1708 | lorretta
1709 | jammie
1710 | hortensia
1711 | gaynell
1712 | adria
1713 | vina
1714 | vicenta
1715 | tangela
1716 | stephine
1717 | norine
1718 | nella
1719 | liana
1720 | leslee
1721 | kimberely
1722 | iliana
1723 | glory
1724 | felica
1725 | emogene
1726 | elfriede
1727 | eden
1728 | eartha
1729 | carma
1730 | bea
1731 | ocie
1732 | lennie
1733 | kiara
1734 | jacalyn
1735 | carlota
1736 | arielle
1737 | otilia
1738 | kirstin
1739 | kacey
1740 | johnetta
1741 | joetta
1742 | jeraldine
1743 | jaunita
1744 | elana
1745 | dorthea
1746 | cami
1747 | amada
1748 | adelia
1749 | vernita
1750 | tamar
1751 | siobhan
1752 | renea
1753 | rashida
1754 | ouida
1755 | nilsa
1756 | meryl
1757 | kristyn
1758 | julieta
1759 | danica
1760 | breanne
1761 | aurea
1762 | anglea
1763 | sherron
1764 | odette
1765 | malia
1766 | lorelei
1767 | leesa
1768 | kenna
1769 | kathlyn
1770 | fiona
1771 | charlette
1772 | suzie
1773 | shantell
1774 | sabra
1775 | racquel
1776 | myong
1777 | mira
1778 | martine
1779 | lucienne
1780 | lavada
1781 | juliann
1782 | elvera
1783 | delphia
1784 | christiane
1785 | charolette
1786 | carri
1787 | asha
1788 | angella
1789 | paola
1790 | ninfa
1791 | leda
1792 | lai
1793 | eda
1794 | stefani
1795 | shanell
1796 | palma
1797 | machelle
1798 | lissa
1799 | kecia
1800 | kathryne
1801 | karlene
1802 | julissa
1803 | jettie
1804 | jenniffer
1805 | hui
1806 | corrina
1807 | carolann
1808 | alena
1809 | rosaria
1810 | myrtice
1811 | marylee
1812 | liane
1813 | kenyatta
1814 | judie
1815 | janey
1816 | elmira
1817 | eldora
1818 | denna
1819 | cristi
1820 | cathi
1821 | zaida
1822 | vonnie
1823 | viva
1824 | vernie
1825 | rosaline
1826 | mariela
1827 | luciana
1828 | lesli
1829 | karan
1830 | felice
1831 | deneen
1832 | adina
1833 | wynona
1834 | tarsha
1835 | sheron
1836 | shanita
1837 | shani
1838 | shandra
1839 | randa
1840 | pinkie
1841 | nelida
1842 | marilou
1843 | lyla
1844 | laurene
1845 | laci
1846 | joi
1847 | janene
1848 | dorotha
1849 | daniele
1850 | dani
1851 | carolynn
1852 | carlyn
1853 | berenice
1854 | ayesha
1855 | anneliese
1856 | alethea
1857 | thersa
1858 | tamiko
1859 | rufina
1860 | oliva
1861 | mozell
1862 | marylyn
1863 | kristian
1864 | kathyrn
1865 | kasandra
1866 | kandace
1867 | janae
1868 | domenica
1869 | debbra
1870 | dannielle
1871 | arcelia
1872 | aja
1873 | zenobia
1874 | sharen
1875 | sharee
1876 | lavinia
1877 | kum
1878 | kacie
1879 | jackeline
1880 | huong
1881 | felisa
1882 | emelia
1883 | eleanora
1884 | cythia
1885 | cristin
1886 | claribel
1887 | anastacia
1888 | zulma
1889 | zandra
1890 | yoko
1891 | tenisha
1892 | susann
1893 | sherilyn
1894 | shay
1895 | shawanda
1896 | romana
1897 | mathilda
1898 | linsey
1899 | keiko
1900 | joana
1901 | isela
1902 | gretta
1903 | georgetta
1904 | eugenie
1905 | desirae
1906 | delora
1907 | corazon
1908 | antonina
1909 | anika
1910 | willene
1911 | tracee
1912 | tamatha
1913 | nichelle
1914 | mickie
1915 | maegan
1916 | luana
1917 | lanita
1918 | kelsie
1919 | edelmira
1920 | bree
1921 | afton
1922 | teodora
1923 | tamie
1924 | shena
1925 | meg
1926 | linh
1927 | keli
1928 | kaci
1929 | danyelle
1930 | arlette
1931 | albertine
1932 | adelle
1933 | tiffiny
1934 | simona
1935 | nicolasa
1936 | nichol
1937 | nia
1938 | nakisha
1939 | mee
1940 | maira
1941 | loreen
1942 | kizzy
1943 | fallon
1944 | christene
1945 | bobbye
1946 | vincenza
1947 | tanja
1948 | rubie
1949 | roni
1950 | queenie
1951 | margarett
1952 | kimberli
1953 | irmgard
1954 | idell
1955 | hilma
1956 | evelina
1957 | esta
1958 | emilee
1959 | dennise
1960 | dania
1961 | carie
1962 | wai
1963 | risa
1964 | rikki
1965 | particia
1966 | mui
1967 | masako
1968 | luvenia
1969 | loree
1970 | loni
1971 | lien
1972 | gigi
1973 | florencia
1974 | denita
1975 | billye
1976 | tomika
1977 | sharita
1978 | rana
1979 | nikole
1980 | neoma
1981 | margarite
1982 | madalyn
1983 | lucina
1984 | laila
1985 | kali
1986 | jenette
1987 | gabriele
1988 | evelyne
1989 | elenora
1990 | clementina
1991 | alejandrina
1992 | zulema
1993 | violette
1994 | vannessa
1995 | thresa
1996 | retta
1997 | pia
1998 | patience
1999 | noella
2000 | nickie
2001 | jonell
2002 | chaya
2003 | camelia
2004 | bethel
2005 | anya
2006 | suzann
2007 | shu
2008 | mila
2009 | lilla
2010 | laverna
2011 | keesha
2012 | kattie
2013 | georgene
2014 | eveline
2015 | estell
2016 | elizbeth
2017 | vivienne
2018 | vallie
2019 | trudie
2020 | stephane
2021 | magaly
2022 | madie
2023 | kenyetta
2024 | karren
2025 | janetta
2026 | hermine
2027 | drucilla
2028 | debbi
2029 | celestina
2030 | candie
2031 | britni
2032 | beckie
2033 | amina
2034 | zita
2035 | yun
2036 | yolande
2037 | vivien
2038 | vernetta
2039 | trudi
2040 | sommer
2041 | pearle
2042 | patrina
2043 | ossie
2044 | nicolle
2045 | loyce
2046 | letty
2047 | larisa
2048 | katharina
2049 | joselyn
2050 | jonelle
2051 | jenell
2052 | iesha
2053 | heide
2054 | florinda
2055 | florentina
2056 | flo
2057 | elodia
2058 | dorine
2059 | brunilda
2060 | brigid
2061 | ashli
2062 | ardella
2063 | twana
2064 | thu
2065 | tarah
2066 | shavon
2067 | serina
2068 | rayna
2069 | ramonita
2070 | nga
2071 | margurite
2072 | lucrecia
2073 | kourtney
2074 | kati
2075 | jesenia
2076 | crista
2077 | ayana
2078 | alica
2079 | alia
2080 | vinnie
2081 | suellen
2082 | romelia
2083 | rachell
2084 | olympia
2085 | michiko
2086 | kathaleen
2087 | jolie
2088 | jessi
2089 | janessa
2090 | hana
2091 | elease
2092 | carletta
2093 | britany
2094 | shona
2095 | salome
2096 | rosamond
2097 | regena
2098 | raina
2099 | ngoc
2100 | nelia
2101 | louvenia
2102 | lesia
2103 | latrina
2104 | laticia
2105 | larhonda
2106 | jina
2107 | jacki
2108 | emmy
2109 | deeann
2110 | coretta
2111 | arnetta
2112 | thalia
2113 | shanice
2114 | neta
2115 | mikki
2116 | micki
2117 | lonna
2118 | leana
2119 | lashunda
2120 | kiley
2121 | joye
2122 | jacqulyn
2123 | ignacia
2124 | hyun
2125 | hiroko
2126 | henriette
2127 | elayne
2128 | delinda
2129 | dahlia
2130 | coreen
2131 | consuela
2132 | conchita
2133 | celine
2134 | babette
2135 | ayanna
2136 | anette
2137 | albertina
2138 | shawnee
2139 | shaneka
2140 | quiana
2141 | pamelia
2142 | min
2143 | merri
2144 | merlene
2145 | margit
2146 | kiesha
2147 | kiera
2148 | kaylene
2149 | jodee
2150 | jenise
2151 | erlene
2152 | emmie
2153 | dalila
2154 | daisey
2155 | casie
2156 | belia
2157 | babara
2158 | versie
2159 | vanesa
2160 | shelba
2161 | shawnda
2162 | nikia
2163 | naoma
2164 | marna
2165 | margeret
2166 | madaline
2167 | lawana
2168 | kindra
2169 | jutta
2170 | jazmine
2171 | janett
2172 | hannelore
2173 | glendora
2174 | gertrud
2175 | garnett
2176 | freeda
2177 | frederica
2178 | florance
2179 | flavia
2180 | carline
2181 | beverlee
2182 | anjanette
2183 | valda
2184 | tamala
2185 | shonna
2186 | sha
2187 | sarina
2188 | oneida
2189 | merilyn
2190 | marleen
2191 | lurline
2192 | lenna
2193 | katherin
2194 | jin
2195 | jeni
2196 | hae
2197 | gracia
2198 | glady
2199 | farah
2200 | enola
2201 | ema
2202 | dominque
2203 | devona
2204 | delana
2205 | cecila
2206 | caprice
2207 | alysha
2208 | alethia
2209 | vena
2210 | theresia
2211 | tawny
2212 | shakira
2213 | samara
2214 | sachiko
2215 | rachele
2216 | pamella
2217 | marni
2218 | mariel
2219 | maren
2220 | malisa
2221 | ligia
2222 | lera
2223 | latoria
2224 | larae
2225 | kimber
2226 | kathern
2227 | karey
2228 | jennefer
2229 | janeth
2230 | halina
2231 | fredia
2232 | delisa
2233 | debroah
2234 | ciera
2235 | angelika
2236 | andree
2237 | altha
2238 | yen
2239 | vivan
2240 | terresa
2241 | tanna
2242 | suk
2243 | sudie
2244 | soo
2245 | signe
2246 | salena
2247 | ronni
2248 | rebbecca
2249 | myrtie
2250 | malika
2251 | maida
2252 | loan
2253 | leonarda
2254 | kayleigh
2255 | ethyl
2256 | ellyn
2257 | dayle
2258 | cammie
2259 | brittni
2260 | birgit
2261 | avelina
2262 | asuncion
2263 | arianna
2264 | akiko
2265 | venice
2266 | tyesha
2267 | tonie
2268 | tiesha
2269 | takisha
2270 | steffanie
2271 | sindy
2272 | meghann
2273 | manda
2274 | macie
2275 | kellye
2276 | kellee
2277 | joslyn
2278 | inger
2279 | indira
2280 | glinda
2281 | glennis
2282 | fernanda
2283 | faustina
2284 | eneida
2285 | elicia
2286 | dot
2287 | digna
2288 | dell
2289 | arletta
2290 | willia
2291 | tammara
2292 | tabetha
2293 | sherrell
2294 | sari
2295 | rebbeca
2296 | pauletta
2297 | natosha
2298 | nakita
2299 | mammie
2300 | kenisha
2301 | kazuko
2302 | kassie
2303 | earlean
2304 | daphine
2305 | corliss
2306 | clotilde
2307 | carolyne
2308 | bernetta
2309 | augustina
2310 | audrea
2311 | annis
2312 | annabell
2313 | yan
2314 | tennille
2315 | tamica
2316 | selene
2317 | rosana
2318 | regenia
2319 | qiana
2320 | markita
2321 | macy
2322 | leeanne
2323 | laurine
2324 | kym
2325 | jessenia
2326 | janita
2327 | georgine
2328 | genie
2329 | emiko
2330 | elvie
2331 | deandra
2332 | dagmar
2333 | corie
2334 | collen
2335 | cherish
2336 | romaine
2337 | porsha
2338 | pearlene
2339 | micheline
2340 | merna
2341 | margorie
2342 | margaretta
2343 | lore
2344 | jenine
2345 | hermina
2346 | fredericka
2347 | elke
2348 | drusilla
2349 | dorathy
2350 | dione
2351 | celena
2352 | brigida
2353 | angeles
2354 | allegra
2355 | tamekia
2356 | synthia
2357 | sook
2358 | slyvia
2359 | rosann
2360 | reatha
2361 | raye
2362 | marquetta
2363 | margart
2364 | layla
2365 | kymberly
2366 | kiana
2367 | kayleen
2368 | katlyn
2369 | karmen
2370 | joella
2371 | irina
2372 | emelda
2373 | eleni
2374 | detra
2375 | clemmie
2376 | cheryll
2377 | chantell
2378 | cathey
2379 | arnita
2380 | arla
2381 | angle
2382 | angelic
2383 | alyse
2384 | zofia
2385 | thomasine
2386 | tennie
2387 | sherly
2388 | sherley
2389 | sharyl
2390 | remedios
2391 | petrina
2392 | nickole
2393 | myung
2394 | myrle
2395 | mozella
2396 | louanne
2397 | lisha
2398 | latia
2399 | krysta
2400 | julienne
2401 | jeanene
2402 | jacqualine
2403 | isaura
2404 | gwenda
2405 | earleen
2406 | cleopatra
2407 | carlie
2408 | audie
2409 | antonietta
2410 | alise
2411 | verdell
2412 | tomoko
2413 | thao
2414 | talisha
2415 | shemika
2416 | savanna
2417 | santina
2418 | rosia
2419 | raeann
2420 | odilia
2421 | nana
2422 | minna
2423 | magan
2424 | lynelle
2425 | karma
2426 | joeann
2427 | ivana
2428 | inell
2429 | ilana
2430 | hye
2431 | hee
2432 | gudrun
2433 | dreama
2434 | crissy
2435 | chante
2436 | carmelina
2437 | arvilla
2438 | annamae
2439 | alvera
2440 | aleida
2441 | yanira
2442 | vanda
2443 | tianna
2444 | tam
2445 | stefania
2446 | shira
2447 | nicol
2448 | nancie
2449 | monserrate
2450 | melynda
2451 | melany
2452 | lovella
2453 | laure
2454 | kacy
2455 | jacquelynn
2456 | hyon
2457 | gertha
2458 | eliana
2459 | christena
2460 | christeen
2461 | charise
2462 | caterina
2463 | carley
2464 | candyce
2465 | arlena
2466 | ammie
2467 | willette
2468 | vanita
2469 | tuyet
2470 | syreeta
2471 | penney
2472 | nyla
2473 | maryam
2474 | marya
2475 | magen
2476 | ludie
2477 | loma
2478 | livia
2479 | lanell
2480 | kimberlie
2481 | julee
2482 | donetta
2483 | diedra
2484 | denisha
2485 | deane
2486 | dawne
2487 | clarine
2488 | cherryl
2489 | bronwyn
2490 | alla
2491 | valery
2492 | tonda
2493 | sueann
2494 | soraya
2495 | shoshana
2496 | shela
2497 | sharleen
2498 | shanelle
2499 | nerissa
2500 | meridith
2501 | mellie
2502 | maye
2503 | maple
2504 | magaret
2505 | lili
2506 | leonila
2507 | leonie
2508 | leeanna
2509 | lavonia
2510 | lavera
2511 | kristel
2512 | kathey
2513 | kathe
2514 | jann
2515 | ilda
2516 | hildred
2517 | hildegarde
2518 | genia
2519 | fumiko
2520 | evelin
2521 | ermelinda
2522 | elly
2523 | dung
2524 | doloris
2525 | dionna
2526 | danae
2527 | berneice
2528 | annice
2529 | alix
2530 | verena
2531 | verdie
2532 | shawnna
2533 | shawana
2534 | shaunna
2535 | rozella
2536 | randee
2537 | ranae
2538 | milagro
2539 | lynell
2540 | luise
2541 | loida
2542 | lisbeth
2543 | karleen
2544 | junita
2545 | jona
2546 | isis
2547 | hyacinth
2548 | hedy
2549 | gwenn
2550 | ethelene
2551 | erline
2552 | donya
2553 | domonique
2554 | delicia
2555 | dannette
2556 | cicely
2557 | branda
2558 | blythe
2559 | bethann
2560 | ashlyn
2561 | annalee
2562 | alline
2563 | yuko
2564 | vella
2565 | trang
2566 | towanda
2567 | tesha
2568 | sherlyn
2569 | narcisa
2570 | miguelina
2571 | meri
2572 | maybell
2573 | marlana
2574 | marguerita
2575 | madlyn
2576 | lory
2577 | loriann
2578 | leonore
2579 | leighann
2580 | laurice
2581 | latesha
2582 | laronda
2583 | katrice
2584 | kasie
2585 | kaley
2586 | jadwiga
2587 | glennie
2588 | gearldine
2589 | francina
2590 | epifania
2591 | dyan
2592 | dorie
2593 | diedre
2594 | denese
2595 | demetrice
2596 | delena
2597 | cristie
2598 | cleora
2599 | catarina
2600 | carisa
2601 | barbera
2602 | almeta
2603 | trula
2604 | tereasa
2605 | solange
2606 | sheilah
2607 | shavonne
2608 | sanora
2609 | rochell
2610 | mathilde
2611 | margareta
2612 | maia
2613 | lynsey
2614 | lawanna
2615 | launa
2616 | kena
2617 | keena
2618 | katia
2619 | glynda
2620 | gaylene
2621 | elvina
2622 | elanor
2623 | danuta
2624 | danika
2625 | cristen
2626 | cordie
2627 | coletta
2628 | clarita
2629 | carmon
2630 | brynn
2631 | azucena
2632 | aundrea
2633 | angele
2634 | verlie
2635 | verlene
2636 | tamesha
2637 | silvana
2638 | sebrina
2639 | samira
2640 | reda
2641 | raylene
2642 | penni
2643 | norah
2644 | noma
2645 | mireille
2646 | melissia
2647 | maryalice
2648 | laraine
2649 | kimbery
2650 | karyl
2651 | karine
2652 | kam
2653 | jolanda
2654 | johana
2655 | jesusa
2656 | jaleesa
2657 | jacquelyne
2658 | iluminada
2659 | hilaria
2660 | hanh
2661 | gennie
2662 | francie
2663 | floretta
2664 | exie
2665 | edda
2666 | drema
2667 | delpha
2668 | bev
2669 | barbar
2670 | assunta
2671 | ardell
2672 | annalisa
2673 | alisia
2674 | yukiko
2675 | yolando
2676 | wonda
2677 | wei
2678 | waltraud
2679 | veta
2680 | temeka
2681 | tameika
2682 | shirleen
2683 | shenita
2684 | piedad
2685 | ozella
2686 | mirtha
2687 | marilu
2688 | kimiko
2689 | juliane
2690 | jenice
2691 | janay
2692 | jacquiline
2693 | hilde
2694 | fae
2695 | elois
2696 | echo
2697 | devorah
2698 | chau
2699 | brinda
2700 | betsey
2701 | arminda
2702 | aracelis
2703 | apryl
2704 | annett
2705 | alishia
2706 | veola
2707 | usha
2708 | toshiko
2709 | theola
2710 | tashia
2711 | talitha
2712 | shery
2713 | renetta
2714 | reiko
2715 | rasheeda
2716 | obdulia
2717 | mika
2718 | melaine
2719 | meggan
2720 | marlen
2721 | marget
2722 | marceline
2723 | mana
2724 | magdalen
2725 | librada
2726 | lezlie
2727 | latashia
2728 | lasandra
2729 | kelle
2730 | isidra
2731 | isa
2732 | inocencia
2733 | gwyn
2734 | francoise
2735 | erminia
2736 | erinn
2737 | dimple
2738 | devora
2739 | criselda
2740 | armanda
2741 | arie
2742 | ariane
2743 | angelena
2744 | aliza
2745 | adriene
2746 | adaline
2747 | xochitl
2748 | twanna
2749 | tomiko
2750 | tamisha
2751 | taisha
2752 | susy
2753 | siu
2754 | rutha
2755 | rhona
2756 | noriko
2757 | natashia
2758 | merrie
2759 | marinda
2760 | mariko
2761 | margert
2762 | loris
2763 | lizzette
2764 | leisha
2765 | kaila
2766 | joannie
2767 | jerrica
2768 | jene
2769 | jannet
2770 | janee
2771 | jacinda
2772 | herta
2773 | elenore
2774 | doretta
2775 | delaine
2776 | daniell
2777 | claudie
2778 | britta
2779 | apolonia
2780 | amberly
2781 | alease
2782 | yuri
2783 | yuk
2784 | wen
2785 | waneta
2786 | ute
2787 | tomi
2788 | sharri
2789 | sandie
2790 | roselle
2791 | reynalda
2792 | raguel
2793 | phylicia
2794 | patria
2795 | olimpia
2796 | odelia
2797 | mitzie
2798 | minda
2799 | mignon
2800 | mica
2801 | mendy
2802 | marivel
2803 | maile
2804 | lynetta
2805 | lavette
2806 | lauryn
2807 | latrisha
2808 | lakiesha
2809 | kiersten
2810 | kary
2811 | josphine
2812 | jolyn
2813 | jetta
2814 | janise
2815 | jacquie
2816 | ivelisse
2817 | glynis
2818 | gianna
2819 | gaynelle
2820 | danyell
2821 | danille
2822 | dacia
2823 | coralee
2824 | cher
2825 | ceola
2826 | arianne
2827 | aleshia
2828 | yung
2829 | williemae
2830 | trinh
2831 | thora
2832 | tai
2833 | svetlana
2834 | sherika
2835 | shemeka
2836 | shaunda
2837 | roseline
2838 | ricki
2839 | melda
2840 | mallie
2841 | lavonna
2842 | latina
2843 | laquanda
2844 | lala
2845 | lachelle
2846 | klara
2847 | kandis
2848 | johna
2849 | jeanmarie
2850 | jaye
2851 | grayce
2852 | gertude
2853 | emerita
2854 | ebonie
2855 | clorinda
2856 | ching
2857 | chery
2858 | carola
2859 | breann
2860 | blossom
2861 | bernardine
2862 | becki
2863 | arletha
2864 | argelia
2865 | ara
2866 | alita
2867 | yulanda
2868 | yon
2869 | yessenia
2870 | tobi
2871 | tasia
2872 | sylvie
2873 | shirl
2874 | shirely
2875 | shella
2876 | shantelle
2877 | sacha
2878 | rebecka
2879 | providencia
2880 | paulene
2881 | misha
2882 | miki
2883 | marline
2884 | marica
2885 | lorita
2886 | latoyia
2887 | lasonya
2888 | kerstin
2889 | kenda
2890 | keitha
2891 | kathrin
2892 | jaymie
2893 | gricelda
2894 | ginette
2895 | eryn
2896 | elina
2897 | elfrieda
2898 | danyel
2899 | cheree
2900 | chanelle
2901 | barrie
2902 | aurore
2903 | annamaria
2904 | alleen
2905 | ailene
2906 | aide
2907 | yasmine
2908 | vashti
2909 | treasa
2910 | tiffaney
2911 | sheryll
2912 | sharie
2913 | shanae
2914 | sau
2915 | raisa
2916 | neda
2917 | mitsuko
2918 | mirella
2919 | milda
2920 | maryanna
2921 | maragret
2922 | mabelle
2923 | luetta
2924 | lorina
2925 | letisha
2926 | latarsha
2927 | lanelle
2928 | lajuana
2929 | krissy
2930 | karly
2931 | karena
2932 | jessika
2933 | jerica
2934 | jeanelle
2935 | jalisa
2936 | jacelyn
2937 | izola
2938 | euna
2939 | etha
2940 | domitila
2941 | dominica
2942 | daina
2943 | creola
2944 | carli
2945 | camie
2946 | brittny
2947 | ashanti
2948 | anisha
2949 | aleen
2950 | adah
2951 | yasuko
2952 | valrie
2953 | tona
2954 | tinisha
2955 | thi
2956 | terisa
2957 | taneka
2958 | simonne
2959 | shalanda
2960 | serita
2961 | ressie
2962 | refugia
2963 | olene
2964 | margherita
2965 | mandie
2966 | maire
2967 | lyndia
2968 | luci
2969 | lorriane
2970 | loreta
2971 | leonia
2972 | lavona
2973 | lashawnda
2974 | lakia
2975 | kyoko
2976 | krystina
2977 | krysten
2978 | kenia
2979 | kelsi
2980 | jeanice
2981 | isobel
2982 | georgiann
2983 | genny
2984 | felicidad
2985 | eilene
2986 | deloise
2987 | conception
2988 | clora
2989 | cherilyn
2990 | calandra
2991 | armandina
2992 | anisa
2993 | ula
2994 | tiera
2995 | theressa
2996 | stephania
2997 | sima
2998 | shyla
2999 | shonta
3000 | shera
3001 | shaquita
3002 | shala
3003 | rossana
3004 | nohemi
3005 | nery
3006 | moriah
3007 | melita
3008 | melida
3009 | melani
3010 | marylynn
3011 | marisha
3012 | mariette
3013 | malorie
3014 | madelene
3015 | ludivina
3016 | loria
3017 | lorette
3018 | loralee
3019 | lianne
3020 | lavenia
3021 | laurinda
3022 | lashon
3023 | kit
3024 | kimi
3025 | keila
3026 | katelynn
3027 | kai
3028 | jone
3029 | joane
3030 | jayna
3031 | janella
3032 | hue
3033 | hertha
3034 | francene
3035 | elinore
3036 | despina
3037 | delsie
3038 | deedra
3039 | clemencia
3040 | carolin
3041 | bulah
3042 | brittanie
3043 | bok
3044 | blondell
3045 | bibi
3046 | beaulah
3047 | beata
3048 | annita
3049 | agripina
3050 | virgen
3051 | valene
3052 | twanda
3053 | tommye
3054 | toi
3055 | tarra
3056 | tari
3057 | tammera
3058 | shakia
3059 | sadye
3060 | ruthanne
3061 | rochel
3062 | rivka
3063 | pura
3064 | nenita
3065 | natisha
3066 | merrilee
3067 | melodee
3068 | marvis
3069 | lucilla
3070 | leena
3071 | laveta
3072 | larita
3073 | lanie
3074 | keren
3075 | ileen
3076 | georgeann
3077 | genna
3078 | frida
3079 | ewa
3080 | eufemia
3081 | emely
3082 | ela
3083 | edyth
3084 | deonna
3085 | deadra
3086 | darlena
3087 | chanell
3088 | cathern
3089 | cassondra
3090 | cassaundra
3091 | bernarda
3092 | berna
3093 | arlinda
3094 | anamaria
3095 | vertie
3096 | valeri
3097 | torri
3098 | tatyana
3099 | stasia
3100 | sherise
3101 | sherill
3102 | sanda
3103 | ruthe
3104 | rosy
3105 | robbi
3106 | ranee
3107 | quyen
3108 | pearly
3109 | palmira
3110 | onita
3111 | nisha
3112 | niesha
3113 | nida
3114 | nam
3115 | merlyn
3116 | mayola
3117 | marylouise
3118 | marth
3119 | margene
3120 | madelaine
3121 | londa
3122 | leontine
3123 | leoma
3124 | leia
3125 | lauralee
3126 | lanora
3127 | lakita
3128 | kiyoko
3129 | keturah
3130 | katelin
3131 | kareen
3132 | jonie
3133 | johnette
3134 | jenee
3135 | jeanett
3136 | izetta
3137 | hiedi
3138 | heike
3139 | hassie
3140 | giuseppina
3141 | georgann
3142 | fidela
3143 | fernande
3144 | elwanda
3145 | ellamae
3146 | eliz
3147 | dusti
3148 | dotty
3149 | cyndy
3150 | coralie
3151 | celesta
3152 | argentina
3153 | alverta
3154 | xenia
3155 | wava
3156 | vanetta
3157 | torrie
3158 | tashina
3159 | tandy
3160 | tambra
3161 | tama
3162 | stepanie
3163 | shila
3164 | shaunta
3165 | sharan
3166 | shaniqua
3167 | shae
3168 | setsuko
3169 | serafina
3170 | sandee
3171 | rosamaria
3172 | priscila
3173 | olinda
3174 | nadene
3175 | muoi
3176 | michelina
3177 | mercedez
3178 | maryrose
3179 | marcene
3180 | mao
3181 | magali
3182 | mafalda
3183 | lannie
3184 | kayce
3185 | karoline
3186 | kamilah
3187 | kamala
3188 | justa
3189 | joline
3190 | jennine
3191 | jacquetta
3192 | iraida
3193 | georgeanna
3194 | franchesca
3195 | emeline
3196 | elane
3197 | ehtel
3198 | earlie
3199 | dulcie
3200 | dalene
3201 | classie
3202 | chere
3203 | charis
3204 | caroyln
3205 | carmina
3206 | carita
3207 | bethanie
3208 | ayako
3209 | arica
3210 | alysa
3211 | alessandra
3212 | akilah
3213 | adrien
3214 | zetta
3215 | youlanda
3216 | yelena
3217 | yahaira
3218 | wendolyn
3219 | tijuana
3220 | terina
3221 | teresia
3222 | suzi
3223 | sherell
3224 | shavonda
3225 | shaunte
3226 | sharda
3227 | shakita
3228 | sena
3229 | ryann
3230 | rubi
3231 | riva
3232 | reginia
3233 | rachal
3234 | parthenia
3235 | pamula
3236 | monnie
3237 | monet
3238 | michaele
3239 | melia
3240 | malka
3241 | maisha
3242 | lisandra
3243 | lekisha
3244 | lean
3245 | lakendra
3246 | krystin
3247 | kortney
3248 | kizzie
3249 | kittie
3250 | kera
3251 | kendal
3252 | kemberly
3253 | kanisha
3254 | julene
3255 | jule
3256 | johanne
3257 | jamee
3258 | halley
3259 | gidget
3260 | galina
3261 | fredricka
3262 | fleta
3263 | fatimah
3264 | eusebia
3265 | elza
3266 | eleonore
3267 | dorthey
3268 | doria
3269 | donella
3270 | dinorah
3271 | delorse
3272 | claretha
3273 | christinia
3274 | charlyn
3275 | bong
3276 | belkis
3277 | azzie
3278 | andera
3279 | aiko
3280 | adena
3281 | yer
3282 | yajaira
3283 | wan
3284 | vania
3285 | ulrike
3286 | toshia
3287 | tifany
3288 | stefany
3289 | shizue
3290 | shenika
3291 | shawanna
3292 | sharolyn
3293 | sharilyn
3294 | shaquana
3295 | shantay
3296 | rozanne
3297 | roselee
3298 | remona
3299 | reanna
3300 | raelene
3301 | phung
3302 | petronila
3303 | natacha
3304 | nancey
3305 | myrl
3306 | miyoko
3307 | miesha
3308 | merideth
3309 | marvella
3310 | marquitta
3311 | marhta
3312 | marchelle
3313 | lizeth
3314 | libbie
3315 | lahoma
3316 | ladawn
3317 | kina
3318 | katheleen
3319 | katharyn
3320 | karisa
3321 | kaleigh
3322 | junie
3323 | julieann
3324 | johnsie
3325 | janean
3326 | jaimee
3327 | jackqueline
3328 | hisako
3329 | herma
3330 | helaine
3331 | gwyneth
3332 | gita
3333 | eustolia
3334 | emelina
3335 | elin
3336 | edris
3337 | donnette
3338 | donnetta
3339 | dierdre
3340 | denae
3341 | darcel
3342 | clarisa
3343 | cinderella
3344 | chia
3345 | charlesetta
3346 | charita
3347 | celsa
3348 | cassy
3349 | cassi
3350 | carlee
3351 | bruna
3352 | brittaney
3353 | brande
3354 | billi
3355 | bao
3356 | antonetta
3357 | angla
3358 | angelyn
3359 | analisa
3360 | alane
3361 | wenona
3362 | wendie
3363 | veronique
3364 | vannesa
3365 | tobie
3366 | tempie
3367 | sumiko
3368 | sulema
3369 | sparkle
3370 | somer
3371 | sheba
3372 | sharice
3373 | shanel
3374 | shalon
3375 | rosio
3376 | roselia
3377 | renay
3378 | rema
3379 | reena
3380 | ozie
3381 | oretha
3382 | oralee
3383 | oda
3384 | ngan
3385 | nakesha
3386 | milly
3387 | marybelle
3388 | margrett
3389 | maragaret
3390 | manie
3391 | lurlene
3392 | lillia
3393 | lieselotte
3394 | lavelle
3395 | lashaunda
3396 | lakeesha
3397 | kaycee
3398 | kalyn
3399 | joya
3400 | joette
3401 | jenae
3402 | janiece
3403 | illa
3404 | grisel
3405 | glayds
3406 | genevie
3407 | gala
3408 | fredda
3409 | eleonor
3410 | debera
3411 | deandrea
3412 | corrinne
3413 | cordia
3414 | contessa
3415 | colene
3416 | cleotilde
3417 | chantay
3418 | cecille
3419 | beatris
3420 | azalee
3421 | arlean
3422 | ardath
3423 | anjelica
3424 | anja
3425 | alfredia
3426 | aleisha
3427 | zada
3428 | yuonne
3429 | willodean
3430 | vennie
3431 | vanna
3432 | tyisha
3433 | tova
3434 | torie
3435 | tonisha
3436 | tilda
3437 | tien
3438 | sirena
3439 | sherril
3440 | shanti
3441 | senaida
3442 | samella
3443 | robbyn
3444 | renda
3445 | reita
3446 | phebe
3447 | paulita
3448 | nobuko
3449 | nguyet
3450 | neomi
3451 | mikaela
3452 | melania
3453 | maximina
3454 | marg
3455 | maisie
3456 | lynna
3457 | lilli
3458 | lashaun
3459 | lakenya
3460 | lael
3461 | kirstie
3462 | kathline
3463 | kasha
3464 | karlyn
3465 | karima
3466 | jovan
3467 | josefine
3468 | jennell
3469 | jacqui
3470 | jackelyn
3471 | hyo
3472 | hien
3473 | grazyna
3474 | florrie
3475 | floria
3476 | eleonora
3477 | dwana
3478 | dorla
3479 | delmy
3480 | deja
3481 | dede
3482 | dann
3483 | crysta
3484 | clelia
3485 | claris
3486 | chieko
3487 | cherlyn
3488 | cherelle
3489 | charmain
3490 | chara
3491 | cammy
3492 | bee
3493 | arnette
3494 | ardelle
3495 | annika
3496 | amiee
3497 | amee
3498 | allena
3499 | yvone
3500 | yuki
3501 | yoshie
3502 | yevette
3503 | yael
3504 | willetta
3505 | voncile
3506 | venetta
3507 | tula
3508 | tonette
3509 | timika
3510 | temika
3511 | telma
3512 | teisha
3513 | taren
3514 | stacee
3515 | shawnta
3516 | saturnina
3517 | ricarda
3518 | pok
3519 | pasty
3520 | onie
3521 | nubia
3522 | marielle
3523 | mariella
3524 | marianela
3525 | mardell
3526 | luanna
3527 | loise
3528 | lisabeth
3529 | lindsy
3530 | lilliana
3531 | lilliam
3532 | lelah
3533 | leigha
3534 | leanora
3535 | kristeen
3536 | khalilah
3537 | keeley
3538 | kandra
3539 | junko
3540 | joaquina
3541 | jerlene
3542 | jani
3543 | jamika
3544 | hsiu
3545 | hermila
3546 | genevive
3547 | evia
3548 | eugena
3549 | emmaline
3550 | elfreda
3551 | elene
3552 | donette
3553 | delcie
3554 | deeanna
3555 | darcey
3556 | cuc
3557 | clarinda
3558 | cira
3559 | chae
3560 | celinda
3561 | catheryn
3562 | casimira
3563 | carmelia
3564 | camellia
3565 | breana
3566 | bobette
3567 | bernardina
3568 | bebe
3569 | basilia
3570 | arlyne
3571 | amal
3572 | alayna
3573 | zonia
3574 | zenia
3575 | yuriko
3576 | yaeko
3577 | wynell
3578 | willena
3579 | vernia
3580 | tora
3581 | terrilyn
3582 | terica
3583 | tenesha
3584 | tawna
3585 | tajuana
3586 | taina
3587 | stephnie
3588 | sona
3589 | sina
3590 | shondra
3591 | shizuko
3592 | sherlene
3593 | sherice
3594 | sharika
3595 | rossie
3596 | rosena
3597 | rima
3598 | ria
3599 | rheba
3600 | renna
3601 | natalya
3602 | nancee
3603 | melodi
3604 | meda
3605 | matha
3606 | marketta
3607 | maricruz
3608 | marcelene
3609 | malvina
3610 | luba
3611 | louetta
3612 | leida
3613 | lecia
3614 | lauran
3615 | lashawna
3616 | laine
3617 | khadijah
3618 | katerine
3619 | kasi
3620 | kallie
3621 | julietta
3622 | jesusita
3623 | jestine
3624 | jessia
3625 | jeffie
3626 | janyce
3627 | isadora
3628 | georgianne
3629 | fidelia
3630 | evita
3631 | eura
3632 | eulah
3633 | estefana
3634 | elsy
3635 | eladia
3636 | dodie
3637 | dia
3638 | denisse
3639 | deloras
3640 | delila
3641 | daysi
3642 | crystle
3643 | concha
3644 | claretta
3645 | charlsie
3646 | charlena
3647 | carylon
3648 | bettyann
3649 | asley
3650 | ashlea
3651 | amira
3652 | agueda
3653 | agnus
3654 | yuette
3655 | vinita
3656 | victorina
3657 | tynisha
3658 | treena
3659 | toccara
3660 | tish
3661 | thomasena
3662 | tegan
3663 | soila
3664 | shenna
3665 | sharmaine
3666 | shantae
3667 | shandi
3668 | september
3669 | saran
3670 | sarai
3671 | sana
3672 | rosette
3673 | rolande
3674 | regine
3675 | otelia
3676 | olevia
3677 | nicholle
3678 | necole
3679 | naida
3680 | myrta
3681 | myesha
3682 | mitsue
3683 | minta
3684 | mertie
3685 | margy
3686 | mahalia
3687 | madalene
3688 | loura
3689 | lorean
3690 | lesha
3691 | leonida
3692 | lenita
3693 | lavone
3694 | lashell
3695 | lashandra
3696 | lamonica
3697 | kimbra
3698 | katherina
3699 | karry
3700 | kanesha
3701 | jong
3702 | jeneva
3703 | jaquelyn
3704 | hwa
3705 | gilma
3706 | ghislaine
3707 | gertrudis
3708 | fransisca
3709 | fermina
3710 | ettie
3711 | etsuko
3712 | ellan
3713 | elidia
3714 | edra
3715 | dorethea
3716 | doreatha
3717 | denyse
3718 | deetta
3719 | daine
3720 | cyrstal
3721 | corrin
3722 | cayla
3723 | carlita
3724 | camila
3725 | burma
3726 | bula
3727 | buena
3728 | barabara
3729 | avril
3730 | alaine
3731 | zana
3732 | wilhemina
3733 | wanetta
3734 | veronika
3735 | verline
3736 | vasiliki
3737 | tonita
3738 | tisa
3739 | teofila
3740 | tayna
3741 | taunya
3742 | tandra
3743 | takako
3744 | sunni
3745 | suanne
3746 | sixta
3747 | sharell
3748 | seema
3749 | rosenda
3750 | robena
3751 | raymonde
3752 | pei
3753 | pamila
3754 | ozell
3755 | neida
3756 | mistie
3757 | micha
3758 | merissa
3759 | maurita
3760 | maryln
3761 | maryetta
3762 | marcell
3763 | malena
3764 | makeda
3765 | lovetta
3766 | lourie
3767 | lorrine
3768 | lorilee
3769 | laurena
3770 | lashay
3771 | larraine
3772 | laree
3773 | lacresha
3774 | kristle
3775 | krishna
3776 | keva
3777 | keira
3778 | karole
3779 | joie
3780 | jinny
3781 | jeannetta
3782 | jama
3783 | heidy
3784 | gilberte
3785 | gema
3786 | faviola
3787 | evelynn
3788 | enda
3789 | elli
3790 | ellena
3791 | divina
3792 | dagny
3793 | collene
3794 | codi
3795 | cindie
3796 | chassidy
3797 | chasidy
3798 | catrice
3799 | catherina
3800 | cassey
3801 | caroll
3802 | carlena
3803 | candra
3804 | calista
3805 | bryanna
3806 | britteny
3807 | beula
3808 | bari
3809 | audrie
3810 | audria
3811 | ardelia
3812 | annelle
3813 | angila
3814 | alona
3815 | allyn
3816 |
--------------------------------------------------------------------------------
/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 | angel
197 | nathaniel
198 | rafael
199 | edgar
200 | milton
201 | raul
202 | ben
203 | cecil
204 | duane
205 | andre
206 | elmer
207 | brad
208 | gabriel
209 | ron
210 | roland
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 | wm
325 | pete
326 | randolph
327 | garry
328 | geoffrey
329 | jonathon
330 | felipe
331 | bennie
332 | gerardo
333 | ed
334 | dominic
335 | loren
336 | delbert
337 | colin
338 | guillermo
339 | earnest
340 | benny
341 | noel
342 | rodolfo
343 | myron
344 | edmund
345 | salvatore
346 | cedric
347 | lowell
348 | gregg
349 | sherman
350 | devin
351 | sylvester
352 | roosevelt
353 | israel
354 | jermaine
355 | forrest
356 | wilbert
357 | leland
358 | simon
359 | irving
360 | owen
361 | rufus
362 | woodrow
363 | kristopher
364 | levi
365 | marcos
366 | gustavo
367 | lionel
368 | marty
369 | gilberto
370 | clint
371 | nicolas
372 | laurence
373 | ismael
374 | orville
375 | drew
376 | ervin
377 | dewey
378 | al
379 | wilfred
380 | josh
381 | hugo
382 | ignacio
383 | caleb
384 | tomas
385 | sheldon
386 | erick
387 | frankie
388 | darrel
389 | rogelio
390 | terence
391 | alonzo
392 | elias
393 | bert
394 | elbert
395 | ramiro
396 | conrad
397 | noah
398 | grady
399 | phil
400 | cornelius
401 | lamar
402 | rolando
403 | clay
404 | percy
405 | dexter
406 | bradford
407 | merle
408 | darin
409 | amos
410 | terrell
411 | moses
412 | irvin
413 | saul
414 | roman
415 | darnell
416 | randal
417 | tommie
418 | timmy
419 | darrin
420 | brendan
421 | toby
422 | van
423 | abel
424 | dominick
425 | emilio
426 | elijah
427 | cary
428 | domingo
429 | aubrey
430 | emmett
431 | marlon
432 | emanuel
433 | jerald
434 | edmond
435 | emil
436 | dewayne
437 | otto
438 | teddy
439 | reynaldo
440 | bret
441 | jess
442 | trent
443 | humberto
444 | emmanuel
445 | stephan
446 | louie
447 | vicente
448 | lamont
449 | garland
450 | micah
451 | efrain
452 | heath
453 | rodger
454 | demetrius
455 | ethan
456 | eldon
457 | rocky
458 | pierre
459 | eli
460 | bryce
461 | antoine
462 | robbie
463 | kendall
464 | royce
465 | sterling
466 | grover
467 | elton
468 | cleveland
469 | dylan
470 | chuck
471 | damian
472 | reuben
473 | stan
474 | leonardo
475 | russel
476 | erwin
477 | benito
478 | hans
479 | monte
480 | blaine
481 | ernie
482 | curt
483 | quentin
484 | agustin
485 | jamal
486 | devon
487 | adolfo
488 | tyson
489 | wilfredo
490 | bart
491 | jarrod
492 | vance
493 | denis
494 | damien
495 | joaquin
496 | harlan
497 | desmond
498 | elliot
499 | darwin
500 | gregorio
501 | kermit
502 | roscoe
503 | esteban
504 | anton
505 | solomon
506 | norbert
507 | elvin
508 | nolan
509 | carey
510 | rod
511 | quinton
512 | hal
513 | brain
514 | rob
515 | elwood
516 | kendrick
517 | darius
518 | moises
519 | marlin
520 | fidel
521 | thaddeus
522 | cliff
523 | marcel
524 | ali
525 | raphael
526 | bryon
527 | armand
528 | alvaro
529 | jeffry
530 | dane
531 | joesph
532 | thurman
533 | ned
534 | sammie
535 | rusty
536 | michel
537 | monty
538 | rory
539 | fabian
540 | reggie
541 | kris
542 | isaiah
543 | gus
544 | avery
545 | loyd
546 | diego
547 | adolph
548 | millard
549 | rocco
550 | gonzalo
551 | derick
552 | rodrigo
553 | gerry
554 | rigoberto
555 | alphonso
556 | ty
557 | rickie
558 | noe
559 | vern
560 | elvis
561 | bernardo
562 | mauricio
563 | hiram
564 | donovan
565 | basil
566 | nickolas
567 | scot
568 | vince
569 | quincy
570 | eddy
571 | sebastian
572 | federico
573 | ulysses
574 | heriberto
575 | donnell
576 | denny
577 | gavin
578 | emery
579 | romeo
580 | jayson
581 | dion
582 | dante
583 | clement
584 | coy
585 | odell
586 | jarvis
587 | bruno
588 | issac
589 | dudley
590 | sanford
591 | colby
592 | carmelo
593 | nestor
594 | hollis
595 | stefan
596 | donny
597 | art
598 | linwood
599 | beau
600 | weldon
601 | galen
602 | isidro
603 | truman
604 | delmar
605 | johnathon
606 | silas
607 | frederic
608 | irwin
609 | merrill
610 | charley
611 | marcelino
612 | carlo
613 | trenton
614 | kurtis
615 | aurelio
616 | winfred
617 | vito
618 | collin
619 | denver
620 | leonel
621 | emory
622 | pasquale
623 | mohammad
624 | mariano
625 | danial
626 | landon
627 | dirk
628 | branden
629 | adan
630 | numbers
631 | clair
632 | buford
633 | german
634 | bernie
635 | wilmer
636 | emerson
637 | zachery
638 | jacques
639 | errol
640 | josue
641 | edwardo
642 | wilford
643 | theron
644 | raymundo
645 | daren
646 | tristan
647 | robby
648 | lincoln
649 | jame
650 | genaro
651 | octavio
652 | cornell
653 | hung
654 | arron
655 | antony
656 | herschel
657 | alva
658 | giovanni
659 | garth
660 | cyrus
661 | cyril
662 | ronny
663 | stevie
664 | lon
665 | kennith
666 | carmine
667 | augustine
668 | erich
669 | chadwick
670 | wilburn
671 | russ
672 | myles
673 | jonas
674 | mitchel
675 | mervin
676 | zane
677 | jamel
678 | lazaro
679 | alphonse
680 | randell
681 | major
682 | johnie
683 | jarrett
684 | ariel
685 | abdul
686 | dusty
687 | luciano
688 | seymour
689 | scottie
690 | eugenio
691 | mohammed
692 | valentin
693 | arnulfo
694 | lucien
695 | ferdinand
696 | thad
697 | ezra
698 | aldo
699 | rubin
700 | royal
701 | mitch
702 | earle
703 | abe
704 | marquis
705 | lanny
706 | kareem
707 | jamar
708 | boris
709 | isiah
710 | emile
711 | elmo
712 | aron
713 | leopoldo
714 | everette
715 | josef
716 | eloy
717 | dorian
718 | rodrick
719 | reinaldo
720 | lucio
721 | jerrod
722 | weston
723 | hershel
724 | lemuel
725 | lavern
726 | burt
727 | jules
728 | gil
729 | eliseo
730 | ahmad
731 | nigel
732 | efren
733 | antwan
734 | alden
735 | margarito
736 | refugio
737 | dino
738 | osvaldo
739 | les
740 | deandre
741 | normand
742 | kieth
743 | ivory
744 | trey
745 | norberto
746 | napoleon
747 | jerold
748 | fritz
749 | rosendo
750 | milford
751 | sang
752 | deon
753 | christoper
754 | alfonzo
755 | lyman
756 | josiah
757 | brant
758 | wilton
759 | rico
760 | jamaal
761 | dewitt
762 | brenton
763 | yong
764 | olin
765 | faustino
766 | claudio
767 | judson
768 | gino
769 | edgardo
770 | alec
771 | jarred
772 | donn
773 | trinidad
774 | tad
775 | porfirio
776 | odis
777 | lenard
778 | chauncey
779 | tod
780 | mel
781 | marcelo
782 | kory
783 | augustus
784 | keven
785 | hilario
786 | bud
787 | sal
788 | orval
789 | mauro
790 | dannie
791 | zachariah
792 | olen
793 | anibal
794 | milo
795 | jed
796 | thanh
797 | amado
798 | lenny
799 | tory
800 | richie
801 | horacio
802 | brice
803 | mohamed
804 | delmer
805 | dario
806 | mac
807 | jonah
808 | jerrold
809 | robt
810 | hank
811 | sung
812 | rupert
813 | rolland
814 | kenton
815 | damion
816 | chi
817 | antone
818 | waldo
819 | fredric
820 | bradly
821 | kip
822 | burl
823 | tyree
824 | jefferey
825 | ahmed
826 | willy
827 | stanford
828 | oren
829 | moshe
830 | mikel
831 | enoch
832 | brendon
833 | quintin
834 | jamison
835 | florencio
836 | darrick
837 | tobias
838 | minh
839 | hassan
840 | giuseppe
841 | demarcus
842 | cletus
843 | tyrell
844 | lyndon
845 | keenan
846 | werner
847 | theo
848 | geraldo
849 | columbus
850 | chet
851 | bertram
852 | markus
853 | huey
854 | hilton
855 | dwain
856 | donte
857 | tyron
858 | omer
859 | isaias
860 | hipolito
861 | fermin
862 | chung
863 | adalberto
864 | jamey
865 | teodoro
866 | mckinley
867 | maximo
868 | sol
869 | raleigh
870 | lawerence
871 | abram
872 | rashad
873 | emmitt
874 | daron
875 | chong
876 | samual
877 | otha
878 | miquel
879 | eusebio
880 | dong
881 | domenic
882 | darron
883 | wilber
884 | renato
885 | hoyt
886 | haywood
887 | ezekiel
888 | chas
889 | florentino
890 | elroy
891 | clemente
892 | arden
893 | neville
894 | edison
895 | deshawn
896 | carrol
897 | shayne
898 | nathanial
899 | jordon
900 | danilo
901 | claud
902 | val
903 | sherwood
904 | raymon
905 | rayford
906 | cristobal
907 | ambrose
908 | titus
909 | hyman
910 | felton
911 | ezequiel
912 | erasmo
913 | lonny
914 | len
915 | ike
916 | milan
917 | lino
918 | jarod
919 | herb
920 | andreas
921 | rhett
922 | jude
923 | douglass
924 | cordell
925 | oswaldo
926 | ellsworth
927 | virgilio
928 | toney
929 | nathanael
930 | del
931 | benedict
932 | mose
933 | hong
934 | isreal
935 | garret
936 | fausto
937 | asa
938 | arlen
939 | zack
940 | modesto
941 | francesco
942 | manual
943 | jae
944 | gaylord
945 | gaston
946 | filiberto
947 | deangelo
948 | michale
949 | granville
950 | wes
951 | malik
952 | zackary
953 | tuan
954 | nicky
955 | cristopher
956 | antione
957 | malcom
958 | korey
959 | jospeh
960 | colton
961 | waylon
962 | von
963 | hosea
964 | shad
965 | santo
966 | rudolf
967 | rolf
968 | rey
969 | renaldo
970 | marcellus
971 | lucius
972 | kristofer
973 | harland
974 | arnoldo
975 | rueben
976 | leandro
977 | kraig
978 | jerrell
979 | jeromy
980 | hobert
981 | cedrick
982 | arlie
983 | winford
984 | wally
985 | luigi
986 | keneth
987 | jacinto
988 | graig
989 | franklyn
990 | edmundo
991 | sid
992 | leif
993 | jeramy
994 | willian
995 | vincenzo
996 | shon
997 | michal
998 | lynwood
999 | jere
1000 | hai
1001 | elden
1002 | darell
1003 | broderick
1004 | alonso
1005 |
--------------------------------------------------------------------------------
/IMatcherFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Zxcvbn.Matcher;
6 |
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// Interface that matcher factories must implement. Matcher factories return a list of the matchers
11 | /// that will be used to evaluate the password
12 | ///
13 | public interface IMatcherFactory
14 | {
15 | ///
16 | /// Create the matchers to be used by an instance of Zxcvbn.
17 | ///
18 | /// This function will be called once per each password being evaluated, to give the opportunity to provide
19 | /// different user inputs for each password. Matchers that are not dependent on user inputs should ideally be created
20 | /// once and cached so that processing (e.g. dictionary loading) will only have to be performed once, these cached
21 | /// matchers plus any user input matches would then be returned when CreateMatchers is called.
22 | ///
23 | /// List of per-password user information for this invocation
24 | /// An enumerable of objects that will be used to pattern match this password
25 | IEnumerable CreateMatchers(IEnumerable userInputs);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Dropbox, Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/LinqExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn
7 | {
8 | ///
9 | /// Useful shared Linq extensions
10 | ///
11 | static class LinqExtensions
12 | {
13 | ///
14 | /// Used to group elements by a key function, but only where elements are adjacent
15 | ///
16 | /// Function used to choose the key for grouping
17 | /// THe enumerable being grouped
18 | /// An enumerable of
19 | /// Type of key value used for grouping
20 | /// Type of elements that are grouped
21 | public static IEnumerable> GroupAdjacent(this IEnumerable source, Func keySelector)
22 | {
23 | var prevKey = default(TKey);
24 | var prevStartIndex = 0;
25 | var prevInit = false;
26 | var itemsList = new List();
27 |
28 | var i = 0;
29 | foreach (var item in source)
30 | {
31 | var key = keySelector(item);
32 | if (prevInit)
33 | {
34 | if (!prevKey.Equals(key))
35 | {
36 | yield return new AdjacentGrouping(key, itemsList, prevStartIndex, i - 1);
37 |
38 | prevKey = key;
39 | itemsList = new List();
40 | itemsList.Add(item);
41 | prevStartIndex = i;
42 | }
43 | else
44 | {
45 | itemsList.Add(item);
46 | }
47 | }
48 | else
49 | {
50 | prevKey = key;
51 | itemsList.Add(item);
52 | prevInit = true;
53 | }
54 |
55 | i++;
56 | }
57 |
58 | if (prevInit) yield return new AdjacentGrouping(prevKey, itemsList, prevStartIndex, i - 1); ;
59 | }
60 |
61 | ///
62 | /// A single grouping from the GroupAdjacent function, includes start and end indexes for the grouping in addition to standard IGrouping bits
63 | ///
64 | /// Type of grouped elements
65 | /// Type of key used for grouping
66 | public class AdjacentGrouping : IGrouping, IEnumerable
67 | {
68 | ///
69 | /// The key value for this grouping
70 | ///
71 | public TKey Key
72 | {
73 | get;
74 | private set;
75 | }
76 |
77 | ///
78 | /// The start index in the source enumerable for this group (i.e. index of first element)
79 | ///
80 | public int StartIndex
81 | {
82 | get;
83 | private set;
84 | }
85 |
86 | ///
87 | /// The end index in the enumerable for this group (i.e. the index of the last element)
88 | ///
89 | public int EndIndex
90 | {
91 | get;
92 | private set;
93 | }
94 |
95 | private IEnumerable m_groupItems;
96 |
97 | internal AdjacentGrouping(TKey key, IEnumerable groupItems, int startIndex, int endIndex)
98 | {
99 | this.Key = key;
100 | this.StartIndex = startIndex;
101 | this.EndIndex = endIndex;
102 | m_groupItems = groupItems;
103 | }
104 |
105 | private AdjacentGrouping() { }
106 |
107 | IEnumerator IEnumerable.GetEnumerator()
108 | {
109 | return m_groupItems.GetEnumerator();
110 | }
111 |
112 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
113 | {
114 | return m_groupItems.GetEnumerator();
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Matcher/DateMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace Zxcvbn.Matcher
8 | {
9 | ///
10 | /// This matcher attempts to guess dates, with and without date separators. e.g. 1197 (could be 1/1/97) through to 18/12/2015.
11 | ///
12 | /// The format for matching dates is quite particular, and only detected years in the range 00-99 and 1900-2019 are considered by
13 | /// this matcher.
14 | ///
15 | public class DateMatcher : IMatcher
16 | {
17 | // TODO: This whole matcher is a rather messy but works (just), could do with a touching up. In particular it does not provide matched date details for dates without separators
18 |
19 |
20 | const string DatePattern = "date";
21 |
22 |
23 | // The two regexes for matching dates with slashes is lifted directly from zxcvbn (matching.coffee about :400)
24 | const string DateWithSlashesSuffixPattern = @" ( \d{1,2} ) # day or month
25 | ( \s | - | / | \\ | _ | \. ) # separator
26 | ( \d{1,2} ) # month or day
27 | \2 # same separator
28 | ( 19\d{2} | 200\d | 201\d | \d{2} ) # year";
29 |
30 | const string DateWithSlashesPrefixPattern = @" ( 19\d{2} | 200\d | 201\d | \d{2} ) # year
31 | ( \s | - | / | \\ | _ | \. ) # separator
32 | ( \d{1,2} ) # day or month
33 | \2 # same separator
34 | ( \d{1,2} ) # month or day";
35 |
36 | ///
37 | /// Find date matches in
38 | ///
39 | /// The passsord to check
40 | /// An enumerable of date matches
41 | ///
42 | public IEnumerable MatchPassword(string password)
43 | {
44 | var matches = new List();
45 |
46 | var possibleDates = Regex.Matches(password, "\\d{4,8}"); // Slashless dates
47 | foreach (System.Text.RegularExpressions.Match dateMatch in possibleDates)
48 | {
49 | if (IsDate(dateMatch.Value)) matches.Add(new Match()
50 | {
51 | Pattern = DatePattern,
52 | i = dateMatch.Index,
53 | j = dateMatch.Index + dateMatch.Length - 1,
54 | Token = dateMatch.Value,
55 | Entropy = CalculateEntropy(dateMatch.Value, null, false)
56 | });
57 | }
58 |
59 | var slashDatesSuffix = Regex.Matches(password, DateWithSlashesSuffixPattern, RegexOptions.IgnorePatternWhitespace);
60 | foreach (System.Text.RegularExpressions.Match dateMatch in slashDatesSuffix)
61 | {
62 | var year = dateMatch.Groups[4].Value.ToInt();
63 | var month = dateMatch.Groups[3].Value.ToInt(); // or day
64 | var day = dateMatch.Groups[1].Value.ToInt(); // or month
65 |
66 | // Do a quick check for month/day swap (e.g. US dates)
67 | if (12 <= month && month <= 31 && day <= 12) { var t = month; month = day; day = t; }
68 |
69 | if (IsDateInRange(year, month, day)) matches.Add(new DateMatch()
70 | {
71 | Pattern = DatePattern,
72 | i = dateMatch.Index,
73 | j = dateMatch.Index + dateMatch.Length - 1,
74 | Token = dateMatch.Value,
75 | Entropy = CalculateEntropy(dateMatch.Value, year, true),
76 | Separator = dateMatch.Groups[2].Value,
77 | Year = year,
78 | Month = month,
79 | Day = day
80 | });
81 | }
82 |
83 | var slashDatesPrefix = Regex.Matches(password, DateWithSlashesPrefixPattern, RegexOptions.IgnorePatternWhitespace);
84 | foreach (System.Text.RegularExpressions.Match dateMatch in slashDatesPrefix)
85 | {
86 | var year = dateMatch.Groups[1].Value.ToInt();
87 | var month = dateMatch.Groups[3].Value.ToInt(); // or day
88 | var day = dateMatch.Groups[4].Value.ToInt(); // or month
89 |
90 | // Do a quick check for month/day swap (e.g. US dates)
91 | if (12 <= month && month <= 31 && day <= 12) { var t = month; month = day; day = t; }
92 |
93 | if (IsDateInRange(year, month, day)) matches.Add(new DateMatch()
94 | {
95 | Pattern = DatePattern,
96 | i = dateMatch.Index,
97 | j = dateMatch.Index + dateMatch.Length - 1,
98 | Token = dateMatch.Value,
99 | Entropy = CalculateEntropy(dateMatch.Value, year, true),
100 | Separator = dateMatch.Groups[2].Value,
101 | Year = year,
102 | Month = month,
103 | Day = day
104 | });
105 | }
106 |
107 | return matches;
108 | }
109 |
110 | private double CalculateEntropy(string match, int? year, bool separator)
111 | {
112 | // The entropy calculation is pretty straightforward
113 |
114 | // This is a slight departure from the zxcvbn case where the match has the actual year so the two-year vs four-year
115 | // can always be known rather than guessed for strings without separators.
116 | if (!year.HasValue)
117 | {
118 | // Guess year length from string length
119 | year = match.Length <= 6 ? 99 : 9999;
120 | }
121 |
122 | var entropy = 0.0;
123 | if (year < 100) entropy = Math.Log(31 * 12 * 100, 2); // 100 years (two-digits)
124 | else entropy = Math.Log(31 * 12 * 119, 2); // 119 years (four digit years valid range)
125 |
126 | if (separator) entropy += 2; // Extra two bits for separator (/\...)
127 |
128 | return entropy;
129 | }
130 |
131 | ///
132 | /// Determine whether a string resembles a date (year first or year last)
133 | ///
134 | private Boolean IsDate(string match)
135 | {
136 | bool isValid = false;
137 |
138 | // Try year length depending on match length. Length six should try both two and four digits
139 |
140 | if (match.Length <= 6)
141 | {
142 | // Try a two digit year, suffix and prefix
143 | isValid |= IsDateWithYearType(match, true, 2);
144 | isValid |= IsDateWithYearType(match, false, 2);
145 | }
146 | if (match.Length >= 6)
147 | {
148 | // Try a four digit year, suffix and prefix
149 | isValid |= IsDateWithYearType(match, true, 4);
150 | isValid |= IsDateWithYearType(match, false, 4);
151 | }
152 |
153 | return isValid;
154 | }
155 |
156 | private Boolean IsDateWithYearType(string match, bool suffix, int yearLen)
157 | {
158 | int year = 0;
159 | if (suffix) match.IntParseSubstring(match.Length - yearLen, yearLen, out year);
160 | else match.IntParseSubstring(0, yearLen, out year);
161 |
162 | if (suffix) return IsYearInRange(year) && IsDayMonthString(match.Substring(0, match.Length - yearLen));
163 | else return IsYearInRange(year) && IsDayMonthString(match.Substring(yearLen, match.Length - yearLen));
164 | }
165 |
166 | ///
167 | /// Determines whether a substring of a date string resembles a day and month (day-month or month-day)
168 | ///
169 | private Boolean IsDayMonthString(string match)
170 | {
171 | int p1 = 0, p2 = 0;
172 |
173 | // Parse the day/month string into two parts
174 | if (match.Length == 2)
175 | {
176 | // e.g. 1 2 [1234]
177 | match.IntParseSubstring(0, 1, out p1);
178 | match.IntParseSubstring(1, 1, out p2);
179 | }
180 | else if (match.Length == 3)
181 | {
182 | // e.g. 1 12 [1234] or 12 1 [1234]
183 |
184 | match.IntParseSubstring(0, 1, out p1);
185 | match.IntParseSubstring(1, 2, out p2);
186 |
187 | // This one is a little different in that there's two ways to parse it so go one way first
188 | if (IsMonthDayInRange(p1, p2) || IsMonthDayInRange(p2, p1)) return true;
189 |
190 | match.IntParseSubstring(0, 2, out p1);
191 | match.IntParseSubstring(2, 1, out p2);
192 | }
193 | else if (match.Length == 4)
194 | {
195 | // e.g. 14 11 [1234]
196 |
197 | match.IntParseSubstring(0, 2, out p1);
198 | match.IntParseSubstring(2, 2, out p2);
199 | }
200 |
201 | // Check them both ways around to see if a valid day/month pair
202 | return IsMonthDayInRange(p1, p2) || IsMonthDayInRange(p2, p1);
203 | }
204 |
205 | private Boolean IsDateInRange(int year, int month, int day)
206 | {
207 | return IsYearInRange(year) && IsMonthDayInRange(month, day);
208 | }
209 |
210 | // Two-digit years are allowed, otherwise in 1900-2019
211 | private Boolean IsYearInRange(int year)
212 | {
213 | return (1900 <= year && year <= 2019) || (0 < year && year <= 99);
214 | }
215 |
216 | // Assume all months have 31 days, we only care that things look like dates not that they're completely valid
217 | private Boolean IsMonthDayInRange(int month, int day)
218 | {
219 | return 1 <= month && month <= 12 && 1 <= day && day <= 31;
220 | }
221 | }
222 |
223 | ///
224 | /// A match found by the date matcher
225 | ///
226 | public class DateMatch : Match
227 | {
228 | ///
229 | /// The detected year
230 | ///
231 | public int Year { get; set; }
232 |
233 | ///
234 | /// The detected month
235 | ///
236 | public int Month { get; set; }
237 |
238 | ///
239 | /// The detected day
240 | ///
241 | public int Day { get; set; }
242 |
243 | ///
244 | /// Where a date with separators is matched, this will contain the separator that was used (e.g. '/', '-')
245 | ///
246 | public string Separator { get; set; }
247 | }
248 |
249 | }
250 |
--------------------------------------------------------------------------------
/Matcher/DictionaryMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.IO;
6 | using System.Text.RegularExpressions;
7 |
8 | namespace Zxcvbn.Matcher
9 | {
10 | ///
11 | /// This matcher reads in a list of words (in frequency order) and matches substrings of the password against that dictionary.
12 | ///
13 | /// The dictionary to be used can be specified directly by passing an enumerable of strings through the constructor (e.g. for
14 | /// matching agains user inputs). Most dictionaries will be in word list files.
15 | ///
16 | /// Using external files is a departure from the JS version of Zxcvbn which bakes in the word lists, so the default dictionaries
17 | /// have been included in the Zxcvbn assembly as embedded resources (to remove the external dependency). Thus when a word list is specified
18 | /// by name, it is first checked to see if it matches and embedded resource and if not is assumed to be an external file.
19 | ///
20 | /// Thus custom dictionaries can be included by providing the name of an external text file, but the built-in dictionaries (english.lst,
21 | /// female_names.lst, male_names.lst, passwords.lst, surnames.lst) can be used without concern about locating a dictionary file in an accessible
22 | /// place.
23 | ///
24 | /// Dictionary word lists must be in decreasing frequency order and contain one word per line with no additional information.
25 | ///
26 | public class DictionaryMatcher : IMatcher
27 | {
28 | const string DictionaryPattern = "dictionary";
29 |
30 | private string dictionaryName;
31 | private Lazy> rankedDictionary;
32 |
33 | ///
34 | /// Creates a new dictionary matcher. must be the path (relative or absolute) to a file containing one word per line,
35 | /// entirely in lowercase, ordered by frequency (decreasing); or must be the name of a built-in dictionary.
36 | ///
37 | /// The name provided to the dictionary used
38 | /// The filename of the dictionary (full or relative path) or name of built-in dictionary
39 | public DictionaryMatcher(string name, string wordListPath)
40 | {
41 | this.dictionaryName = name;
42 | rankedDictionary = new Lazy>(() => BuildRankedDictionary(wordListPath));
43 | }
44 |
45 | ///
46 | /// Creates a new dictionary matcher from the passed in word list. If there is any frequency order then they should be in
47 | /// decreasing frequency order.
48 | ///
49 | public DictionaryMatcher(string name, IEnumerable wordList)
50 | {
51 | this.dictionaryName = name;
52 |
53 | // Must ensure that the dictionary is using lowercase words only
54 | rankedDictionary = new Lazy>(() => BuildRankedDictionary(wordList.Select(w => w.ToLower())));
55 | }
56 |
57 | ///
58 | /// Match substrings of password agains the loaded dictionary
59 | ///
60 | /// The password to match
61 | /// An enumerable of dictionary matches
62 | ///
63 | public IEnumerable MatchPassword(string password)
64 | {
65 | var passwordLower = password.ToLower();
66 |
67 | var matches = (from i in Enumerable.Range(0, password.Length)
68 | from j in Enumerable.Range(i, password.Length - i)
69 | let psub = passwordLower.Substring(i, j - i + 1)
70 | where rankedDictionary.Value.ContainsKey(psub)
71 | select new DictionaryMatch()
72 | {
73 | Pattern = DictionaryPattern,
74 | i = i,
75 | j = j,
76 | Token = password.Substring(i, j - i + 1), // Could have different case so pull from password
77 | MatchedWord = psub,
78 | Rank = rankedDictionary.Value[psub],
79 | DictionaryName = dictionaryName,
80 | Cardinality = rankedDictionary.Value.Count
81 | }).ToList();
82 |
83 | foreach (var match in matches) CalculateEntropyForMatch(match);
84 |
85 | return matches;
86 | }
87 |
88 | private void CalculateEntropyForMatch(DictionaryMatch match)
89 | {
90 | match.BaseEntropy = Math.Log(match.Rank, 2);
91 | match.UppercaseEntropy = PasswordScoring.CalculateUppercaseEntropy(match.Token);
92 |
93 | match.Entropy = match.BaseEntropy + match.UppercaseEntropy;
94 | }
95 |
96 |
97 |
98 | private Dictionary BuildRankedDictionary(string wordListFile)
99 | {
100 | // Look first to wordlists embedded in assembly (i.e. default dictionaries) otherwise treat as file path
101 |
102 | var lines = Utility.GetEmbeddedResourceLines("Zxcvbn.Dictionaries.{0}".F(wordListFile)) ?? File.ReadAllLines(wordListFile);
103 |
104 | return BuildRankedDictionary(lines);
105 | }
106 |
107 | private Dictionary BuildRankedDictionary(IEnumerable wordList)
108 | {
109 | var dict = new Dictionary();
110 |
111 | var i = 1;
112 | foreach (var word in wordList)
113 | {
114 | // The word list is assumed to be in increasing frequency order
115 | dict[word] = i++;
116 | }
117 |
118 | return dict;
119 | }
120 | }
121 |
122 | ///
123 | /// Matches found by the dictionary matcher contain some additional information about the matched word.
124 | ///
125 | public class DictionaryMatch : Match
126 | {
127 | ///
128 | /// The dictionary word matched
129 | ///
130 | public string MatchedWord { get; set; }
131 |
132 | ///
133 | /// The rank of the matched word in the dictionary (i.e. 1 is most frequent, and larger numbers are less common words)
134 | ///
135 | public int Rank { get; set; }
136 |
137 | ///
138 | /// The name of the dictionary the matched word was found in
139 | ///
140 | public string DictionaryName { get; set; }
141 |
142 |
143 | ///
144 | /// The base entropy of the match, calculated from frequency rank
145 | ///
146 | public double BaseEntropy { get; set; }
147 |
148 | ///
149 | /// Additional entropy for this match from the use of mixed case
150 | ///
151 | public double UppercaseEntropy { get; set; }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Matcher/IMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// All pattern matchers must implement the IMatcher interface.
10 | ///
11 | public interface IMatcher
12 | {
13 | ///
14 | /// This function is called once for each matcher for each password being evaluated. It should perform the matching process and return
15 | /// an enumerable of Match objects for each match found.
16 | ///
17 | ///
18 | ///
19 | IEnumerable MatchPassword(string password);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Matcher/L33tMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// This matcher applies some known l33t character substitutions and then attempts to match against passed in dictionary matchers.
10 | /// This detects passwords like 4pple which has a '4' substituted for an 'a'
11 | ///
12 | public class L33tMatcher : IMatcher
13 | {
14 | private List dictionaryMatchers;
15 | private Dictionary substitutions;
16 |
17 | ///
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(List dictionaryMatchers)
22 | {
23 | this.dictionaryMatchers = dictionaryMatchers;
24 | substitutions = BuildSubstitutionsMap();
25 | }
26 |
27 | ///
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) : this(new List { dictionaryMatcher })
32 | {
33 | }
34 |
35 | ///
36 | /// Apply applicable l33t transformations and check against the dictionaries.
37 | ///
38 | /// The password to check
39 | /// A list of match objects where l33t substitutions match dictionary words
40 | ///
41 | public IEnumerable MatchPassword(string password)
42 | {
43 | var subs = EnumerateSubtitutions(GetRelevantSubstitutions(password));
44 |
45 | var matches = (from subDict in subs
46 | let sub_password = TranslateString(subDict, password)
47 | from matcher in dictionaryMatchers
48 | from match in matcher.MatchPassword(sub_password).OfType()
49 | let token = password.Substring(match.i, match.j - match.i + 1)
50 | let usedSubs = subDict.Where(kv => token.Contains(kv.Key)) // Count subs ised in matched token
51 | where usedSubs.Count() > 0 // Only want matches where substitutions were used
52 | select new L33tDictionaryMatch(match)
53 | {
54 | Token = token,
55 | Subs = usedSubs.ToDictionary(kv => kv.Key, kv => kv.Value)
56 | }).ToList();
57 |
58 | foreach (var match in matches) CalulateL33tEntropy(match);
59 |
60 | return matches;
61 | }
62 |
63 | private void CalulateL33tEntropy(L33tDictionaryMatch match)
64 | {
65 | // I'm a bit dubious about this function, but I have duplicated zxcvbn functionality regardless
66 |
67 | var possibilities = 0;
68 |
69 | foreach (var kvp in match.Subs)
70 | {
71 | var subbedChars = match.Token.Where(c => c == kvp.Key).Count();
72 | var unsubbedChars = match.Token.Where(c => c == kvp.Value).Count(); // Won't this always be zero?
73 |
74 | possibilities += Enumerable.Range(0, Math.Min(subbedChars, unsubbedChars) + 1).Sum(i => (int)PasswordScoring.Binomial(subbedChars + unsubbedChars, i));
75 | }
76 |
77 | var entropy = Math.Log(possibilities, 2);
78 |
79 | // In the case of only a single subsitution (e.g. 4pple) this would otherwise come out as zero, so give it one bit
80 | match.L33tEntropy = (entropy < 1 ? 1 : entropy);
81 | match.Entropy += match.L33tEntropy;
82 |
83 | // We have to recalculate the uppercase entropy -- the password matcher will have used the subbed password not the original text
84 | match.Entropy -= match.UppercaseEntropy;
85 | match.UppercaseEntropy = PasswordScoring.CalculateUppercaseEntropy(match.Token);
86 | match.Entropy += match.UppercaseEntropy;
87 | }
88 |
89 | private string TranslateString(Dictionary charMap, string str)
90 | {
91 | // Make substitutions from the character map wherever possible
92 | return new String(str.Select(c => charMap.ContainsKey(c) ? charMap[c] : c).ToArray());
93 | }
94 |
95 | private Dictionary GetRelevantSubstitutions(string password)
96 | {
97 | // Return a map of only the useful substitutions, i.e. only characters that the password
98 | // contains a substituted form of
99 | return substitutions.Where(kv => kv.Value.Any(lc => password.Contains(lc)))
100 | .ToDictionary(kv => kv.Key, kv => new String(kv.Value.Where(lc => password.Contains(lc)).ToArray()));
101 | }
102 |
103 | private List> EnumerateSubtitutions(Dictionary table)
104 | {
105 | // Produce a list of maps from l33t character to normal character. Some substitutions can be more than one normal character though,
106 | // so we have to produce an entry that maps from the l33t char to both possibilities
107 |
108 | //XXX: This function produces different combinations to the original in zxcvbn. It may require some more work to get identical.
109 |
110 | //XXX: The function is also limited in that it only ever considers one substitution for each l33t character (e.g. ||ke could feasibly
111 | // match 'like' but this method would never show this). My understanding is that this is also a limitation in zxcvbn and so I
112 | // feel no need to correct it here.
113 |
114 | var subs = new List>();
115 | subs.Add(new Dictionary()); // Must be at least one mapping dictionary to work
116 |
117 | foreach (var mapPair in table)
118 | {
119 | var normalChar = mapPair.Key;
120 |
121 | foreach (var l33tChar in mapPair.Value)
122 | {
123 | // Can't add while enumerating so store here
124 | var addedSubs = new List>();
125 |
126 | foreach (var subDict in subs)
127 | {
128 | if (subDict.ContainsKey(l33tChar))
129 | {
130 | // This mapping already contains a corresponding normal character for this character, so keep the existing one as is
131 | // but add a duplicate with the mappring replaced with this normal character
132 | var newSub = new Dictionary(subDict);
133 | newSub[l33tChar] = normalChar;
134 | addedSubs.Add(newSub);
135 | }
136 | else
137 | {
138 | subDict[l33tChar] = normalChar;
139 | }
140 | }
141 |
142 | subs.AddRange(addedSubs);
143 | }
144 | }
145 |
146 | return subs;
147 | }
148 |
149 | private Dictionary BuildSubstitutionsMap()
150 | {
151 | // Is there an easier way of building this table?
152 | var subs = new Dictionary();
153 |
154 | subs['a'] = "4@";
155 | subs['b'] = "8";
156 | subs['c'] = "({[<";
157 | subs['e'] = "3";
158 | subs['g'] = "69";
159 | subs['i'] = "1!|";
160 | subs['l'] = "1|7";
161 | subs['o'] = "0";
162 | subs['s'] = "$5";
163 | subs['t'] = "+7";
164 | subs['x'] = "%";
165 | subs['z'] = "2";
166 |
167 | return subs;
168 | }
169 | }
170 |
171 | ///
172 | /// L33tMatcher results are like dictionary match results with some extra information that pertains to the extra entropy that
173 | /// is garnered by using substitutions.
174 | ///
175 | public class L33tDictionaryMatch : DictionaryMatch
176 | {
177 | ///
178 | /// The extra entropy from using l33t substitutions
179 | ///
180 | public double L33tEntropy { get; set; }
181 |
182 | ///
183 | /// The character mappings that are in use for this match
184 | ///
185 | public Dictionary Subs { get; set; }
186 |
187 | ///
188 | /// Create a new l33t match from a dictionary match
189 | ///
190 | /// The dictionary match to initialise the l33t match from
191 | public L33tDictionaryMatch(DictionaryMatch dm)
192 | {
193 | this.BaseEntropy = dm.BaseEntropy;
194 | this.Cardinality = dm.Cardinality;
195 | this.DictionaryName = dm.DictionaryName;
196 | this.Entropy = dm.Entropy;
197 | this.i = dm.i;
198 | this.j = dm.j;
199 | this.MatchedWord = dm.MatchedWord;
200 | this.Pattern = dm.Pattern;
201 | this.Rank = dm.Rank;
202 | this.Token = dm.Token;
203 | this.UppercaseEntropy = dm.UppercaseEntropy;
204 |
205 | Subs = new Dictionary();
206 | }
207 |
208 | ///
209 | /// Create an empty l33t match
210 | ///
211 | public L33tDictionaryMatch()
212 | {
213 | Subs = new Dictionary();
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/Matcher/RegexMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace Zxcvbn.Matcher
8 | {
9 | ///
10 | /// Use a regular expression to match agains the password. (e.g. 'year' and 'digits' pattern matchers are implemented with this matcher.
11 | /// A note about cardinality: the cardinality parameter is used to calculate the entropy of matches found with the regex matcher. Since
12 | /// this cannot be calculated automatically from the regex pattern it must be provided. It can be provided per-character or per-match. Per-match will
13 | /// result in every match having the same entropy (lg cardinality) whereas per-character will depend on the match length (lg cardinality ^ length)
14 | ///
15 | public class RegexMatcher : IMatcher
16 | {
17 | Regex matchRegex;
18 | string matcherName;
19 | int cardinality;
20 | bool perCharCardinality;
21 |
22 | ///
23 | /// Create a new regex pattern matcher
24 | ///
25 | /// The regex pattern to match
26 | /// The cardinality of this match. Since this cannot be calculated from a pattern it must be provided. Can
27 | /// be give per-matched-character or per-match
28 | /// True if cardinality is given as per-matched-character
29 | /// The name to give this matcher ('pattern' in resulting matches)
30 | public RegexMatcher(string pattern, int cardinality, bool perCharCardinality = true, string matcherName = "regex")
31 | : this(new Regex(pattern), cardinality, perCharCardinality, matcherName)
32 | {
33 | }
34 |
35 | ///
36 | /// Create a new regex pattern matcher
37 | ///
38 | /// The regex object used to perform matching
39 | /// The cardinality of this match. Since this cannot be calculated from a pattern it must be provided. Can
40 | /// be give per-matched-character or per-match
41 | /// True if cardinality is given as per-matched-character
42 | /// The name to give this matcher ('pattern' in resulting matches)
43 | public RegexMatcher(Regex matchRegex, int cardinality, bool perCharCardinality, string matcherName = "regex")
44 | {
45 | this.matchRegex = matchRegex;
46 | this.matcherName = matcherName;
47 | this.cardinality = cardinality;
48 | this.perCharCardinality = perCharCardinality;
49 | }
50 |
51 | ///
52 | /// Find all matches of the regex in
53 | ///
54 | /// The password to check
55 | /// An enumerable of matches for each regex match in
56 | public IEnumerable MatchPassword(string password)
57 | {
58 | var reMatches = matchRegex.Matches(password);
59 |
60 | var pwMatches = new List();
61 |
62 | foreach (System.Text.RegularExpressions.Match rem in reMatches)
63 | {
64 | pwMatches.Add(new Match()
65 | {
66 | Pattern = matcherName,
67 | i = rem.Index,
68 | j = rem.Index + rem.Length - 1,
69 | Token = password.Substring(rem.Index, rem.Length),
70 | Cardinality = cardinality,
71 | Entropy = Math.Log((perCharCardinality ? Math.Pow(cardinality, rem.Length) : cardinality), 2) // Raise cardinality to length when giver per character
72 | });
73 | }
74 |
75 | return pwMatches;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Matcher/RepeatMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// Match repeated characters in the password (repeats must be more than two characters long to count)
10 | ///
11 | public class RepeatMatcher : IMatcher
12 | {
13 | const string RepeatPattern = "repeat";
14 |
15 | ///
16 | /// Find repeat matches in
17 | ///
18 | /// The password to check
19 | /// List of repeat matches
20 | ///
21 | public IEnumerable MatchPassword(string password)
22 | {
23 | var matches = new List();
24 |
25 | // Be sure to not count groups of one or two characters
26 | return password.GroupAdjacent(c => c).Where(g => g.Count() > 2).Select(g => new RepeatMatch {
27 | Pattern = RepeatPattern,
28 | Token = password.Substring(g.StartIndex, g.EndIndex - g.StartIndex + 1),
29 | i = g.StartIndex,
30 | j = g.EndIndex,
31 | Entropy = CalculateEntropy(password.Substring(g.StartIndex, g.EndIndex - g.StartIndex + 1)),
32 | RepeatChar = g.Key
33 | });
34 | }
35 |
36 | private double CalculateEntropy(string match)
37 | {
38 | return Math.Log(PasswordScoring.PasswordCardinality(match) * match.Length, 2);
39 | }
40 | }
41 |
42 | ///
43 | /// A match found with the RepeatMatcher
44 | ///
45 | public class RepeatMatch : Match
46 | {
47 | ///
48 | /// The character that was repeated
49 | ///
50 | public char RepeatChar { get; set; }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Matcher/SequenceMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// This matcher detects lexicographical sequences (and in reverse) e.g. abcd, 4567, PONML etc.
10 | ///
11 | public class SequenceMatcher : IMatcher
12 | {
13 | // Sequences should not overlap, sequences here must be ascending, their reverses will be checked automatically
14 | string[] Sequences = new string[] {
15 | "abcdefghijklmnopqrstuvwxyz",
16 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
17 | "01234567890"
18 | };
19 |
20 | string[] SequenceNames = new string[] {
21 | "lower",
22 | "upper",
23 | "digits"
24 | };
25 |
26 | const string SequencePattern = "sequence";
27 |
28 | ///
29 | /// Find matching sequences in
30 | ///
31 | /// The password to check
32 | /// Enumerable of sqeunec matches
33 | ///
34 | public IEnumerable MatchPassword(string password)
35 | {
36 | // Sequences to check should be the set of sequences and their reverses (i.e. want to match "abcd" and "dcba")
37 | var seqs = Sequences.Union(Sequences.Select(s => s.StringReverse())).ToList();
38 |
39 | var matches = new List();
40 |
41 | var i = 0;
42 | while (i < password.Length - 1)
43 | {
44 | int j = i + 1;
45 |
46 | // Find a sequence that the current and next characters could be part of
47 | var seq = (from s in seqs
48 | let ixI = s.IndexOf(password[i])
49 | let ixJ = s.IndexOf(password[j])
50 | where ixJ == ixI + 1 // i.e. two consecutive letters in password are consecutive in sequence
51 | select s).FirstOrDefault();
52 |
53 | // This isn't an ideal check, but we want to know whether the sequence is ascending/descending to keep entropy
54 | // calculation consistent with zxcvbn
55 | var ascending = Sequences.Contains(seq);
56 |
57 | // seq will be null when there are no matching sequences
58 | if (seq != null)
59 | {
60 | var startIndex = seq.IndexOf(password[i]);
61 |
62 | // Find length of matching sequence (j should be the character after the end of the matching subsequence)
63 | for (; j < password.Length && startIndex + j - i < seq.Length && seq[startIndex + j - i] == password[j]; j++) ;
64 |
65 | var length = j - i;
66 |
67 | // Only want to consider sequences that are longer than two characters
68 | if (length > 2)
69 | {
70 | // Find the sequence index so we can match it up with its name
71 | var seqIndex = seqs.IndexOf(seq);
72 | if (seqIndex >= Sequences.Length) seqIndex -= Sequences.Length; // match reversed sequence with its original
73 |
74 | var match = password.Substring(i, j - i);
75 | matches.Add(new SequenceMatch() {
76 | i = i,
77 | j = j - 1,
78 | Token = match,
79 | Pattern = SequencePattern,
80 | Entropy = CalculateEntropy(match, ascending),
81 | Ascending = ascending,
82 | SequenceName = SequenceNames[seqIndex],
83 | SequenceSize = Sequences[seqIndex].Length
84 | });
85 | }
86 | }
87 |
88 | i = j;
89 | }
90 |
91 | return matches;
92 | }
93 |
94 | private double CalculateEntropy(string match, bool ascending)
95 | {
96 | var firstChar = match[0];
97 |
98 | // XXX: This entropy calculation is hard coded, ideally this would (somehow) be derived from the sequences above
99 | double baseEntropy;
100 | if (firstChar == 'a' || firstChar == '1') baseEntropy = 1;
101 | else if ('0' <= firstChar && firstChar <= '9') baseEntropy = Math.Log(10, 2); // Numbers
102 | else if ('a' <= firstChar && firstChar <= 'z') baseEntropy = Math.Log(26, 2); // Lowercase
103 | else baseEntropy = Math.Log(26, 1) + 1; // + 1 for uppercase
104 |
105 | if (!ascending) baseEntropy += 1; // Descending instead of ascending give + 1 bit of entropy
106 |
107 | return baseEntropy + Math.Log(match.Length, 2);
108 | }
109 | }
110 |
111 | ///
112 | /// A match made using the containing some additional sequence information.
113 | ///
114 | public class SequenceMatch : Match
115 | {
116 | ///
117 | /// The name of the sequence that the match was found in (e.g. 'lower', 'upper', 'digits')
118 | ///
119 | public string SequenceName { get; set; }
120 |
121 | ///
122 | /// The size of the sequence the match was found in (e.g. 26 for lowercase letters)
123 | ///
124 | public int SequenceSize { get; set; }
125 |
126 | ///
127 | /// Whether the match was found in ascending order (cdefg) or not (zyxw)
128 | ///
129 | public bool Ascending { get; set; }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Matcher/SpatialMatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Zxcvbn.Matcher
7 | {
8 | ///
9 | /// A matcher that checks for keyboard layout patterns (e.g. 78523 on a keypad, or plkmn on a QWERTY keyboard).
10 | /// Has patterns for QWERTY, DVORAK, numeric keybad and mac numeric keypad
11 | /// The matcher accounts for shifted characters (e.g. qwErt or po9*7y) when detecting patterns as well as multiple changes in direction.
12 | ///
13 | public class SpatialMatcher : IMatcher
14 | {
15 | const string SpatialPattern = "spatial";
16 |
17 | Lazy> spatialGraphs = new Lazy>(() => GenerateSpatialGraphs());
18 |
19 | ///
20 | /// Match the password against the known keyboard layouts
21 | ///
22 | /// Password to match
23 | /// List of matching patterns
24 | ///
25 | public IEnumerable MatchPassword(string password)
26 | {
27 | return spatialGraphs.Value.SelectMany((g) => SpatialMatch(g, password)).ToList();
28 | }
29 |
30 | ///
31 | /// Match the password against a single pattern
32 | ///
33 | /// Adjacency graph for this key layout
34 | /// Password to match
35 | /// List of matching patterns
36 | private List SpatialMatch(SpatialGraph graph, string password)
37 | {
38 | var matches = new List();
39 |
40 | var i = 0;
41 | while (i < password.Length - 1)
42 | {
43 | int turns = 0, shiftedCount = 0;
44 | var lastDirection = -1;
45 |
46 | var j = i + 1;
47 | for (; j < password.Length; ++j)
48 | {
49 | bool shifted;
50 | var foundDirection = graph.GetAdjacentCharDirection(password[j - 1], password[j], out shifted);
51 |
52 | if (foundDirection != -1)
53 | {
54 | // Spatial match continues
55 | if (shifted) shiftedCount++;
56 | if (lastDirection != foundDirection)
57 | {
58 | turns++;
59 | lastDirection = foundDirection;
60 | }
61 | }
62 | else break; // This character not a spatial match
63 |
64 | }
65 |
66 | // Only consider runs of greater than two
67 | if (j - i > 2)
68 | {
69 | matches.Add(new SpatialMatch()
70 | {
71 | Pattern = SpatialPattern,
72 | i = i,
73 | j = j - 1,
74 | Token = password.Substring(i, j - i),
75 | Graph = graph.Name,
76 | Entropy = graph.CalculateEntropy(j - i, turns, shiftedCount),
77 | Turns = turns,
78 | ShiftedCount = shiftedCount
79 | });
80 | }
81 |
82 | i = j;
83 | }
84 |
85 | return matches;
86 | }
87 |
88 |
89 | // In the JS version these are precomputed, but for now we'll generate them here when they are first needed.
90 | private static List GenerateSpatialGraphs()
91 | {
92 | // Kwyboard layouts directly from zxcvbn's build_keyboard_adjacency_graph.py script
93 | const string qwerty = @"
94 | `~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
95 | qQ wW eE rR tT yY uU iI oO pP [{ ]} \|
96 | aA sS dD fF gG hH jJ kK lL ;: '""
97 | zZ xX cC vV bB nN mM ,< .> /?
98 | ";
99 |
100 | const string dvorak = @"
101 | `~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) [{ ]}
102 | '"" ,< .> pP yY fF gG cC rR lL /? =+ \|
103 | aA oO eE uU iI dD hH tT nN sS -_
104 | ;: qQ jJ kK xX bB mM wW vV zZ
105 | ";
106 |
107 | const string keypad = @"
108 | / * -
109 | 7 8 9 +
110 | 4 5 6
111 | 1 2 3
112 | 0 .
113 | ";
114 |
115 | const string mac_keypad = @"
116 | = / *
117 | 7 8 9 -
118 | 4 5 6 +
119 | 1 2 3
120 | 0 .
121 | ";
122 |
123 |
124 | return new List { new SpatialGraph("qwerty", qwerty, true),
125 | new SpatialGraph("dvorak", dvorak, true),
126 | new SpatialGraph("keypad", keypad, false),
127 | new SpatialGraph("mac_keypad", mac_keypad, false)
128 | };
129 | }
130 |
131 | // See build_keyboard_adjacency_graph.py in zxcvbn for how these are generated
132 | private class SpatialGraph
133 | {
134 | public string Name { get; private set; }
135 | private Dictionary> AdjacencyGraph { get; set; }
136 | public int StartingPositions { get; private set; }
137 | public double AverageDegree { get; private set; }
138 |
139 | public SpatialGraph(string name, string layout, bool slanted)
140 | {
141 | this.Name = name;
142 | BuildGraph(layout, slanted);
143 | }
144 |
145 |
146 | ///
147 | /// Returns true when testAdjacent is in c's adjacency list
148 | ///
149 | public bool IsCharAdjacent(char c, char testAdjacent)
150 | {
151 | if (AdjacencyGraph.ContainsKey(c)) return AdjacencyGraph[c].Any(s => s.Contains(testAdjacent));
152 | return false;
153 | }
154 |
155 | ///
156 | /// Returns the 'direction' of the adjacent character (i.e. index in the adjacency list).
157 | /// If the character is not adjacent, -1 is returned
158 | ///
159 | /// Uses the 'shifted' out parameter to let the caller know if the matched character is shifted
160 | ///
161 | public int GetAdjacentCharDirection(char c, char adjacent, out bool shifted)
162 | {
163 | //XXX: This function is a bit strange, with an out parameter this should be refactored into something sensible
164 |
165 | shifted = false;
166 |
167 | if (!AdjacencyGraph.ContainsKey(c)) return -1;
168 |
169 | var adjacentEntry = AdjacencyGraph[c].FirstOrDefault(s => s != null && s.Contains(adjacent));
170 | if (adjacentEntry == null) return -1;
171 |
172 | shifted = adjacentEntry.IndexOf(adjacent) > 0; // i.e. shifted if not first character in the adjacency
173 | return AdjacencyGraph[c].IndexOf(adjacentEntry);
174 | }
175 |
176 | private Point[] GetSlantedAdjacent(Point c)
177 | {
178 | int x = c.x;
179 | int y = c.y;
180 |
181 | return new Point[] { 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) };
182 | }
183 |
184 | private Point[] GetAlignedAdjacent(Point c)
185 | {
186 | int x = c.x;
187 | int y = c.y;
188 |
189 | return new Point[] { 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) };
190 | }
191 |
192 | private void BuildGraph(string layout, bool slanted)
193 | {
194 |
195 | var tokens = layout.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
196 | var tokenSize = tokens[0].Length;
197 |
198 | // Put the characters in each keyboard cell into the map agains t their coordinates
199 | var positionTable = new Dictionary();
200 | var lines = layout.Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
201 | for (int y = 0; y < lines.Length; ++y)
202 | {
203 | var line = lines[y];
204 | var slant = slanted ? y - 1 : 0;
205 |
206 | foreach (var token in line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries))
207 | {
208 | var x = (line.IndexOf(token) - slant) / (tokenSize + 1);
209 | var p = new Point(x, y);
210 | positionTable[p] = token;
211 | }
212 | }
213 |
214 | AdjacencyGraph = new Dictionary>();
215 | foreach (var pair in positionTable)
216 | {
217 | var p = pair.Key;
218 | foreach (var c in pair.Value)
219 | {
220 | AdjacencyGraph[c] = new List();
221 | var adjacentPoints = slanted ? GetSlantedAdjacent(p) : GetAlignedAdjacent(p);
222 |
223 | foreach (var adjacent in adjacentPoints)
224 | {
225 | // We want to include nulls so that direction is correspondent with index in the list
226 | if (positionTable.ContainsKey(adjacent)) AdjacencyGraph[c].Add(positionTable[adjacent]);
227 | else AdjacencyGraph[c].Add(null);
228 | }
229 | }
230 | }
231 |
232 |
233 |
234 | // Calculate average degree and starting positions, cf. init.coffee
235 | StartingPositions = AdjacencyGraph.Count;
236 | AverageDegree = AdjacencyGraph.Sum(adj => adj.Value.Where(a => a != null).Count()) * 1.0 / StartingPositions;
237 | }
238 |
239 | ///
240 | /// Calculate entropy for a math that was found on this adjacency graph
241 | ///
242 | public double CalculateEntropy(int matchLength, int turns, int shiftedCount)
243 | {
244 | // This is an estimation of the number of patterns with length of matchLength or less with turns turns or less
245 | var possibilities = Enumerable.Range(2, matchLength - 1).Sum(i =>
246 | {
247 | var possible_turns = Math.Min(turns, i - 1);
248 | return Enumerable.Range(1, possible_turns).Sum(j =>
249 | {
250 | return StartingPositions * Math.Pow(AverageDegree, j) * PasswordScoring.Binomial(i - 1, j - 1);
251 | });
252 | });
253 |
254 | var entropy = Math.Log(possibilities, 2);
255 |
256 | // Entropy increaeses for a mix of shifted and unshifted
257 | if (shiftedCount > 0)
258 | {
259 | var unshifted = matchLength - shiftedCount;
260 | entropy += Math.Log(Enumerable.Range(0, Math.Min(shiftedCount, unshifted) + 1).Sum(i => PasswordScoring.Binomial(matchLength, i)), 2);
261 | }
262 |
263 | return entropy;
264 | }
265 | }
266 |
267 | // Instances of Point or Pair in the standard library are in UI assemblies, so define our own version to reduce dependencies
268 | private struct Point
269 | {
270 | public int x;
271 | public int y;
272 |
273 | public Point(int x, int y)
274 | {
275 | this.x = x;
276 | this.y = y;
277 | }
278 |
279 | public override string ToString()
280 | {
281 | return "{" + x + ", " + y + "}";
282 | }
283 | }
284 | }
285 |
286 | ///
287 | /// A match made with the . Contains additional information specific to spatial matches.
288 | ///
289 | public class SpatialMatch : Match
290 | {
291 | ///
292 | /// The name of the keyboard layout used to make the spatial match
293 | ///
294 | public string Graph { get; set; }
295 |
296 | ///
297 | /// The number of turns made (i.e. when diretion of adjacent keys changes)
298 | ///
299 | public int Turns { get; set; }
300 |
301 | ///
302 | /// The number of shifted characters matched in the pattern (adds to entropy)
303 | ///
304 | public int ShiftedCount { get; set; }
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/PasswordScoring.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// Some useful shared functions used for evaluating passwords
11 | ///
12 | static class PasswordScoring
13 | {
14 | ///
15 | /// Calculate the cardinality of the minimal character sets necessary to brute force the password (roughly)
16 | /// (e.g. lowercase = 26, numbers = 10, lowercase + numbers = 36)
17 | ///
18 | /// THe password to evaluate
19 | /// An estimation of the cardinality of charactes for this password
20 | public static int PasswordCardinality(string password)
21 | {
22 | var cl = 0;
23 |
24 | if (password.Any(c => 'a' <= c && c <= 'z')) cl += 26; // Lowercase
25 | if (password.Any(c => 'A' <= c && c <= 'Z')) cl += 26; // Uppercase
26 | if (password.Any(c => '0' <= c && c <= '9')) cl += 10; // Numbers
27 | if (password.Any(c => c <= '/' || (':' <= c && c <= '@') || ('[' <= c && c <= '`') || ('{' <= c && c <= 0x7F))) cl += 33; // Symbols
28 | if (password.Any(c => c > 0x7F)) cl += 100; // 'Unicode' (why 100?)
29 |
30 | return cl;
31 | }
32 |
33 | ///
34 | /// Calculate a rough estimate of crack time for entropy, see zxcbn scoring.coffee for more information on the model used
35 | ///
36 | /// Entropy of password
37 | /// An estimation of seconts taken to crack password
38 | public static double EntropyToCrackTime(double entropy)
39 | {
40 | const double SingleGuess = 0.01;
41 | const double NumAttackers = 100;
42 | const double SecondsPerGuess = SingleGuess / NumAttackers;
43 |
44 | return 0.5 * Math.Pow(2, entropy) * SecondsPerGuess;
45 | }
46 |
47 | ///
48 | /// Return a score for password strength from the crack time. Scores are 0..4, 0 being minimum and 4 maximum strength.
49 | ///
50 | /// Number of seconds estimated for password cracking
51 | /// Password strength. 0 to 4, 0 is minimum
52 | public static int CrackTimeToScore(double crackTimeSeconds)
53 | {
54 | if (crackTimeSeconds < Math.Pow(10, 2)) return 0;
55 | else if (crackTimeSeconds < Math.Pow(10, 4)) return 1;
56 | else if (crackTimeSeconds < Math.Pow(10, 6)) return 2;
57 | else if (crackTimeSeconds < Math.Pow(10, 8)) return 3;
58 | else return 4;
59 | }
60 |
61 | ///
62 | /// Caclulate binomial coefficient (i.e. nCk)
63 | /// Uses same algorithm as zxcvbn (cf. scoring.coffee), from http://blog.plover.com/math/choose.html
64 | ///
65 | /// k
66 | /// n
67 | /// Binomial coefficient; nCk
68 | public static long Binomial(int n, int k)
69 | {
70 | if (k > n) return 0;
71 | if (k == 0) return 1;
72 |
73 | long r = 1;
74 | for (int d = 1; d <= k; ++d)
75 | {
76 | r *= n;
77 | r /= d;
78 | n -= 1;
79 | }
80 |
81 | return r;
82 | }
83 |
84 | ///
85 | /// Estimate the extra entropy in a token that comes from mixing upper and lowercase letters.
86 | /// This has been moved to a static function so that it can be used in multiple entropy calculations.
87 | ///
88 | /// The word to calculate uppercase entropy for
89 | /// An estimation of the entropy gained from casing in
90 | public static double CalculateUppercaseEntropy(string word)
91 | {
92 | const string StartUpper = "^[A-Z][^A-Z]+$";
93 | const string EndUpper = "^[^A-Z]+[A-Z]$";
94 | const string AllUpper = "^[^a-z]+$";
95 | const string AllLower = "^[^A-Z]+$";
96 |
97 | if (Regex.IsMatch(word, AllLower)) return 0;
98 |
99 | // If the word is all uppercase add's only one bit of entropy, add only one bit for initial/end single cap only
100 | if (new[] { StartUpper, EndUpper, AllUpper }.Any(re => Regex.IsMatch(word, re))) return 1;
101 |
102 | var lowers = word.Where(c => 'a' <= c && c <= 'z').Count();
103 | var uppers = word.Where(c => 'A' <= c && c <= 'Z').Count();
104 |
105 | // Calculate numer of ways to capitalise (or inverse if there are fewer lowercase chars) and return lg for entropy
106 | return Math.Log(Enumerable.Range(0, Math.Min(uppers, lowers) + 1).Sum(i => PasswordScoring.Binomial(uppers + lowers, i)), 2);
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Zxcvbn")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Zxcvbn")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("76c2f1da-d88a-4570-8373-b260a3ff53be")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
38 |
39 | [assembly: InternalsVisibleTo("zxcvbn-test")]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Zxcvbn C#/.NET
2 | ==============
3 |
4 | This is a port of the `Zxcvbn` JavaScript password strength estimation library at
5 | https://github.com/lowe/zxcvbn to .NET, written in C#.
6 |
7 | From the `Zxcvbn` readme:
8 |
9 | > `zxcvbn`, named after a crappy password, is a JavaScript password strength
10 | > estimation library. Use it to implement a custom strength bar on a
11 | > signup form near you!
12 | >
13 | > `zxcvbn` attempts to give sound password advice through pattern matching
14 | > and conservative entropy calculations. It finds 10k common passwords,
15 | > common American names and surnames, common English words, and common
16 | > patterns like dates, repeats (aaa), sequences (abcd), and QWERTY
17 | > patterns.
18 | >
19 | > For full motivation, see:
20 | >
21 | > http://tech.dropbox.com/?p=165
22 |
23 | This port aims to produce comparable results with the JS version of `Zxcvbn`. The results
24 | structure that is returned can be interpreted in the same way as with JS `Zxcvbn` and this
25 | port has been tested with a variety of passwords to ensure that it return the same results
26 | as the JS version.
27 |
28 | There are some implementation differences, however, so exact results are not guaranteed.
29 |
30 |
31 | ### Using `Zxcvbn-cs`
32 |
33 | The included Visual Studio project will create a single assembly, Zxcvbn.dll, which is all that is
34 | required to be included in your project.
35 |
36 | To evaluate a single password:
37 |
38 | ``` C#
39 | using Zxcvbn;
40 |
41 | //...
42 |
43 | var result = Zxcvbn.MatchPassword("p@ssw0rd");
44 | ```
45 |
46 | To evaluate many passwords, create an instance of `Zxcvbn` and then use that to evaluate your passwords.
47 | This avoids reloading dictionaries etc. for every password:
48 |
49 | ``` C#
50 | using Zxcvbn;
51 |
52 | //...
53 |
54 | var zx = new Zxcvbn();
55 |
56 | foreach (var password in passwords)
57 | {
58 | var result = zx.EvaluatePassword(password);
59 |
60 | //...
61 | }
62 | ```
63 |
64 | Both `MatchPassword` and `EvaluatePassword` take an optional second parameter that contains an enumerable of
65 | user data strings to also match the password against.
66 |
67 | ### Interpreting Results
68 |
69 | The `Result` structure returned from password evaluation is interpreted the same way as with JS `Zxcvbn`:
70 |
71 | - `result.Entropy`: bits of entropy for the password
72 | - `result.CrackTime`: an estimation of actual crack time, in seconds.
73 | - `result.CrackTimeDisplay`: the crack time, as a friendlier string: "instant", "6 minutes", "centuries", etc.
74 | - `result.Score`: [0,1,2,3,4] if crack time is less than [10\*\*2, 10\*\*4, 10\*\*6, 10\*\*8, Infinity]. (useful for implementing a strength bar.)
75 | - `result.MatchSequence`: the list of pattern matches that was used to calculate Entropy.
76 | - `result.CalculationTime`: how long `Zxcvbn` took to calculate the results.
77 |
78 | ### More Information
79 |
80 | For more information on why password entropy is calculated as it is, refer to `Zxcvbn`s originators:
81 |
82 | https://github.com/lowe/zxcvbn
83 |
84 | http://tech.dropbox.com/?p=165
85 |
86 |
87 | ### Licence
88 |
89 | Since `Zxcvbn-cs` is a port of the original `Zxcvbn` the original copyright and licensing applies. Cf. the LICENSE file.
90 |
--------------------------------------------------------------------------------
/Result.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | // TODO: These should probably be immutable
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// The results of zxcvbn's password analysis
11 | ///
12 | public class Result
13 | {
14 | ///
15 | /// A calculated estimate of how many bits of entropy the password covers, rounded to three decimal places.
16 | ///
17 | public double Entropy { get; set; }
18 |
19 | ///
20 | /// The number of milliseconds that zxcvbn took to calculate results for this password
21 | ///
22 | public long CalcTime { get; set; }
23 |
24 | ///
25 | /// An estimation of the crack time for this password in seconds
26 | ///
27 | public double CrackTime { get; set; }
28 |
29 | ///
30 | /// A friendly string for the crack time (like "centuries", "instant", "7 minutes", "14 hours" etc.)
31 | ///
32 | public string CrackTimeDisplay { get; set; }
33 |
34 | ///
35 | /// A score from 0 to 4 (inclusive), with 0 being least secure and 4 being most secure calculated from crack time:
36 | /// [0,1,2,3,4] if crack time is less than [10**2, 10**4, 10**6, 10**8, Infinity] seconds.
37 | /// Useful for implementing a strength meter
38 | ///
39 | public int Score { get; set; }
40 |
41 | ///
42 | /// The sequence of matches that were used to create the entropy calculation
43 | ///
44 | public IList MatchSequence { get; set; }
45 |
46 | ///
47 | /// The password that was used to generate these results
48 | ///
49 | public string Password { get; set; }
50 | }
51 |
52 | ///
53 | /// A single match that one of the pattern matchers has made against the password being tested.
54 | ///
55 | /// Some pattern matchers implement subclasses of match that can provide more information on their specific results.
56 | ///
57 | /// Matches must all have the , , , and
58 | /// fields (i.e. all but the field, which is optional) set before being returned from the matcher
59 | /// in which they are created.
60 | ///
61 | public class Match
62 | {
63 | ///
64 | /// The name of the pattern matcher used to generate this match
65 | ///
66 | public string Pattern { get; set; }
67 |
68 | ///
69 | /// The portion of the password that was matched
70 | ///
71 | public string Token { get; set; }
72 |
73 | ///
74 | /// The entropy that this portion of the password covers using the current pattern matching technique
75 | ///
76 | public double Entropy { get; set; }
77 |
78 |
79 | // The following are more internal measures, but may be useful to consumers
80 |
81 | ///
82 | /// Some pattern matchers can associate the cardinality of the set of possible matches that the
83 | /// entropy calculation is derived from. Not all matchers provide a value for cardinality.
84 | ///
85 | public int Cardinality { get; set; }
86 |
87 | ///
88 | /// The start index in the password string of the matched token.
89 | ///
90 | public int i { get; set; } // Start Index
91 |
92 | ///
93 | /// The end index in the password string of the matched token.
94 | ///
95 | public int j { get; set; } // End Index
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/TODO-Global.txt:
--------------------------------------------------------------------------------
1 | - Documentaion comments
2 | - Readme
3 | - Especially about dictionary files
4 | - Licencing?
5 | - Check correspondence with JS version, esp:
6 | - Date Checker seems to reject two digit years? (cf. check_date) -- confirm
7 | - Date checker operates differently (i.e. it may return more matches in JS version) this should not affect end result -- confirm
8 | - l33t matching is different here, test that I actually understand zxcvbn's implementation and that results are comparable.
--------------------------------------------------------------------------------
/Translation.cs:
--------------------------------------------------------------------------------
1 | namespace Zxcvbn
2 | {
3 | ///
4 | /// The supported languages.
5 | ///
6 | public enum Translation
7 | {
8 | English,
9 | German,
10 | France
11 | }
12 | }
--------------------------------------------------------------------------------
/Utility.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Reflection;
7 | using System.IO;
8 |
9 | namespace Zxcvbn
10 | {
11 | ///
12 | /// A few useful extension methods used through the Zxcvbn project
13 | ///
14 | static class Utility
15 | {
16 | ///
17 | /// Convert a number of seconds into a human readable form. Rounds up.
18 | /// To be consistent with zxcvbn, it returns the unit + 1 (i.e. 60 * 10 seconds = 10 minutes would come out as "11 minutes"
19 | /// this is probably to avoid ever needing to deal with plurals
20 | ///
21 | /// The time in seconds
22 | /// The language in which the string is returned
23 | /// A human-friendly time string
24 | public static string DisplayTime(double seconds, Translation translation = Translation.English)
25 | {
26 | long minute = 60, hour = minute * 60, day = hour * 24, month = day * 31, year = month * 12, century = year * 100;
27 |
28 | if (seconds < minute) return GetTranslation("instant", translation);
29 | else if (seconds < hour) return string.Format("{0} " + GetTranslation("minutes", translation), (1 + Math.Ceiling(seconds / minute)));
30 | else if (seconds < day) return string.Format("{0} " + GetTranslation("hours", translation), (1 + Math.Ceiling(seconds / hour)));
31 | else if (seconds < month) return string.Format("{0} " + GetTranslation("days", translation), (1 + Math.Ceiling(seconds / day)));
32 | else if (seconds < year) return string.Format("{0} " + GetTranslation("months", translation), (1 + Math.Ceiling(seconds / month)));
33 | else if (seconds < century) return string.Format("{0} " + GetTranslation("years", translation), (1 + Math.Ceiling(seconds / year)));
34 | else return GetTranslation("centuries", translation);
35 | }
36 |
37 | private static string GetTranslation(string matcher, Translation translation)
38 | {
39 | string translated;
40 |
41 | switch (matcher)
42 | {
43 | case "instant":
44 | switch (translation)
45 | {
46 | case Translation.German:
47 | translated = "unmittelbar";
48 | break;
49 | case Translation.France:
50 | translated = "instantané";
51 | break;
52 | default:
53 | translated = "instant";
54 | break;
55 | }
56 | break;
57 | case "minutes":
58 | switch (translation)
59 | {
60 | case Translation.German:
61 | translated = "Minuten";
62 | break;
63 | case Translation.France:
64 | translated = "Minutes";
65 | break;
66 | default:
67 | translated = "minutes";
68 | break;
69 | }
70 | break;
71 | case "hours":
72 | switch (translation)
73 | {
74 | case Translation.German:
75 | translated = "Stunden";
76 | break;
77 | case Translation.France:
78 | translated = "Heures";
79 | break;
80 | default:
81 | translated = "hours";
82 | break;
83 | }
84 | break;
85 | case "days":
86 | switch (translation)
87 | {
88 | case Translation.German:
89 | translated = "Tage";
90 | break;
91 | case Translation.France:
92 | translated = "Journées";
93 | break;
94 | default:
95 | translated = "days";
96 | break;
97 | }
98 | break;
99 | case "months":
100 | switch (translation)
101 | {
102 | case Translation.German:
103 | translated = "Monate";
104 | break;
105 | case Translation.France:
106 | translated = "Mois";
107 | break;
108 | default:
109 | translated = "months";
110 | break;
111 | }
112 | break;
113 | case "years":
114 | switch (translation)
115 | {
116 | case Translation.German:
117 | translated = "Jahre";
118 | break;
119 | case Translation.France:
120 | translated = "Ans";
121 | break;
122 | default:
123 | translated = "years";
124 | break;
125 | }
126 | break;
127 | case "centuries":
128 | switch (translation)
129 | {
130 | case Translation.German:
131 | translated = "Jahrhunderte";
132 | break;
133 | case Translation.France:
134 | translated = "Siècles";
135 | break;
136 | default:
137 | translated = "centuries";
138 | break;
139 | }
140 | break;
141 | default:
142 | translated = matcher;
143 | break;
144 | }
145 |
146 | return translated;
147 | }
148 |
149 | ///
150 | /// Shortcut for string.Format
151 | ///
152 | /// Format args
153 | /// Format string
154 | /// Formatted string
155 | public static string F(this string str, params object[] args)
156 | {
157 | return string.Format(str, args);
158 | }
159 |
160 | ///
161 | /// Reverse a string in one call
162 | ///
163 | /// String to reverse
164 | /// String in reverse
165 | public static string StringReverse(this string str)
166 | {
167 | return new string(str.Reverse().ToArray());
168 | }
169 |
170 | ///
171 | /// A convenience for parsing a substring as an int and returning the results. Uses TryParse, and so returns zero where there is no valid int
172 | ///
173 | /// Substring parsed as int or zero
174 | /// Length of substring to parse
175 | /// Start index of substring to parse
176 | /// String to get substring of
177 | /// True if the parse succeeds
178 | public static bool IntParseSubstring(this string str, int startIndex, int length, out int r)
179 | {
180 | return Int32.TryParse(str.Substring(startIndex, length), out r);
181 | }
182 |
183 | ///
184 | /// Quickly convert a string to an integer, uses TryParse so any non-integers will return zero
185 | ///
186 | /// String to parse into an int
187 | /// Parsed int or zero
188 | public static int ToInt(this string str)
189 | {
190 | int r = 0;
191 | Int32.TryParse(str, out r);
192 | return r;
193 | }
194 |
195 | ///
196 | /// Returns a list of the lines of text from an embedded resource in the assembly.
197 | ///
198 | /// The name of the resource to get the contents of
199 | /// An enumerable of lines of text in the resource or null if the resource does not exist
200 | public static IEnumerable GetEmbeddedResourceLines(string resourceName)
201 | {
202 | var asm = Assembly.GetExecutingAssembly();
203 | if (!asm.GetManifestResourceNames().Contains(resourceName)) return null; // Not an embedded resource
204 |
205 | var lines = new List();
206 |
207 | using (var stream = asm.GetManifestResourceStream(resourceName))
208 | using (var text = new StreamReader(stream))
209 | {
210 | while (!text.EndOfStream)
211 | {
212 | lines.Add(text.ReadLine());
213 | }
214 | }
215 |
216 | return lines;
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/Zxcvbn.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Zxcvbn.Matcher;
6 |
7 | namespace Zxcvbn
8 | {
9 | ///
10 | /// Zxcvbn is used to estimate the strength of passwords.
11 | ///
12 | /// This implementation is a port of the Zxcvbn JavaScript library by Dan Wheeler:
13 | /// https://github.com/lowe/zxcvbn
14 | ///
15 | /// To quickly evaluate a password, use the static function.
16 | ///
17 | /// To evaluate a number of passwords, create an instance of this object and repeatedly call the function.
18 | /// Reusing the the Zxcvbn instance will ensure that pattern matchers will only be created once rather than being recreated for each password
19 | /// e=being evaluated.
20 | ///
21 | public class Zxcvbn
22 | {
23 | private const string BruteforcePattern = "bruteforce";
24 |
25 | private IMatcherFactory matcherFactory;
26 | private readonly Translation translation;
27 |
28 | ///
29 | /// Create a new instance of Zxcvbn that uses the default matchers.
30 | ///
31 | public Zxcvbn(Translation translation = Translation.English)
32 | : this(new DefaultMatcherFactory())
33 | {
34 | this.translation = translation;
35 | }
36 |
37 | ///
38 | /// Create an instance of Zxcvbn that will use the given matcher factory to create matchers to use
39 | /// to find password weakness.
40 | ///
41 | /// The factory used to create the pattern matchers used
42 | /// The language in which the strings are returned
43 | public Zxcvbn(IMatcherFactory matcherFactory, Translation translation = Translation.English)
44 | {
45 | this.matcherFactory = matcherFactory;
46 | this.translation = translation;
47 | }
48 |
49 | ///
50 | /// Perform the password matching on the given password and user inputs, returing the result structure with information
51 | /// on the lowest entropy match found.
52 | ///
53 | /// User data will be treated as another kind of dictionary matching, but can be different for each password being evaluated.para>
54 | ///
55 | /// Password
56 | /// Optionally, an enumarable of user data
57 | /// Result for lowest entropy match
58 | public Result EvaluatePassword(string password, IEnumerable userInputs = null)
59 | {
60 | userInputs = userInputs ?? new string[0];
61 |
62 | IEnumerable matches = new List();
63 |
64 | var timer = System.Diagnostics.Stopwatch.StartNew();
65 |
66 | foreach (var matcher in matcherFactory.CreateMatchers(userInputs))
67 | {
68 | matches = matches.Union(matcher.MatchPassword(password));
69 | }
70 |
71 | var result = FindMinimumEntropyMatch(password, matches);
72 |
73 | timer.Stop();
74 | result.CalcTime = timer.ElapsedMilliseconds;
75 |
76 | return result;
77 | }
78 |
79 | ///
80 | /// Returns a new result structure initialised with data for the lowest entropy result of all of the matches passed in, adding brute-force
81 | /// matches where there are no lesser entropy found pattern matches.
82 | ///
83 | /// Password being evaluated
84 | /// List of matches found against the password
85 | /// A result object for the lowest entropy match sequence
86 | private Result FindMinimumEntropyMatch(string password, IEnumerable matches)
87 | {
88 | var bruteforce_cardinality = PasswordScoring.PasswordCardinality(password);
89 |
90 | // Minimum entropy up to position k in the password
91 | var minimumEntropyToIndex = new double[password.Length];
92 | var bestMatchForIndex = new Match[password.Length];
93 |
94 | for (var k = 0; k < password.Length; k++)
95 | {
96 | // Start with bruteforce scenario added to previous sequence to beat
97 | minimumEntropyToIndex[k] = (k == 0 ? 0 : minimumEntropyToIndex[k - 1]) + Math.Log(bruteforce_cardinality, 2);
98 |
99 | // All matches that end at the current character, test to see if the entropy is less
100 | foreach (var match in matches.Where(m => m.j == k))
101 | {
102 | var candidate_entropy = (match.i <= 0 ? 0 : minimumEntropyToIndex[match.i - 1]) + match.Entropy;
103 | if (candidate_entropy < minimumEntropyToIndex[k])
104 | {
105 | minimumEntropyToIndex[k] = candidate_entropy;
106 | bestMatchForIndex[k] = match;
107 | }
108 | }
109 | }
110 |
111 |
112 | // Walk backwards through lowest entropy matches, to build the best password sequence
113 | var matchSequence = new List();
114 | for (var k = password.Length - 1; k >= 0; k--)
115 | {
116 | if (bestMatchForIndex[k] != null)
117 | {
118 | matchSequence.Add(bestMatchForIndex[k]);
119 | k = bestMatchForIndex[k].i; // Jump back to start of match
120 | }
121 | }
122 | matchSequence.Reverse();
123 |
124 |
125 | // The match sequence might have gaps, fill in with bruteforce matching
126 | // After this the matches in matchSequence must cover the whole string (i.e. match[k].j == match[k + 1].i - 1)
127 | if (matchSequence.Count == 0)
128 | {
129 | // To make things easy, we'll separate out the case where there are no matches so everything is bruteforced
130 | matchSequence.Add(new Match()
131 | {
132 | i = 0,
133 | j = password.Length,
134 | Token = password,
135 | Cardinality = bruteforce_cardinality,
136 | Pattern = BruteforcePattern,
137 | Entropy = Math.Log(Math.Pow(bruteforce_cardinality, password.Length), 2)
138 | });
139 | }
140 | else
141 | {
142 | // There are matches, so find the gaps and fill them in
143 | var matchSequenceCopy = new List();
144 | for (var k = 0; k < matchSequence.Count; k++)
145 | {
146 | var m1 = matchSequence[k];
147 | var m2 = (k < matchSequence.Count - 1 ? matchSequence[k + 1] : new Match() { i = password.Length }); // Next match, or a match past the end of the password
148 |
149 | matchSequenceCopy.Add(m1);
150 | if (m1.j < m2.i - 1)
151 | {
152 | // Fill in gap
153 | var ns = m1.j + 1;
154 | var ne = m2.i - 1;
155 | matchSequenceCopy.Add(new Match()
156 | {
157 | i = ns,
158 | j = ne,
159 | Token = password.Substring(ns, ne - ns + 1),
160 | Cardinality = bruteforce_cardinality,
161 | Pattern = BruteforcePattern,
162 | Entropy = Math.Log(Math.Pow(bruteforce_cardinality, ne - ns + 1), 2)
163 | });
164 | }
165 | }
166 |
167 | matchSequence = matchSequenceCopy;
168 | }
169 |
170 |
171 | var minEntropy = (password.Length == 0 ? 0 : minimumEntropyToIndex[password.Length - 1]);
172 | var crackTime = PasswordScoring.EntropyToCrackTime(minEntropy);
173 |
174 | var result = new Result();
175 | result.Password = password;
176 | result.Entropy = Math.Round(minEntropy, 3);
177 | result.MatchSequence = matchSequence;
178 | result.CrackTime = Math.Round(crackTime, 3);
179 | result.CrackTimeDisplay = Utility.DisplayTime(crackTime, this.translation);
180 | result.Score = PasswordScoring.CrackTimeToScore(crackTime);
181 |
182 | return result;
183 | }
184 |
185 | ///
186 | /// A static function to match a password against the default matchers without having to create
187 | /// an instance of Zxcvbn yourself, with supplied user data.
188 | ///
189 | /// Supplied user data will be treated as another kind of dictionary matching.
190 | ///
191 | /// the password to test
192 | /// optionally, the user inputs list
193 | /// The results of the password evaluation
194 | public static Result MatchPassword(string password, IEnumerable userInputs = null)
195 | {
196 | var zx = new Zxcvbn(new DefaultMatcherFactory());
197 | return zx.EvaluatePassword(password, userInputs);
198 | }
199 |
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/scripts/build_dictionaries.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import time
4 | import codecs
5 | import urllib
6 | import urllib2
7 |
8 | from pprint import pprint
9 |
10 | SLEEP_TIME = 20 # seconds
11 |
12 | def get_ranked_english():
13 | '''
14 | wikitionary has a list of ~40k English words, ranked by frequency of occurance in TV and movie transcripts.
15 | more details at:
16 | http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/TV/2006/explanation
17 |
18 | the list is separated into pages of 1000 or 2000 terms each.
19 | * the first 10k words are separated into pages of 1000 terms each.
20 | * the remainder is separated into pages of 2000 terms each:
21 | '''
22 | URL_TMPL = 'http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/TV/2006/%s'
23 | urls = []
24 | for i in xrange(10):
25 | freq_range = "%d-%d" % (i * 1000 + 1, (i+1) * 1000)
26 | urls.append(URL_TMPL % freq_range)
27 |
28 | for i in xrange(0,15):
29 | freq_range = "%d-%d" % (10000 + 2 * i * 1000 + 1, 10000 + (2 * i + 2) * 1000)
30 | urls.append(URL_TMPL % freq_range)
31 |
32 | urls.append(URL_TMPL % '40001-41284')
33 |
34 | ranked_terms = [] # ordered by rank, in decreasing frequency.
35 | for url in urls:
36 | html, is_cached = wiki_download(url)
37 | if not is_cached:
38 | time.sleep(SLEEP_TIME)
39 | new_terms = parse_wiki_terms(html)
40 | ranked_terms.extend(new_terms)
41 |
42 | return ranked_terms
43 |
44 | def wiki_download(url):
45 | '''
46 | scrape friendly: sleep 20 seconds between each request, cache each result.
47 | '''
48 | DOWNLOAD_TMPL = '../data/tv_and_movie_freqlist%s.html'
49 | freq_range = url[url.rindex('/')+1:]
50 |
51 | tmp_path = DOWNLOAD_TMPL % freq_range
52 | if os.path.exists(tmp_path):
53 | print 'cached.......', url
54 | with codecs.open(tmp_path, 'r', 'utf8') as f:
55 | return f.read(), True
56 | with codecs.open(tmp_path, 'w', 'utf8') as f:
57 | print 'downloading...', url
58 | req = urllib2.Request(url, headers={
59 | 'User-Agent': 'zxcvbn'
60 | })
61 | response = urllib2.urlopen(req)
62 | result = response.read().decode('utf8')
63 | f.write(result)
64 | return result, False
65 |
66 | def parse_wiki_terms(doc):
67 | '''who needs an html parser. fragile hax, but checks the result at the end'''
68 | results = []
69 | last3 = ['', '', '']
70 | header = True
71 | for line in doc.split('\n'):
72 | last3.pop(0)
73 | last3.append(line.strip())
74 | if all(s.startswith('') and not s == ' | | ' for s in last3):
75 | if header:
76 | header = False
77 | continue
78 | last3 = [s.replace('', '').replace(' | ', '').strip() for s in last3]
79 | rank, term, count = last3
80 | rank = int(rank.split()[0])
81 | term = term.replace('', '')
82 | term = term[term.index('>')+1:].lower()
83 | results.append(term)
84 | assert len(results) in [1000, 2000, 1284] # early docs have 1k entries, later have 2k, last doc has 1284
85 | return results
86 |
87 | def get_ranked_census_names():
88 | '''
89 | takes name lists from the the 2000 us census, prepares as a json array in order of frequency (most common names first).
90 |
91 | more info:
92 | http://www.census.gov/genealogy/www/data/2000surnames/index.html
93 |
94 | files in data are downloaded copies of:
95 | http://www.census.gov/genealogy/names/dist.all.last
96 | http://www.census.gov/genealogy/names/dist.male.first
97 | http://www.census.gov/genealogy/names/dist.female.first
98 | '''
99 | FILE_TMPL = '../data/us_census_2000_%s.txt'
100 | SURNAME_CUTOFF_PERCENTILE = 85 # ie7 can't handle huge lists. cut surname list off at a certain percentile.
101 | lists = []
102 | for list_name in ['surnames', 'male_first', 'female_first']:
103 | path = FILE_TMPL % list_name
104 | lst = []
105 | for line in codecs.open(path, 'r', 'utf8'):
106 | if line.strip():
107 | if list_name == 'surnames' and float(line.split()[2]) > SURNAME_CUTOFF_PERCENTILE:
108 | break
109 | name = line.split()[0].lower()
110 | lst.append(name)
111 | lists.append(lst)
112 | return lists
113 |
114 | def get_ranked_common_passwords():
115 | lst = []
116 | for line in codecs.open('../data/common_passwords.txt', 'r', 'utf8'):
117 | if line.strip():
118 | lst.append(line.strip())
119 | return lst
120 |
121 | def to_ranked_dict(lst):
122 | return dict((word, i) for i, word in enumerate(lst))
123 |
124 | def filter_short(terms):
125 | '''
126 | only keep if brute-force possibilities are greater than this word's rank in the dictionary
127 | '''
128 | return [term for i, term in enumerate(terms) if 26**(len(term)) > i]
129 |
130 | def filter_dup(lst, lists):
131 | '''
132 | filters lst to only include terms that don't have lower rank in another list
133 | '''
134 | max_rank = len(lst) + 1
135 | dct = to_ranked_dict(lst)
136 | dicts = [to_ranked_dict(l) for l in lists]
137 | return [word for word in lst if all(dct[word] < dct2.get(word, max_rank) for dct2 in dicts)]
138 |
139 | def filter_ascii(lst):
140 | '''
141 | removes words with accent chars etc.
142 | (most accented words in the english lookup exist in the same table unaccented.)
143 | '''
144 | return [word for word in lst if all(ord(c) < 128 for c in word)]
145 |
146 | def to_js(lst, lst_name):
147 | return 'var %s = %s;\n\n' % (lst_name, simplejson.dumps(lst))
148 |
149 | def main():
150 | english = get_ranked_english()
151 | surnames, male_names, female_names = get_ranked_census_names()
152 | passwords = get_ranked_common_passwords()
153 |
154 | [english,
155 | surnames, male_names, female_names,
156 | passwords] = [filter_ascii(filter_short(lst)) for lst in (english,
157 | surnames, male_names, female_names,
158 | passwords)]
159 |
160 | # make dictionaries disjoint so that d1 & d2 == set() for any two dictionaries
161 | all_dicts = set(tuple(l) for l in [english, surnames, male_names, female_names, passwords])
162 | passwords = filter_dup(passwords, all_dicts - set([tuple(passwords)]))
163 | male_names = filter_dup(male_names, all_dicts - set([tuple(male_names)]))
164 | female_names = filter_dup(female_names, all_dicts - set([tuple(female_names)]))
165 | surnames = filter_dup(surnames, all_dicts - set([tuple(surnames)]))
166 | english = filter_dup(english, all_dicts - set([tuple(english)]))
167 |
168 | lsts = locals()
169 | for lst_name in 'passwords male_names female_names surnames english'.split():
170 | with open('%s.lst' % lst_name, 'w') as f:
171 | lst = lsts[lst_name]
172 | f.writelines('%s\n' % item for item in lst)
173 | #f.write(to_js(lst, lst_name))
174 |
175 | print '\nall done! totals:\n'
176 | print 'passwords....', len(passwords)
177 | print 'male.........', len(male_names)
178 | print 'female.......', len(female_names)
179 | print 'surnames.....', len(surnames)
180 | print 'english......', len(english)
181 | print
182 |
183 | if __name__ == '__main__':
184 | if os.path.basename(os.getcwd()) != 'scripts':
185 | print 'run this from the scripts directory'
186 | exit(1)
187 | main()
188 |
--------------------------------------------------------------------------------
/zxcvbn-cs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {E80F559C-B5EB-4F14-9E3F-686A9C0237BF}
8 | Library
9 | Properties
10 | Zxcvbn
11 | Zxcvbn
12 | v4.0
13 | 512
14 | Client
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | bin\Debug\Zxcvbn.XML
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 | bin\Release\Zxcvbn.XML
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
--------------------------------------------------------------------------------
/zxcvbn-cs.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zxcvbn-cs", "zxcvbn-cs.csproj", "{E80F559C-B5EB-4F14-9E3F-686A9C0237BF}"
5 | EndProject
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zxcvbn-test", "zxcvbn-test\zxcvbn-test.csproj", "{64B83E87-A0D6-4614-939A-4AE0EA663DA9}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {E80F559C-B5EB-4F14-9E3F-686A9C0237BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {E80F559C-B5EB-4F14-9E3F-686A9C0237BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {E80F559C-B5EB-4F14-9E3F-686A9C0237BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {E80F559C-B5EB-4F14-9E3F-686A9C0237BF}.Release|Any CPU.Build.0 = Release|Any CPU
18 | {64B83E87-A0D6-4614-939A-4AE0EA663DA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {64B83E87-A0D6-4614-939A-4AE0EA663DA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {64B83E87-A0D6-4614-939A-4AE0EA663DA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {64B83E87-A0D6-4614-939A-4AE0EA663DA9}.Release|Any CPU.Build.0 = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(SolutionProperties) = preSolution
24 | HideSolutionNode = FALSE
25 | EndGlobalSection
26 | EndGlobal
27 |
--------------------------------------------------------------------------------
/zxcvbn-test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("zxcvbn-test")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("zxcvbn-test")]
13 | [assembly: AssemblyCopyright("Copyright © 2013")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("d0f66d89-8346-48eb-8c0c-db2567bd02c2")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/zxcvbn-test/ZxcvbnTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Zxcvbn;
6 |
7 | namespace zxcvbn_test
8 | {
9 | [TestClass]
10 | public class ZxcvbnTest
11 | {
12 | private static string[] testPasswords = new string[] {
13 | "zxcvbn",
14 | "qwER43@!",
15 | "Tr0ub4dour&3",
16 | "correcthorsebatterystaple",
17 | "coRrecth0rseba++ery9.23.2007staple$",
18 | "D0g..................",
19 | "abcdefghijk987654321",
20 | "neverforget13/3/1997",
21 | "1qaz2wsx3edc",
22 | "temppass22",
23 | "briansmith",
24 | "briansmith4mayor",
25 | "password1",
26 | "viking",
27 | "thx1138",
28 | "ScoRpi0ns",
29 | "do you know",
30 | "ryanhunter2000",
31 | "rianhunter2000",
32 | "asdfghju7654rewq",
33 | "AOEUIDHG&*()LS_",
34 | "12345678",
35 | "defghi6789",
36 | "rosebud",
37 | "Rosebud",
38 | "ROSEBUD",
39 | "rosebuD",
40 | "ros3bud99",
41 | "r0s3bud99",
42 | "R0$38uD99",
43 | "verlineVANDERMARK",
44 | "eheuczkqyq",
45 | "rWibMFACxAUGZmxhVncy",
46 | "Ba9ZyWABu99[BK#6MBgbH88Tofv)vs$w"
47 | };
48 |
49 | // Entropies for the above passwords, in order, as generated by zxcvbn
50 | private static double[] expectedEntropies = new double[] {
51 | 6.845,
52 | 26.44,
53 | 30.435,
54 | 45.212,
55 | 66.018,
56 | 20.678,
57 | 11.951,
58 | 32.628,
59 | 19.314,
60 | 22.179,
61 | 4.322,
62 | 18.64,
63 | 2,
64 | 7.531,
65 | 7.426,
66 | 20.621,
67 | 20.257,
68 | 14.506,
69 | 21.734,
70 | 29.782,
71 | 33.254,
72 | 1.585,
73 | 12.607,
74 | 7.937,
75 | 8.937,
76 | 8.937,
77 | 8.937,
78 | 19.276,
79 | 19.276,
80 | 25.076,
81 | 26.293,
82 | 42.813,
83 | 104.551,
84 | 167.848
85 | };
86 |
87 | [TestMethod]
88 | public void RunAllTestPasswords()
89 | {
90 | var zx = new Zxcvbn.Zxcvbn(new Zxcvbn.DefaultMatcherFactory());
91 |
92 | for (int i = 0; i < testPasswords.Length; ++i)
93 | {
94 | var password = testPasswords[i];
95 |
96 | var result = zx.EvaluatePassword(password);
97 |
98 | O("");
99 | O("Password: {0}", result.Password);
100 | O("Entropy: {0}", result.Entropy);
101 | O("Crack Time (s): {0}", result.CrackTime);
102 | O("Crack Time (d): {0}", result.CrackTimeDisplay);
103 | O("Score (0 to 4): {0}", result.Score);
104 | O("Calc time (ms): {0}", result.CalcTime);
105 | O("--------------------");
106 |
107 | foreach (var match in result.MatchSequence)
108 | {
109 | if (match != result.MatchSequence.First()) O("+++++++++++++++++");
110 |
111 | O(match.Token);
112 | O("Pattern: {0}", match.Pattern);
113 | O("Entropy: {0}", match.Entropy);
114 |
115 | if (match is Zxcvbn.Matcher.DictionaryMatch)
116 | {
117 | var dm = match as Zxcvbn.Matcher.DictionaryMatch;
118 | O("Dict. Name: {0}", dm.DictionaryName);
119 | O("Rank: {0}", dm.Rank);
120 | O("Base Entropy: {0}", dm.BaseEntropy);
121 | O("Upper Entpy: {0}", dm.UppercaseEntropy);
122 | }
123 |
124 | if (match is Zxcvbn.Matcher.L33tDictionaryMatch)
125 | {
126 | var lm = match as Zxcvbn.Matcher.L33tDictionaryMatch;
127 | O("L33t Entpy: {0}", lm.L33tEntropy);
128 | O("Unleet: {0}", lm.MatchedWord);
129 | }
130 |
131 | if (match is Zxcvbn.Matcher.SpatialMatch)
132 | {
133 | var sm = match as Zxcvbn.Matcher.SpatialMatch;
134 | O("Graph: {0}", sm.Graph);
135 | O("Turns: {0}", sm.Turns);
136 | O("Shifted Keys: {0}", sm.ShiftedCount);
137 | }
138 |
139 | if (match is Zxcvbn.Matcher.RepeatMatch)
140 | {
141 | var rm = match as Zxcvbn.Matcher.RepeatMatch;
142 | O("Repeat char: {0}", rm.RepeatChar);
143 | }
144 |
145 | if (match is Zxcvbn.Matcher.SequenceMatch)
146 | {
147 | var sm = match as Zxcvbn.Matcher.SequenceMatch;
148 | O("Seq. name: {0}", sm.SequenceName);
149 | O("Seq. size: {0}", sm.SequenceSize);
150 | O("Ascending: {0}", sm.Ascending);
151 | }
152 |
153 | if (match is Zxcvbn.Matcher.DateMatch)
154 | {
155 | var dm = match as Zxcvbn.Matcher.DateMatch;
156 | O("Day: {0}", dm.Day);
157 | O("Month: {0}", dm.Month);
158 | O("Year: {0}", dm.Year);
159 | O("Separator: {0}", dm.Separator);
160 | }
161 | }
162 |
163 | O("");
164 | O("=========================================");
165 |
166 | Assert.AreEqual(expectedEntropies[i], result.Entropy);
167 | }
168 | }
169 |
170 | private void O(string format, params object[] args)
171 | {
172 | System.Diagnostics.Debug.WriteLine(format, args);
173 | }
174 |
175 |
176 | [TestMethod]
177 | public void BruteForceCardinalityTest()
178 | {
179 | Assert.AreEqual(26, Zxcvbn.PasswordScoring.PasswordCardinality("asdf"));
180 | Assert.AreEqual(26, Zxcvbn.PasswordScoring.PasswordCardinality("ASDF"));
181 | Assert.AreEqual(52, Zxcvbn.PasswordScoring.PasswordCardinality("aSDf"));
182 | Assert.AreEqual(10, Zxcvbn.PasswordScoring.PasswordCardinality("124890"));
183 | Assert.AreEqual(62, Zxcvbn.PasswordScoring.PasswordCardinality("aS159Df"));
184 | Assert.AreEqual(33, Zxcvbn.PasswordScoring.PasswordCardinality("!@<%:{$:#<@}{+&)(*%"));
185 | Assert.AreEqual(100, Zxcvbn.PasswordScoring.PasswordCardinality("©"));
186 | Assert.AreEqual(95, Zxcvbn.PasswordScoring.PasswordCardinality("ThisIs@T3stP4ssw0rd!"));
187 | }
188 |
189 | [TestMethod]
190 | public void TimeDisplayStrings()
191 | {
192 | // Note that the time strings should be + 1
193 | Assert.AreEqual("11 minutes", Zxcvbn.Utility.DisplayTime(60 * 10, Translation.English));
194 | Assert.AreEqual("2 days", Zxcvbn.Utility.DisplayTime(60 * 60 * 24, Translation.English));
195 | Assert.AreEqual("17 years", Zxcvbn.Utility.DisplayTime(60 * 60 * 24 * 365 * 15.4, Translation.English));
196 | }
197 |
198 | [TestMethod]
199 | public void TimeDisplayStringsGerman()
200 | {
201 | // Note that the time strings should be + 1
202 | Assert.AreEqual("11 Minuten", Zxcvbn.Utility.DisplayTime(60 * 10, Translation.German));
203 | Assert.AreEqual("2 Tage", Zxcvbn.Utility.DisplayTime(60 * 60 * 24, Translation.German));
204 | Assert.AreEqual("17 Jahre", Zxcvbn.Utility.DisplayTime(60 * 60 * 24 * 365 * 15.4, Translation.German));
205 | }
206 |
207 | [TestMethod]
208 | public void RepeatMatcher()
209 | {
210 | var repeat = new Zxcvbn.Matcher.RepeatMatcher();
211 |
212 | var res = repeat.MatchPassword("aaasdffff");
213 | Assert.AreEqual(2, res.Count());
214 |
215 | var m1 = res.ElementAt(0);
216 | Assert.AreEqual(0, m1.i);
217 | Assert.AreEqual(2, m1.j);
218 | Assert.AreEqual("aaa", m1.Token);
219 |
220 | var m2 = res.ElementAt(1);
221 | Assert.AreEqual(5, m2.i);
222 | Assert.AreEqual(8, m2.j);
223 | Assert.AreEqual("ffff", m2.Token);
224 |
225 |
226 | res = repeat.MatchPassword("asdf");
227 | Assert.AreEqual(0, res.Count());
228 | }
229 |
230 | [TestMethod]
231 | public void SequenceMatcher()
232 | {
233 | var seq = new Zxcvbn.Matcher.SequenceMatcher();
234 |
235 | var res = seq.MatchPassword("abcd");
236 | Assert.AreEqual(1, res.Count());
237 | var m1 = res.First();
238 | Assert.AreEqual(0, m1.i);
239 | Assert.AreEqual(3, m1.j);
240 | Assert.AreEqual("abcd", m1.Token);
241 |
242 | res = seq.MatchPassword("asdfabcdhujzyxwhgjj");
243 | Assert.AreEqual(2, res.Count());
244 |
245 | m1 = res.ElementAt(0);
246 | Assert.AreEqual(4, m1.i);
247 | Assert.AreEqual(7, m1.j);
248 | Assert.AreEqual("abcd", m1.Token);
249 |
250 | var m2 = res.ElementAt(1);
251 | Assert.AreEqual(11, m2.i);
252 | Assert.AreEqual(14, m2.j);
253 | Assert.AreEqual("zyxw", m2.Token);
254 |
255 | res = seq.MatchPassword("dfsjkhfjksdh");
256 | Assert.AreEqual(0, res.Count());
257 | }
258 |
259 | [TestMethod]
260 | public void DigitsRegexMatcher()
261 | {
262 | var re = new Zxcvbn.Matcher.RegexMatcher("\\d{3,}", 10);
263 |
264 | var res = re.MatchPassword("abc123def");
265 | Assert.AreEqual(1, res.Count());
266 | var m1 = res.First();
267 | Assert.AreEqual(3, m1.i);
268 | Assert.AreEqual(5, m1.j);
269 | Assert.AreEqual("123", m1.Token);
270 |
271 | res = re.MatchPassword("123456789a12345b1234567");
272 | Assert.AreEqual(3, res.Count());
273 | var m3 = res.ElementAt(2);
274 | Assert.AreEqual("1234567", m3.Token);
275 |
276 | res = re.MatchPassword("12");
277 | Assert.AreEqual(0, res.Count());
278 |
279 | res = re.MatchPassword("dfsdfdfhgjkdfngjl");
280 | Assert.AreEqual(0, res.Count());
281 | }
282 |
283 | [TestMethod]
284 | public void DateMatcher()
285 | {
286 | var dm = new Zxcvbn.Matcher.DateMatcher();
287 |
288 | var res = dm.MatchPassword("1297");
289 | Assert.AreEqual(1, res.Count());
290 |
291 | res = dm.MatchPassword("98123");
292 | Assert.AreEqual(1, res.Count());
293 |
294 | res = dm.MatchPassword("221099");
295 | Assert.AreEqual(1, res.Count());
296 |
297 | res = dm.MatchPassword("352002");
298 | Assert.AreEqual(1, res.Count());
299 |
300 | res = dm.MatchPassword("2011157");
301 | Assert.AreEqual(1, res.Count());
302 |
303 | res = dm.MatchPassword("11222015");
304 | Assert.AreEqual(1, res.Count());
305 |
306 | res = dm.MatchPassword("2013/06/1");
307 | Assert.AreEqual(2, res.Count()); // 2 since 2013 is a valid date without separators in its own right
308 |
309 | res = dm.MatchPassword("13-05-08");
310 | Assert.AreEqual(2, res.Count()); // 2 since prefix and suffix year sep matcher valid, so counts twice
311 |
312 | res = dm.MatchPassword("17 8 1992");
313 | Assert.AreEqual(3, res.Count()); // 3 since 1992 is a valid date without separators in its own right, and a partial match is valid prefix year
314 |
315 | res = dm.MatchPassword("10.16.16");
316 | Assert.AreEqual(1, res.Count());
317 | }
318 |
319 | [TestMethod]
320 | public void SpatialMatcher()
321 | {
322 | var sm = new Zxcvbn.Matcher.SpatialMatcher();
323 |
324 | var res = sm.MatchPassword("qwert");
325 | Assert.AreEqual(1, res.Count());
326 | var m1 = res.First();
327 | Assert.AreEqual("qwert", m1.Token);
328 | Assert.AreEqual(0, m1.i);
329 | Assert.AreEqual(4, m1.j);
330 |
331 | res = sm.MatchPassword("plko14569852pyfdb");
332 | Assert.AreEqual(6, res.Count()); // Multiple matches from different keyboard types
333 | }
334 |
335 | [TestMethod]
336 | public void BinomialTest()
337 | {
338 | Assert.AreEqual(1, Zxcvbn.PasswordScoring.Binomial(0, 0));
339 | Assert.AreEqual(1, Zxcvbn.PasswordScoring.Binomial(1, 0));
340 | Assert.AreEqual(0, Zxcvbn.PasswordScoring.Binomial(0, 1));
341 | Assert.AreEqual(1, Zxcvbn.PasswordScoring.Binomial(1, 1));
342 | Assert.AreEqual(56, Zxcvbn.PasswordScoring.Binomial(8, 3));
343 | Assert.AreEqual(2598960, Zxcvbn.PasswordScoring.Binomial(52, 5));
344 | }
345 |
346 | [TestMethod]
347 | public void DictionaryTest()
348 | {
349 | var dm = new Zxcvbn.Matcher.DictionaryMatcher("test", "test_dictionary.txt");
350 |
351 | var res = dm.MatchPassword("NotInDictionary");
352 | Assert.AreEqual(0, res.Count());
353 |
354 | res = dm.MatchPassword("choreography");
355 | Assert.AreEqual(1, res.Count());
356 |
357 | res = dm.MatchPassword("ChOrEograPHy");
358 | Assert.AreEqual(1, res.Count());
359 |
360 |
361 | var leet = new Zxcvbn.Matcher.L33tMatcher(dm);
362 | res = leet.MatchPassword("3mu");
363 | Assert.AreEqual(1, res.Count());
364 |
365 | res = leet.MatchPassword("3mupr4nce|egume");
366 | }
367 |
368 | [TestMethod]
369 | public void L33tTest()
370 | {
371 | var l = new Zxcvbn.Matcher.L33tMatcher(new Zxcvbn.Matcher.DictionaryMatcher("test", new List {"password"}));
372 |
373 | l.MatchPassword("password");
374 | l.MatchPassword("p@ssword");
375 | l.MatchPassword("p1ssword");
376 | l.MatchPassword("p1!ssword");
377 | l.MatchPassword("p1!ssw0rd");
378 | l.MatchPassword("p1!ssw0rd|");
379 | }
380 |
381 | [TestMethod]
382 | public void EmptyPassword()
383 | {
384 | var res = Zxcvbn.Zxcvbn.MatchPassword("");
385 | Assert.AreEqual(0, res.Entropy);
386 | }
387 |
388 | [TestMethod]
389 | public void SinglePasswordTest()
390 | {
391 | var res = Zxcvbn.Zxcvbn.MatchPassword("||ke");
392 | }
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/zxcvbn-test/test_dictionary.txt:
--------------------------------------------------------------------------------
1 | prance
2 | emu
3 | choreography
4 | legume
--------------------------------------------------------------------------------
/zxcvbn-test/zxcvbn-test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {64B83E87-A0D6-4614-939A-4AE0EA663DA9}
7 | Library
8 | Properties
9 | zxcvbn_test
10 | zxcvbn-test
11 | v4.5
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 10.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 |
29 |
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | {e80f559c-b5eb-4f14-9e3f-686a9c0237bf}
59 | zxcvbn-cs
60 |
61 |
62 |
63 |
64 | PreserveNewest
65 |
66 |
67 |
68 |
69 |
70 |
71 | False
72 |
73 |
74 | False
75 |
76 |
77 | False
78 |
79 |
80 | False
81 |
82 |
83 |
84 |
85 |
86 |
87 |
94 |
--------------------------------------------------------------------------------