公開日: 2019-08-21
更新日: 2020-05-27

zxcvbnを利用して、パスワードの推測しやすさを評価する

zxcvbn

ブラウザでパスワード生成とパスワード保存の機能拡張が使えるようになって、パスワード管理は便利になっている一方、 いまだに推測されやすいパスワードを使い続けるユーザも一定の割合でいて、パスワード流出の事件は定期的にTech系ニュースでたびたび報じられています。

一つのサービスでパスワードが流出しても、他のサービスでは安全が保たれるように、パスワードを使い回さないように推奨されている時点で、 すでに人間がパスワードを管理することは不可能と考えられます。

Dropbox謹製のzxcvbnというライブラリを利用して、ユーザの登録時にパスワードの評価を行い、推測しやすいパスワードについては登録を受け付けないやり方を紹介します。

zxcvbnのリポジトリはこちらです。 JavaScriptだけでなく、さまざまな言語にポーティングされているのがわかります。 npmbowergit cloneなどでインストールしましょう。

最初は登録/パスワードリセットのhtmlのソースコードです。 id="bar"には、パスワードの強度をバーで表示します。 id="reason"には、入力されたパスワードについてのフィードバックを表示します。

<div class="card-body">
  <form method="POST" action="https://tiny-services.com/register">
    <input type="hidden" name="_token" value="OAFoGRw9MPsG0szU6u8xtHBXTL0PG5LVqQdp3kWz">
    <div class="form-group">
      <label for="name" class="form-label">Name</label>
      <input id="name" type="text" class="form-input " name="name" value="" required autocomplete="name" autofocus>
    </div>

    <div class="form-group">
      <label for="email" class="form-label">E-Mail Address</label>
      <input id="email" type="email" class="form-input " name="email" value="" required autocomplete="email">
    </div>

    <div class="form-group">
      <label for="password" class="form-label">Password</label>
      <input id="password" type="password" class="form-input " name="password" required autocomplete="new-password">
    </div>

    <div class="form-group">
      <div class="pv2">
        <label for="bar" class="form-label">Password Strength</label>
        <div id="bar" style="height: 16px;">
        </div>
      </div>

      <div class="pv2">
        <div id="reason" style="height: 16px; line-height: 1.0;">
        </div>
      </div>
    </div>

    <div class="form-group">
      <label for="password-confirm" class="form-label">Confirm Password</label>
      <input id="password-confirm" type="password" class="form-input" name="password_confirmation" required autocomplete="new-password">
    </div>

    <div class="form-group">
      <div class="d-flex justify-end pv2">
        <button type="submit" id="submit" class="btn btn-primary">
          Register
        </button>
      </div>
    </div>
  </form>
</div>
            

次にスクリプトの読み込み部分です。 zxcvbn.jsとpassword_validation.jsの2つを読み込んでいます。

<script src="https://tiny-services.com/js/zxcvbn.js"></script>
<script src="https://tiny-services.com/js/password_validation.js"></script>
            

password_validation.jsは、最初に即時関数を使って、passwordとpassword_confirmationのkeyupイベントに対して、イベントリスナとして、validate()を設定します。 その後、一度、validate()を呼び出して実行します。

(function () {
  const password_input = document.getElementById('password');
  password_input.addEventListener('keyup', validate);

  const password_confirmation_input = document.getElementById('password-confirm');
  password_confirmation_input.addEventListener('keyup', validate);

  validate();
}());
            

validate()は、validate_password()を呼び出し、その結果をtoggle_confirmation_password()に渡します。 さらに、validate_passwords()を呼び出し、その結果をtoggle_submit_button()に渡します。

function validate()
{
  const status_password = validate_password();
  toggle_confirmation_password(status_password);

  const status_confirmation_password = validate_passwords();
  toggle_submit_button(status_confirmation_password);
}
            

validate_password()は、passwordの値を取得して、zxcvbnライブラリに渡して、その結果をeval_strength()関数に渡します。 scoreが4以上であればtrueを、そうでなければfalseを返します。

function validate_password()
{
  const element  = document.getElementById('password');
  const password = element.value;
  const result   = zxcvbn(password);

  eval_strength(result);

  if(result.score >= 4) {
    return true;
  }

  return false;
}
            

eval_strength()は、resultを受け取って、htmlのid=barとid=reasonを描画します。

function eval_strength(result)
{
  const score = result.score;
  const warning = result.feedback.warning;

  const bar = document.getElementById('bar');
  bar.innerHTML = '';

  if(result.score >= 4 && result.feedback.warning.length == 0) {
    reason.innerHTML = 'Your password is Strong enough.';
  }
  else {
    const reason = document.getElementById('reason');
    reason.innerHTML = ''+warning+'';
  }
}
            

validate_passwords()は、id="password"とid="password-confirm"の値を比較して、一致すればtrueを、しなければfalseを返します。

function validate_passwords()
{
  const password1 = document.getElementById('password').value;
  const password2 = document.getElementById('password-confirm').value;

  if(password1 == password2 && password1.length > 0) {
    return true;
  }

  return false;
}
            

toggle_confirmation_password()は、引数がtrueならpassword-confirmの属性からdisabledを削除し、確認用パスワードを入力可能にします。

function toggle_confirmation_password(status)
{
  const element = document.getElementById('password-confirm');
  if(status) {
    element.removeAttribute('disabled');
  }
  else {
    element.setAttribute('disabled', 'disabled');
  }

  return status;
}
            

toggle_submit_button()は、引数がtrueならsubmitの属性からdisabledを削除し、送信ボタンを押下可能にします。

function toggle_submit_button(status)
{
  const element = document.getElementById('submit');
  if(status) {
    element.removeAttribute('disabled');
  }
  else {
    element.setAttribute('disabled', 'disabled');
  }

  return status;
}
            

  1. 入力したパスワードが弱く、推測しやすい場合
  2. weak password
  3. 入力したパスワードが強く、推測しにくい場合
  4. strong password

zxcvbnを使って、入力されたパスワードを評価し、強度が足りない場合には、登録を受け付けないようにすることができました。 zxcvbnは辞書を含むため、minifyした後でも819.6KBと大きいです。 そのため、ユーザ登録処理のときだけ読み込むようにしましょう。