hnwの日記

パスワードをプレーンテキストで保存してはいけないという話とその解決策

プログラムから使うパスワードをどう保存するか

外部サービスにアクセスするプログラムを書く場合、そのサービスの認証方式は何か、認証鍵をどう保存するか、というのはシステム全体のセキュリティを考える上で重要な話題です。

昨今のWebサービスであれば、クライアントごとに権限を絞ったアクセストークンを発行し、万一トークンが漏れた場合には漏れたトークンのみを無効化するような仕組みが提供されていたりします。こうした仕組みがあれば鍵の取り回しについて悩む必要はないでしょう。

一方、パスワード認証のサービスをプログラムから利用する場合、そのパスワードをどう管理すればよいのでしょうか。例えば銀行のネットバンキングシステムに自動ログインするプログラムを書く場合に、プログラムが利用するID・パスワードを平文で保存することに問題はないのでしょうか。

この問題についての議論を私はほとんど見たことがありませんが、関係しそうな文章をOWASPで見つけました。

この文章によれば、「パスワードを平文のプレーンテキストで保存してはいけない、BASE64のような可逆なエンコーディングもダメだ」とのことです。明示的には書いていないのですが、可能ならパスワードは保存しない方が良い、保存するなら暗号化すべき、ということのようです。

パスワード平文保存のリスクを考える

それにしても、パスワードが暗号化されていると何がどれほど安全になるのでしょうか。

仮に、すべてのローカルファイルを閲覧可能な脆弱性があったとすると、パスワードが暗号化されていたとしても大した意味はありません。というのも、プログラムから暗号文を復号できるということは共通鍵もシステムのどこかに保存されているはずですから、攻撃者も共通鍵を使って平文を入手できてしまうからです。

となると、OWASPの指摘はローカルファイルのうち一部のみが漏洩したような場合への対策だと考えられます。この種の漏洩の典型例はショルダーハック(盗み見)ではないでしょうか。

PASSWORD = foobar # 怖い
PASSWORD = Zm9vYmFyCg== # BASE64。依然怖い

上記のように平文パスワードをファイルに保存していて、このファイルを編集中に誰かが後ろを通りかかったり、運悪く写真を撮られてしまったような場合を考えると、システムに脆弱性がなくてもパスワードが漏洩する可能性は十分にあります。

PASSWORD = sj3OFgKZbHCpEaVB1zHz0Pd3amUcTIvDOoDK9Mdk6PlT6A== # 少し安全

このようにパスワードを暗号化しておけば、暗号文と共通鍵の両方を盗み見られない限りパスワードは漏洩しません。このように考えるとOWASPの指摘は十分意味があるように思います。

なんちゃって暗号化ライブラリを作った

私は銀行に自動ログインするプログラムを書いており、当初はパスワードを平文で保存していたのですが、上記のように考えてパスワード暗号化ライブラリを作成しました。

これはNode.js用ライブラリで、ランダム生成したパスワードを元にCBCモードつきの共通鍵暗号(デフォルトはAES)で暗号化・復号を行います。npmに公開してありますのでコマンド一発で試せます。

$ npm install @hnw/easyaes

使い方としては、まず最初にパスワードを生成します(手動で作ってもいいです)。

$ $(npm bin)/easyaes --keygen > $HOME/.easyaes

次に、暗号化したい文字列を標準入力経由で入力して暗号文を得ます。

$ $(npm bin)/easyaes --encrypt
foobar #標準入力から入力した平文。実際にはエコーバックされません
sj3OFgKZbHCpEaVB1zHz0Pd3amUcTIvDOoDK9Mdk6PlT6A== #出力された暗号文

この暗号文は次のようにJavaScriptから復号できます。

const EasyAes=require("@hnw/easyaes");
cipher = new EasyAes();
console.log(cipher.decrypt('sj3OFgKZbHCpEaVB1zHz0Pd3amUcTIvDOoDK9Mdk6PlT6A==')); // foobar

このように暗号化した値を設定ファイルで使ったりコマンドラインオプションから与えたりして、これをアプリケーション内で復号すればショルダーハックに対してセキュアになるわけです。

これはセキュリティの観点では保険的対策でしかないのですが、精神的な安心度はかなり高まるように感じています。というのも、一部銀行ではいまだにパスワード長が8文字までに制限されているので、平文で保存するとショルダーハックのリスクが非常に高いのです。

もっとマトモなソリューションもある

私の場合はプライベート用途かつ管理者が私だけの環境だったので上記の実装で必要十分だと考えていますが、もっとシリアスな状況ではもっと真面目に鍵を管理すべきです。

たとえばお仕事でセキュアに鍵を管理する目的であればAWS KMSとかHashiCorp Vaultなどを使うのが良いでしょう。これらのソリューションはアクセスコントロールを細かく制御できたりログが取れたりするのが良い点だと思います。

また、マシン起動直後だけはパスワード入力を要求して、以降は入力されたパスワードをメモリ上に暗号化して保存しておくなどの選択肢もあるでしょう。

まとめ

パスワードを平文保存するよりはマシな「なんちゃって暗号化」ライブラリを作りました。ショルダーハック対策くらいにしかなりませんが、短いパスワードや暗証番号をテキストファイルに保存するような場合は特に有用だと感じています。

念のため補足してきますと、銀行パスワードのような重要な情報を扱う場合に一番大切なのはマシン自体のセキュリティレベルを高くすることです。例えば、私ならグローバルIPアドレスを持っているマシンや共用マシンではパスワードのローカル保存は避けます1。本稿で紹介したライブラリは十分安全な環境で更に安心を得るためのものであり、その前提がないと無意味だという点にご注意ください。


  1. 個人的には、私自身が管理している宅内NAT環境であれば十分セキュアだと考えていますが、何をもってセキュアと考えるかの線引きは難しいところです。ITリテラシーが高いつもりの人でもマルウェアをインストールしてしまう可能性はあるわけで、どんな環境であろうと銀行パスワードレベルの重要情報はストレージに保存しない、という考え方もあると思います。