パスワードマスクの切り替え機能について考える

公開日:

徳丸浩さんのブログ記事 COOKPADの「伏せ字にせず入力」ボタンは素晴らしい(blog.tokumaru.org) について。

通常、パスワードの入力欄はコントロール形式(type属性)が "password" で実装されており、ほとんどのブラウザは入力した値を ● や * などの文字に置換してレンダリングします。これはショルダーハッキング(覗き見)を防ぐためと言われていますが、一方で

  • ひらがなや漢字が入力できず(別の場所からコピペすれば一応入力できますが)、使える文字種が制限される
  • 入力している内容が分からず入力ミスしやすい

といった理由から、長い文字列や特殊な記号を織り交ぜるなど安全なパスワードを設定しにくいという問題があり、必ずしもマスクすることが最善というわけでもなさそうです。

徳丸さんも著書体系的に学ぶ 安全なWebアプリケーションの作り方(Amazon)の中で利用者は簡単な(危険な)パスワードを好むようになり、かえって安全性を阻害するリスクの方が大きいのではないかとセキュリティ面の懸念を示されていますが、ユーザビリティ界でもヤコブ・ニールセン博士が2009年にこんな記事を発表されています。

しかしながら、現状ブラウザは利用者がマスクの有無を選択できるようにはなっていませんし、スタイルシート等でページの制作者が指定することもできません。

では、徳丸さんの記事で紹介されたクックパッドのスマートフォンサイトはどうやって伏字の切り替え機能を実現しているかというと、https://cookpad.com/javascripts/password_visibility_button.js?1330049530 にあるようにJavaScriptでtype属性を password から text へ切り替えています。ただし、これをそのまま真似てPCサイトへ導入すると、次のような問題があります。

  • IE8以下はスクリプトの setAttribute() でtype属性を変更することができない
  • type="text" のまま送信すると、オートコンプリートに対応した環境では別フォームや第三者のサイトで入力内容が表示されてしまう可能性がある

IEの問題に対しては、type属性を変えるのではなく、新たに type="text" なinput要素を生成し、 type="password" の方は消してしまうことで同じような動作を再現することができます。livedoorのユーザー登録フォーム(member.livedoor.com)などはこの手法を使っていますね。

オートコンプリートのリスクについて、以下に詳しく説明します。

パスワード入力欄を type="text" にするリスク

FirefoxやGoogle Chrome、IE[1]はオートコンプリート機能を搭載していますが、これはフォームコントロールの名前(name属性)が同じ入力履歴を表示するようになっています。

たとえば、あるサイトAがパスワード入力欄を type="text" でマークアップしていたとします。

<form action="login.php" method="post">
  <p><label for="id">ID</label>
    <input type="text" name="input01" id="id" /></p>
  <p><label for="pwd">パスワード</label>
    <input type="text" name="input02" id="pwd" /></p>
  <p><button type="submit">ログイン</button></p>
</form>

ユーザーがこのフォームにIDとパスワードを入力して送信します。

図1:サイトAでIDとパスワードを入力した様子

一方、別のサイトBでは、name属性がサイトAのパスワードと同じな入力欄(name="input02")が存在しました。

<form action="inquiry.php" method="post">
  <p><label for="name">名前</label>
    <input type="text" name="input01" id="name" /></p>
  <p><label for="mail">メールアドレス</label>
    <input type="email" name="input02" id="mail" /></p>
  <p><label for="inquiry">問い合わせ内容</label>
    <textarea name="textarea01" cols="30" rows="5" id="inquiry"></textarea></p>
  <p><button type="submit">送信</button></p>
</form>

先ほどサイトAでフォーム送信を行ったユーザーが、ここに文字を入力する際、「↑」か「↓」キーを押したり、ダブルクリックしたり、たまたま一文字目がパスワードの最初の文字と一致していたりすると、入力履歴が表示されてしまいます。

図2:サイトBでメールアドレスの一文字目「'」を入力すると、サイトAで入力した履歴が表示された

後ろから覗かれていたり、あるいはプレゼンで画面をスクリーンに映していたりすると悲惨なことになってしまうかもしれませんね。

パスワードなど重要情報を type="passrowd" 以外で取り扱うことについて、脆弱性とまでは言えないと思いますが、好ましくない状況ではあります。

送信前に type="password" に戻してみる

とはいえ、ブラウザが機能を提供していない以上、type属性を変えない限りマスクの解除はできなさそうです。

そこで一時的にtype属性を変更し(実際はIE対応のためinput要素ごと置換)、送信時に "password" に戻せば、オートコンプリートに記録されなくなるのでは、と思い試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<title>ログイン</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
//<![CDATA[
jQuery(function($) {
  var showText = "伏字にせず入力";
  var hideText = "伏字で入力";

  $(':password').each(function() {
    var pwdCtrl = $(this); // パスワードの入力コントロール
    var textCtrl = $('<input/>').attr("name", pwdCtrl.attr("name")); // パスワード入力欄を形式のみtextにして複製

    /* コントロール形式を password / text の交互に切り替えるボタン */
    var typeConvertButton = $('<button type="button"/>').text(showText).bind("click", function() {
      if (pwdCtrl.parent().length > 0) {
        // textコントロールを有効にしてフォーカス、passwordコントロールは無効にする
        $(this).text(hideText);
        pwdCtrl.replaceWith(textCtrl); // textコントロールに置換
        textCtrl.val(pwdCtrl.val());
        textCtrl.focus();
      } else {
        // passwordコントロールを有効にしてフォーカス、textコントロールは無効にする
        $(this).text(showText);
        textCtrl.replaceWith(pwdCtrl); // passwordコントロールに置換
        pwdCtrl.val(textCtrl.val());
        pwdCtrl.focus();
      }
    });

    pwdCtrl.before(typeConvertButton); // コントロール切り替えボタンを挿入

    /* フォームを提出する際、変換したtextコントロールをpasswordに戻す */
    pwdCtrl.closest("form").bind("submit", function() {
      if (pwdCtrl.parent().length === 0) {
        textCtrl.replaceWith(pwdCtrl); // passwordコントロールに置換
        pwdCtrl.val(textCtrl.val());
      }
    });
  });
});
//]]>
</script>
</head>
<body>
<form action="login.php" method="post">
  <p><label for="id">ID</label>
    <input name="id" id="id" /></p>
  <p><label for="pwd">パスワード</label>
    <input type="password" name="pwd" id="pwd" /></p>
  <p><button type="submit">ログイン</button></p>
</form>
</body>
</html>
図3:「伏字にせず入力」ボタンを実装したフォーム

クックパッドのフォームと比較して、送信時に type="password" に戻す処理を追加したほか、こんなところを変えています。

  • 伏字の切り替えボタンはスクリプトで生成する (スクリプト無効環境ではボタンを表示する意味がないため)
  • 切り替えボタンは入力欄より前に配置 (読み上げ環境等を考えると、入力前にボタンの存在を示した方が良いと思う)
  • 切り替えボタンを押下したら、フォーカスを入力欄に合わせる (利用者がTabキーを押さなくても良い配慮だが、動きを予想できずかえって混乱するかもしれない)

このフォームを送信すると、たしかに type="password" には戻るのですが、Firefox13ではオートコンプリートに記録されてしまいました。submitのイベントハンドラが呼ばれる前に記録処理が行われてしまうのでしょうか。

autocomplete="off"を設定する

HTML5では、form要素とinput要素にautocomplete属性(www.w3.org)が追加されており、この値を"off"にするとオートコンプリートを無効にできます。これはブラウザの独自属性として古くから実装されており、IEは5で導入されたようです。

というわけで、先のスクリプトを改修します。スクリプトで生成するinput要素に autocomplete="off" を追加し、フォーム提出時に "password" に戻す処理は削除します。

    var textCtrl = $('<input autocomplete="off"/>').attr("name", pwdCtrl.attr("name")); // パスワード入力欄を形式のみtextにして複製

まとめ

  • passwordコントロールを他形式に変更する際は autocomplete="off" を付ける
  • type属性の変更はIE8以下ではできないので、新たに type="text" なコントロールを生成し、置換する

パスワードを必ずしもマスクする必要はない、という方針自体には私も賛成です。ただし、JavaScriptでもって「標準でない」機能を実装することになるため、アクセシビリティやユーザビリティには充分配慮する必要があると思います。

また、切り替え機能はボタンとチェックボックスなどどれが良いか、入力欄の前後どちらに配置するか、押下後のフォーカスはどこにするか、などは議論の余地がありそうですね。

  • [1]IEはデフォルトでは有効になっていないので、「インターネットオプション」の「コンテンツ」タブの「オートコンプリート」の設定で「フォーム」をチェックする必要があります。