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