hnwの日記

日本語のパスワードジェネレータを作ってみた

Webサービスを使っていると、たまに「秘密の質問」の設定を求められることがあります。

こういう場合、個人的にはランダム文字列を登録したいと思うのですが、次のようにマルチバイト文字しか登録できないことが多い気がします。

普通のパスワードジェネレータではマルチバイト文字のパスワード生成ができないので、このような用途には使えません。そこで、ランダムなマルチバイト文字列を生成するサービス「 秘密の答えジェネレータ」を作ってみました。

自分でも実用しており、既に5サービスに設定しましたが、非常に便利だと感じます。

本稿ではこのサービスの技術面の詳細について紹介します。

「秘密の答えジェネレータ」の構成要素

「秘密の答えジェネレータ」はHTML+JavaScriptだけで実現されており、GitHub Pagesでホストしています。また、独自ドメインDNSおよびSSL化はCloudFlareで行っています。

このような構成にすると月額コスト0円で運用できる点、また万一GitHub Pagesが使えなくなったようなときに移転先がいくらでも見つかる*1点が良いですね。

ブラウザ上でパスワード生成することの是非

パスワード類は自分だけが知っているのが大前提ですから、リモートサーバ上で生成された文字列はパスワードとしては不適切です。その意味で、パスワードジェネレータをサーバサイド実装するのは良いアイデアとは言えません。

前述したとおり、本サービスはHTML+JavaScriptだけで実現されています。言い換えると、ランダム文字列の生成処理は全てローカルマシンのWebブラウザ上で実行されます。つまり、技術的には安全と言えるはずです。

ただ、利用者が一見してサーバサイド実装かクライアントサイド実装かわからない、というのは問題かもしれません。

乱数生成について

JavaScript上で乱数生成する場合Math.random()を使う事例が多いかと思いますが、この関数は多くのブラウザ上で暗号論的擬似乱数生成器(CSPRNG)として実装されておらず、特にパスワード生成には向かないようです*2

一方、多くのブラウザ上で動くCSPRNGとしてwindow.crypto.getRandomValues()という関数があります。私の手元で試したところ、ChromeSafari、Edge、Firefoxの全てで動作しました。PCブラウザだけでなく、スマートフォン上のブラウザでも実装されているようです。

Operaだけはwindow.crypto.getRandomValues()が実装されていないようですが、OperaではMath.random()がCSPRNGになっているとのことです*3。つまり、window.crypto.getRandomValues()が存在しなかったときだけMath.random()にフォールバックすればブラウザ上でもセキュアに乱数生成ができると言えそうです*4

ちなみに、本実装ではwindow.crypto.getRandomValues()から取り出したUInt32の値の剰余を取ることでN文字から1文字を取り出す処理を実装しているのですが、偏りが出てしまうので厳密には良くありません。具体的には、ひらがなのランダム文字列を生成する際に「ぁ」が「ん」より0.0000000233%ほど出現しやすくなっています。将来的に修正するかもしれませんが、現時点ではそのような問題があると認識した上でご利用ください。

ES6実装について

私は普段JavaScriptをほとんど書かないのですが、そろそろ生のES6で書いても許される空気を感じたため、今回のサービスでは実験的に大半の処理をES6で書いてみました。

そのためIE11で動かなくなってしまいましたが、それ以外であればPC・スマートフォンとも大抵のブラウザで動作します。

私が使った機能はarrow functionとclassくらいではあるのですが、トランスパイラなしでES6を書ける時代はすぐそこかな、という印象を持ちました。

秘密の質問は常に悪なのか?

さて、今回秘密の質問に対して嘘の回答をすることをオススメしているわけですが、そもそも秘密の質問というやり方自体にセキュリティ上の問題があるのでしょうか?

個人的には秘密の質問が常に悪いとは思いません。重要な操作の際のみ要求するような、第二パスワード的な位置づけであれば十分意味があるように思います。一方で、秘密の質問に正解するだけでパスワード変更ができるようなサービス設計は非常に問題があると感じます。

また、秘密の質問が複数のサイトで重複しやすい点(母親の旧姓や好きな食べ物など)、また平文で記録されることが多い点、この2つはかなり致命的な問題であると感じます。つまり、あるサービスでユーザ情報の漏洩が起きた場合に、他サービスの秘密の質問の答えまでバレてしまうリスクが高いわけです。

上記のような理由から、秘密の質問の答えは毎回異なるランダム文字列にすべきだと私は考えています。

生成した文字列をどう保管するか

このサービスで生成した文字列をサービス登録後に破棄してしまうことはオススメしません。破棄するのでなく、パスワードリマインダーのメモ欄などに記録しておいた方が後々面倒が無いように思います。

秘密の質問の答えを平文で保存しておくのが不安な場合は、正しい回答と自動生成文字列を組み合わせて使うこともできます。たとえば「母親の旧姓は?」に対して「高橋ゃたぞゅもぽねん」などと入力し、後ろの8文字だけを平文で記録しておけば多少は安心度が増すかもしれません。

参考URL

まとめ

  • 「秘密の質問」向けのパスワードジェネレータを作りました
    • 既存のパスワード生成ツールではマルチバイト文字に対応していないため
  • 半分シャレのつもりでしたが、案外実用的です
    • 入力内容を他人に見られた場合のショルダーハック対策にもなるように思います(↓入力例)

*1:パッと思いついた中ではGAEかNetlifyが良さそうです

*2:Secure random values (in Node.js)

*3:cryptanalysis - Are there any Javascript CSPRNGs? - Cryptography Stack Exchange

*4:IE10以前ではセキュアじゃないと思いますが、今回はそもそも動作しないので無視しています