C++ でパスワードの強度を調べる

(1/1)
>C++でパスワードの強度を調べる
C++でパスワード生成機を作る」でパスワード生成機を作ったが、今回は、入力したパスワードの強度を調べるプログラムを作る。
PHPでパスワードの強度を調べる(その2)」で作ったPHPプログラムを移植したものである。

(2026年6月14日)--levbel,--fullオプション追加, 使用ライブラリ更新
(2026年3月28日)ブラックリスト辞書の強化,使用ライブラリ更新, optional名前衝突回避
(2025年11月30日)使用ライブラリ更新
(2025年8月2日)使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
passwordStrength.msiインストーラ
bin/passwordStrength.exe実行プログラム本体(GUI版)
bin/pswst.exe実行プログラム本体(CUI版)
bin/etc/help.chmヘルプ・ファイル
sour/passwordStrength.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル(GUI版)
sour/resource2.rcリソース・ファイル(CUI版)
sour/application.icoアプリケーション・アイコン(GUI版)
sour/application2.icoアプリケーション・アイコン(CUI版)
sour/passwords.txtブラックリスト辞書(サンプル)
sour/makefileGUI版ビルド
sour/makefile_cmdCUI版ビルド
passwordStrength.cpp 更新履歴
バージョン 更新日 内容
1.1.0 2026/06/14 --levbel,--fullオプション追加, 使用ライブラリ更新
1.0.9 2026/03/28 ブラックリスト辞書の強化,使用ライブラリ更新, optional名前衝突回避
1.0.8 2025/11/30 使用ライブラリ更新
1.0.7 2025/08/02 使用ライブラリ更新
1.0.6 2025/03/29 ブラックリスト辞書の強化,使用ライブラリ更新

使用ライブラリ

CUI版でコマンドライン・オプションを操作する場合などに、オープンソースのライブラリ Boost C++ライブラリが必要になる。ハッシュ値(MD5)の計算に OpenSSL が必要になる。各々の導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

リソースの準備

Eclipse を起動し、新規プロジェクト nengowin を用意する。
ResEdit を起動し、resource.rc を用意する。

Eclipse に戻り、ソース・プログラム "passwordStrength.cpp" を追加する。
リンカー・フラグを -s -mwindows -static -lstdc++ -lgcc -lole32 -lcrypto -lboost_program_options-mt -lpthread -lwinpthread" に設定する。
また、CUI版をビルドするために、構成 CMD を追加し、リンカー・フラグを -s -static -lstdc++ -lgcc -lole32 -lcrypto -lboost_program_options-mt -lpthread に設定する。

MSYS2 コマンドラインからビルドするのであれば、"makefile" と "makefile_cmd" を利用してほしい。

プログラム製作の背景

会員制Webサイトや社内情報システムの規定に、「パスワードは半年ごとに更新する」というものがある。2006年7月にIPAが「定期的に変更する(初期パスワードをそのまま使わない)」のような注意点を挙げている影響を受けての措置なのだが、近年、定期的に変更する効果は薄いとされてきている。むしろ、新しいパスワードを忘れないように付箋に書いたり、他のパスワードを使い回すような運用回避が横行し、むしろパスワードの強度が下がっている可能性もある。

NIST(National Institute of Standards and Technology;米国立標準技術研究所)は、セキュリティ文書「NIST Special Publication 800-63B」の「5.1.1 記憶シークレット」に、パスワードの要件として次の項目を挙げている。
  1. 長いパスワード(8文字以上、最長64文字)
  2. 表示可能文字のASCII,Unicodeや空白の使用を許可
  3. Password1やqwerty123などの違反したパスワード、辞書単語をブラックリストに設定
  4. aaaa1234や123456などの連続した同じ文字の使用を制限
  5. 強固なパスワードチェッカーを使用
  6. 連続で認証失敗した場合に強制的にアカウントをロック
  7. パスワードの入力でペースト機能の使用を許可
  8. パスワードの他、別の種類による二要素認証を強制


この要件の1~4を実装した「5.パスワードチェッカー」を作ろうと考えた。

解説:指定した文字列が数字だけかどうか

passwordStrength.cpp

 546: /**
 547:  * 指定した文字列が数字だけかどうかを求める.
 548:  * @param   string str 文字列
 549:  * @return  bool TRUE:数字だけである/FALSE:ではない
 550: */
 551: bool isNumbersOnly(string str) {
 552:     regex re("^[0-9]+$");
 553:     return regex_match(str, re);
 554: }

ユーザー関数 isNumbersOnly は、指定した文字列が数字だけかどうかを求める。正規表現でマッチングさせている。

解説:指定した文字列が英数字だけかどうか

passwordStrength.cpp

 556: /**
 557:  * 指定した文字列が英数字だけかどうかを求める.
 558:  * @param   string str 文字列
 559:  * @return  bool TRUE:英数字だけである/FALSE:ではない
 560: */
 561: bool isAlphanumeric(string str) {
 562:     regex re("^[A-Z|a-z|0-9]+$");
 563:     return regex_match(str, re);
 564: }

ユーザー関数 isAlphanumeric は、指定した文字列が英数字だけかどうかを求める。正規表現でマッチングさせている。

解説:指定した文字列が辞書に存在するかどうか

passwordStrength.cpp

 566: /**
 567:  * 指定した文字列が辞書に存在するかどうかを求める.
 568:  * 指定した文字列を小文字に統一して辞書ファイルと比較する.
 569:  * @param   string str 文字列
 570:  * @return  bool TRUE:存在する/FALSE:存在しない、またはエラー
 571: */
 572: bool inDictionary(string str) {
 573:     unsigned char s1[SIZE_BUFF + 1];
 574:     unsigned char s2[SIZE_BUFF + 1];
 575: 
 576:     string s0 = str;
 577:     // 小文字に統一する
 578:     transform(s0.begin(), s0.end(), s0.begin(), ::tolower);
 579:     size_t len = s0.length();
 580:     // MD5ハッシュ値をs1に代入する.
 581:     MD5((const unsigned char *)s0.c_str(), len, (unsigned char *)s1);
 582: 
 583: //  printDictionary((Digest *)s1);
 584: //  cout << endl;
 585: 
 586:     // カーソルを砂時計に
 587:     HCURSOR cur = SetCursor(LoadCursor(NULL, IDC_WAIT));
 588: 
 589:     // 辞書ファイルに含まれているかどうか探す.
 590:     bool ret = FALSE;
 591:     const char *dicname = getDictionaryName();
 592:     FILE *infp = fopen(dicname, "rb");
 593:     if (infp == NULL) {
 594:         ErrorMessage = "辞書ファイルが見当たりません";
 595:         cout << ErrorMessage << endl;
 596:         return FALSE;
 597:     }
 598:     for (size_t i = 0i < MAX_WORDSi++) {
 599:         if (feof(infp))     break;
 600:         fread(s2, sizeof(Digest), 1, infp);
 601: 
 602: //      printDictionary((Digest *)s2);
 603: //      cout << endl;
 604: 
 605:         if (memcmp(s1, s2, sizeof(Digest)) == 0) {
 606:             ret = TRUE;
 607:             break;
 608:         }
 609:     }
 610:     fclose(infp);
 611: 
 612:     // カーソルを元に戻す
 613:     SetCursor(cur);
 614: 
 615:     return ret;
 616: }

ユーザー関数 inDictionary は、指定した文字列が辞書に存在するかどうかを求める。
入力されたパスワードを、MD5でハッシュ化し、ブラックリスト辞書にあるかどうかを突合する。ブラックリスト辞書は固定長バイナリファイルなので、forループで回して合致するハッシュ値があるかどうかを調べている。

解説:指定した文字列が記号を含まないかどうか

passwordStrength.cpp

 618: /**
 619:  * 指定した文字列が記号を含むかどうかを返す.
 620:  * @param   string str 文字列
 621:  * @return  bool TRUE:記号を含んでいない/FALSE:含んでいる
 622: */
 623: bool isContainNoSymbol(string str) {
 624:     regex re("^[A-Z|a-z|0-9]+$");
 625:     return regex_match(str, re);
 626: }

ユーザー関数 isContainNoSymbol は、指定した文字列が記号を含まないかどうかを返す。正規表現でマッチングさせている。

解説:指定した文字列に連続した文字を含むかどうか

passwordStrength.cpp

 628: /**
 629:  * 指定した文字列に連続した文字を含むかどうかを求める.
 630:  * @param   string str 文字列
 631:  * @return  bool true:連続した文字を含む/false:含まない
 632: */
 633: bool isSeqCharacters(string str) {
 634:     const char *ptr = str.c_str();
 635:     bool ret = FALSE;
 636:     size_t len = str.length();
 637:     if (len > 1) {
 638:         for (size_t i = 1i < leni++) {
 639:             if (ptr[i] == ptr[i - 1]) {
 640:                 ret = TRUE;
 641:                 break;
 642:             }
 643:         }
 644:     }
 645:     return ret;
 646: }

ユーザー関数 isSeqCharacters は、指定した文字列に連続した文字を含むかどうかを返す。文字列を配列と見なして、直前の文字と一致するかどうかをチェックしている。

解説:総当たりで解読するときの時間

passwordStrength.cpp

 648: /**
 649:  * 指定したパスワードを総当たりで解読するときの時間を求める.
 650:  * @param   string psw パスワード
 651:  * @return  string 解読時間
 652: */
 653: string calcDecodeTime(string psw) {
 654:     // 文字種と桁数を求める.
 655:     regex re1("^[0-9]+$");
 656:     regex re2("^[A-Z]+$");
 657:     regex re3("^[0-9|A-Z]+$");
 658:     regex re4("^[a-z]+$");
 659:     regex re5("^[0-9|a-z]+$");
 660:     regex re6("^[A-Z|a-z]+$");
 661:     regex re7("^[0-9|A-Z|a-z]+$");
 662:     size_t num, len;
 663:     if (regex_match(psw, re1)) {
 664:         num = 10;
 665:     } else if (regex_match(psw, re2)) {
 666:         num = 26;
 667:     } else if (regex_match(psw, re3)) {
 668:         num = 36;
 669:     } else if (regex_match(psw, re4)) {
 670:         num = 26;
 671:     } else if (regex_match(psw, re5)) {
 672:         num = 36;
 673:     } else if (regex_match(psw, re6)) {
 674:         num = 52;
 675:     } else if (regex_match(psw, re7)) {
 676:         num = 62;
 677:     } else {
 678:         num = 86;
 679:     }
 680:     len = psw.length();
 681: 
 682:     // 解読時間を計算する.
 683:     static char buff[SIZE_BUFF + 1];
 684:     double sec = (double)pow(num, len* DECODE_TIME;
 685:     if (sec <1) {
 686:         snprintf(buff, sizeof(buff), "1秒以下で解読できる.");
 687:     } else if (sec < 60) {
 688:         snprintf(buff, sizeof(buff), "解読に約%.0f秒かかる.", sec);
 689:     } else if (sec < (double)60 * 60) {
 690:         snprintf(buff, sizeof(buff), "解読に約%.0f分かかる.", sec / 60);
 691:     } else if (sec < (double)60 * 60 * 24) {
 692:         snprintf(buff, sizeof(buff), "解読に約%.0f時間かかる.", sec / (60 * 60));
 693:     } else if (sec < (double)60 * 60 * 24 * 30) {
 694:         snprintf(buff, sizeof(buff), "解読に約%.0f日かかる.", sec / (60 * 60 * 24));
 695:     } else if (sec < (double)60 * 60 * 24 * 30 * 12) {
 696:         snprintf(buff, sizeof(buff), "解読に約%.0fヶ月かかる.", sec / (60 * 60 * 24 * 30));
 697:     } else if (sec < (double)60 * 60 * 24 * 30 * 12 * 1000) {
 698:         snprintf(buff, sizeof(buff), "解読に約%.0f年かかる.", sec / ((double)60 * 60 * 24 * 30 * 12));
 699:     } else {
 700:         snprintf(buff, sizeof(buff), "解読に1000年以上かかる.");
 701:     }
 702: 
 703:     return (string)buff;
 704: }

ユーザー関数 calcDecodeTime は、指定したパスワードを総当たりで解読するときの時間を求める。
使用されている文字種の数を変数 num に、パスワード長を変数 len に格納し、numlen 乗がが総当たりパターン数となる。これに1パターンあたりの解読時間 DECODE_TIME を乗ずることで解読時間(秒)を求める。NVIDIA製GPU「RTX 4090」1基で、英小文字8文字からなるパスワードを5分で解読できるという記事(NVIDIAの高性能グラボは複雑なパスワードも短時間で突破可能, GIGAZINE, 2024年5月2日)より、$$ \displaystyle DECODE\_TIME = \frac{5 \times 60}{(26 \times 2) ^ 8} $$ と算出した。
この関数は、結果を読みやすいように日本語文字列に変換して戻す。

解説:パスワードの強度を求める

passwordStrength.cpp

 706: /**
 707:  * 指定したパスワードの強度を求める.
 708:  * 強度は1〜5の整数で,数字が大きいほど強度が強い.
 709:  * @param   string psw パスワード
 710:  * @param   size_t min パスワードの最小長;省略時はPASSWORD_MINIMUM_LENGTH
 711:  * @param   size_t max パスワードの最大長;省略時はPASSWORD_MAXIMUM_LENGTH
 712:  * @return  int 強度
 713:  *              1:数字のみ
 714:  *              2:英数n文字以下
 715:  *              3:ブラックリスト辞書に存在する
 716:  *              4:1〜3をクリアし,記号が含まれていない
 717:  *              5:1〜3をクリアし,記号が含まれている
 718:  *              6:1〜5をクリアし,連続した文字がない
 719: */
 720: int getPasswordStrength(string psw, size_t min=PASSWORD_MINIMUM_LENGTH, size_t max=PASSWORD_MAXIMUM_LENGTH) {
 721:     // 空白を除く
 722:     string str = regex_replace(psw, regex("[ \\t]+"), "");
 723: 
 724:     // パスワードstrの強度を算出する.
 725:     int ret = 0;
 726:     if ((psw.length() < min|| (psw.length() > max)) {
 727:         ret = 0;
 728:     } else if (isNumbersOnly(str)) {
 729:         ret = 1;
 730:     } else if (isAlphanumeric(str)) {
 731:         ret = 2;
 732:     } else if (inDictionary(str)) {
 733:         ret = 3;
 734:     } else if (isContainNoSymbol(str)) {
 735:         ret = 4;
 736:     } else if (isSeqCharacters(str)) {
 737:         ret = 5;
 738:     } else {
 739:         ret = 6;
 740:     }
 741:     return ret;
 742: }

ユーザー関数 getPasswordStrength は、これまでの関数を利用し、パスワードの強度を求める。

解説:辞書ファイル名を求める

passwordStrength.cpp

 296: /**
 297:  * 辞書ファイル名を求める.
 298:  * @param   なし
 299:  * @return  string 辞書ファイル名
 300: */
 301: const char *getDictionaryName(void) {
 302:     static char buff[SIZE_BUFF + 1];
 303:     strncpy(buff, (getMyPath(APPNAME+ FILENAME_DIC).c_str(), SIZE_BUFF);
 304:     return (const char *)buff;
 305: }

CUI版の機能であるブラックリスト辞書作成機能を解説する。
まず、ユーザー関数 getDictionaryName は、ブラックリスト辞書ファイルへのフルパスを求める。ユーザーのAppDataの下にある。

解説:辞書ファイルを空にする

passwordStrength.cpp

 321: /**
 322:  * 辞書ファイルを空にする.
 323:  * @param   なし
 324:  * @return  bool TRUE:成功/FALSE:失敗
 325: */
 326: bool emptyDictionary() {
 327:     const char *outfname = getDictionaryName();
 328:     FILE *outfp;
 329:     outfp = fopen(outfname, "wb");
 330:     fclose(outfp);
 331: 
 332:     return TRUE;
 333: }

ユーザー関数 emptyDictionary は、ブラックリスト辞書をゼロ・サイズのファイルにする。

解説:辞書ファイルを指定したメモリに読み込む

passwordStrength.cpp

 335: /**
 336:  * 辞書ファイルを指定したメモリに読み込む.
 337:  * @param   unsigned char *dic  読み込むメモリ
 338:  * @return  size_t 読み込んだ見出語の数
 339: */
 340: size_t readDictionary(unsigned char *dic) {
 341:     size_t cnt;
 342:     const char *infname = getDictionaryName();
 343:     FILE *infp;
 344:     infp = fopen(infname, "rb");
 345:     if (infp == NULL) {
 346:         ErrorMessage = "辞書ファイルが見つかりません";
 347:         return 0;
 348:     }
 349: 
 350:     // 辞書カウンタを初期化してメモリに読み込む.
 351:     DictionaryCounter = 0;
 352:     for (cnt = 0cnt < MAX_WORDScnt++) {
 353:         if (feof(infp))     break;
 354:         fread(dic + cnt * sizeof(Digest), sizeof(Digest), 1, infp);
 355:         DictionaryCounter++;
 356:     }
 357:     fclose(infp);
 358: 
 359:     return cnt;
 360: }

ブラックリスト辞書に追加する場合は、まず、ユーザー関数 readDictionary を使ってブラックリスト辞書をメモリ上に読み込む。

解説:指定したハッシュ値が指定したメモリになければ追加する

passwordStrength.cpp

 380: /**
 381:  * 指定したハッシュ値が指定したメモリになければ追加する.
 382:  * 直近に登録した50万件に重複がなければ追加する.
 383:  * @param   unsigned char *dic  ハッシュ値格納メモリ
 384:  * @param   Digest *md5         ハッシュ値
 385:  * @return  bool TRUE:追加成功/FALSE:失敗
 386: */
 387: bool addDictionary(unsigned char *dic, Digest *md5) {
 388:     static Digest zero;
 389:     memset((void *)&zero, 0, sizeof(Digest));
 390:     size_t i;
 391:     static unsigned char *pointer;
 392:     static size_t ll = sizeof(Digest);
 393:     size_t cnt;
 394: 
 395:     // BACK_WORDS語だけ遡って重複チェックする.
 396:     if (DictionaryCounter > BACK_WORDS) {
 397:         cnt = DictionaryCounter - BACK_WORDS;
 398:         pointer = dic + cnt * ll;
 399:     // それ以外
 400:     } else {
 401:         cnt = 0;
 402:         pointer = dic;
 403:     }
 404: 
 405:     // 指定したハッシュ値が指定したメモリになければ追加する.
 406:     for (i = cnti < MAX_WORDSi++) {
 407:         if (memcmp(pointer, (void *)&zero, ll) == 0) {
 408:             break;
 409:         } else if (memcmp(pointer, (void *)md5, ll) == 0) {
 410:             return FALSE;
 411:         }
 412:         pointer +ll;
 413:     }
 414: 
 415:     // 登録可能かどうかを検査する.
 416:     if (i >MAX_WORDS) {
 417:         ErrorMessage = "辞書に登録できない";
 418:         return FALSE;
 419:     } else {
 420:         memcpy(dic + i * sizeof(Digest), (void *)md5, sizeof(Digest));
 421:         DictionaryCounter++;
 422:         return TRUE;
 423:     }
 424: }

次に、ユーザー関数 addDictionary を使ってメモリ上のブラックリスト辞書にハッシュ値を追加する。
このとき、BACK_WORDS語だけ遡って重複チェックし、同じハッシュ値を見つけたら追加しないようにした。本当は銭湯に戻って重複チェックをしたかったのだが、Wikipedia見出しのように1千万語超になると重複チェックがボトルネックとなり、ブラックリスト辞書の追加に1日以上かかることから、このような処理にした。

解説:指定したメモリの内容を辞書ファイルに書き込む

passwordStrength.cpp

 362: /**
 363:  * 指定したメモリの内容を辞書ファイルに書き込む.
 364:  * @param   unsigned char *dic  書き込むメモリ
 365:  * @param   size_t         cnt  見出し語の数
 366:  * @return  bool TRUE:書き込み成功/FALSE:失敗
 367: */
 368: bool writeDictionary(unsigned char *dic, size_t cnt) {
 369:     const char *outfname = getDictionaryName();
 370:     FILE *outfp;
 371:     outfp = fopen(outfname, "wb");
 372:     for (size_t i = 0i < cnti++) {
 373:         fwrite(dic + i * sizeof(Digest), sizeof(Digest), 1, outfp);
 374:     }
 375:     fclose(outfp);
 376: 
 377:     return TRUE;
 378: }

ユーザー関数 writeDictionary は、メモリ上のブラックリスト辞書をファイルに書き込む。

解説:ブラックリスト・ファイルを読み込んで辞書ファイルに追加

passwordStrength.cpp

 426: /**
 427:  * ブラックリスト・ファイルを読み込んで,辞書ファイルに追加する.
 428:  * @param   const char* infname  パスワード・ファイル名
 429:  * @param   unsigned    column   読み込むカラム番号
 430:  * @return  size_t 読み込んだ見出語の数
 431: */
 432: size_t readKeywords2Dictionary(const char* infname, unsigned column) {
 433:     // カーソルを砂時計に
 434:     HCURSOR cur = SetCursor(LoadCursor(NULL, IDC_WAIT));
 435: 
 436:     // 辞書読み込み用のメモリを確保する。
 437:     unsigned char *dic;
 438:     dic = (unsigned char *)malloc((MAX_WORDS + 1* sizeof(Digest));
 439:     memset(dic, 0, (MAX_WORDS + 1* sizeof(Digest));
 440: 
 441:     // 辞書ファイルをメモリに読み込む.
 442:     if (readDictionary(dic) == FALSE) {
 443:         return FALSE;
 444:     }
 445: 
 446:     // ブラックリスト・ファイルを読み込みオープンする.
 447:     static char s1[SIZE_BUFF + 1];
 448:     static char s2[SIZE_BUFF + 1];
 449:     FILE *infp;
 450:     infp = fopen(infname, "rb");
 451:     if (infp == NULL) {
 452:         ErrorMessage = (string)infname + " の読み込みに失敗しました";
 453:         fclose(infp);
 454:         return FALSE;
 455:     }
 456: 
 457:     // パスワードを1つずつ読み込み,ハッシュ値に変換して辞書ファイルへ追加する.
 458:     size_t cnt  = 0;
 459:     size_t cnt2 = 0;
 460:     size_t len  = 0;
 461:     while (cnt2 < MAX_WORDS) {
 462:         if (feof(infp))     break;
 463:         if (fgets(s1, SIZE_BUFF, infp) == NULL)     break;
 464:         len = strlen(s1);
 465:         int k = 0;
 466:         unsigned col = 1;   // カラム番号
 467:         for (size_t j = 0j < lenj++) {
 468:             // カラム区切り文字を見つけたら,カラム番号を1つ増やす.
 469:             // 読み込むカラム番号を超えたら読み込みを打ち切る.
 470:             if ((s1[j] == '\t'|| (s1[j] == ' '|| (s1[j] == ',')) {
 471:                 col++;
 472:                 if (col > column) {
 473:                     break;
 474:                 }
 475:                 k = 0;
 476:             // 英数記号を取り込む.
 477:             } else if (col == column) {
 478:                 if ((s1[j>'!'&& (s1[j<'~')) {
 479:                     // 小文字に統一する.
 480:                     s2[k] = tolower(s1[j]);
 481:                     k++;
 482:                 }
 483:             }
 484:         }
 485:         s2[k] = '\0';
 486: 
 487:         // 最小長より長ければ辞書に登録する.
 488:         static Digest dd;
 489:         if (k >PASSWORD_MINIMUM_LENGTH) {
 490:             // MD5ハッシュ値をddに代入する.
 491:             MD5((const unsigned char *)s2, k, (unsigned char *)&dd);
 492: 
 493: //          cout << s2 << endl;
 494: //          printDictionary((Digest *)dd);
 495: //          cout << endl;
 496: 
 497:             // 読み込みカウンタ
 498:             if (cnt % 10000 == 0) {
 499:                 cout << cnt << "=>" << cnt2 << '\r';
 500:             }
 501:             if (addDictionary(dic, &dd) == TRUE) {
 502:                 cnt2++;
 503:             }
 504:             cnt++;
 505:         }
 506:     }
 507:     fclose(infp);
 508: 
 509:     // 辞書ファイルへ書き込む.
 510:     writeDictionary(dic, DictionaryCounter);
 511:     cout << "\nDictionaryCounter = " << DictionaryCounter << endl;
 512:     // メモリを解放する.
 513:     free(dic);
 514: 
 515:     // カーソルを元に戻す
 516:     SetCursor(cur);
 517: 
 518:     return cnt2;
 519: }

ユーザー関数 readKeywords2Dictionary は、これまでの関数を使い、ブラックリスト・ファイルを読み込んでブラックリスト辞書に追加する。
ブラックリスト・ファイルはテキストファイルで、1行に1パスワードが記載されているものとする。タブ、空白、カンマでカラムが区切られていてもよく、その場合は、columnにパスワードがあるカラム番号(左端の先頭が1)を指定してやる。

解説:辞書ファイルに登録されているパスワード数

passwordStrength.cpp

 521: /**
 522:  * 辞書ファイルに登録されているパスワード数を求める.
 523:  * パスワード数は"1234","約10万"のような文字列で返す.
 524:  * @param   なし
 525:  * @return  string パスワード数
 526: */
 527: string countDictionary(void) {
 528:     const char *infname = getDictionaryName();
 529:     double fsize = filesystem::file_size((string)infname) / sizeof(Digest);
 530:     char buff[SIZE_BUFF + 1];
 531: 
 532:     if (fsize < 10000) {
 533:         snprintf(buff, SIZE_BUFF, " (登録語数:%.0f)", fsize);
 534:     } else if (fsize < 100000000) {
 535:         snprintf(buff, SIZE_BUFF, " (登録語数:約%.0f万)", fsize / 10000);
 536:     } else if (fsize < 1000000000000) {
 537:         snprintf(buff, SIZE_BUFF, " (登録語数:約%.0f億)", fsize / 100000000);
 538:     } else {
 539:         snprintf(buff, SIZE_BUFF, " (登録語数:1兆以上)");
 540:     }
 541: 
 542:     return buff;
 543: }

ユーザー関数 countDictionary は、ブラックリスト辞書にあるパスワード数を求める。
ブラックリスト辞書は固定長バイナリファイルなので、ハッシュ値の長さ DIGEST_LENGTH で除算すれば、登録語数を得られる。
読みやすいように、語数に応じて日本語に変換した文字列を戻す。

同梱のブラックリスト辞書について

同梱しているサンプルのブラックリスト辞書は、以下のブラック・リストを統合したもので、約61万語が登録されている。
その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header