IBM 現場SEのITな日々

IBM現場SEが日々駆使している技術、現場SEの経験・知見を綴るブログです

(kin) AIXにおけるパスワードハッシュの作成方法

 AIXで色々なパスワードのハッシュの作成方法について書こうかと思う。単純に書くのであればコマンド1つ書いとけばいいのだが、まぁそれだと記事にならないので、その前提なども少々。
 まずはパスワードについての基本から。
 パスワードというものは基本的にシステム上に平文で保管されることは少ない、今となってはほぼないといってもよい。流出に対しての耐性を高めるため。パスワードは基本的にシステム内にハッシュ値として保管される。よく暗号化パスワードなどと表現されているが、基本パスワードとハッシュ値は1対1で且つ不可逆である。すなわちハッシュ値からパスワードを復号、もしくは演算することはできない。よって、パスワードハッシュが流出した場合、いわゆるプルートフォース(総当たり)によるパスワード解析にかけられることになる。そのため、パスワードのハッシュアルゴリズムはこの演算の際に如何に多くのCPUを使わせるかが重要となってくる。大量の演算により結果がでるまでの時間を非現実的にまで引き延ばすことでパスワード解析を事実上不可能にするのである。
 そのため、パスワードハッシュのアルゴリズムはCPUの進化と共に複雑にならざるをえない。当初Crype(3DES)しかなかったアルゴリズムMD5になりSHA2になっているのも、CPUの進化やレインボーテーブルなどの解析方法の進化により単純なアルゴリズムではパスワード解析が現実的になってしまったためである。現在ではSHA1までは既に危険とされSHA512もしくは精々がSHA256辺りまでが当たり前となっている。
 Linuxでは大分前からデフォルトがSHA512(type6)にかわっており、少し前まではツール類が追い付いておらずハッシュ計算に苦労したが、今では普通にcrypt関数でサポートしている。しかし、AIXでは何故か未だにデフォルトがCrypt(3DES)。非常に不思議だがMWとの関連などで仕方ないのだろうか? ちなみにWindowsも長い間LMハッシュが生きていたし、いまだNTハッシュ(MD4)が生きている辺り、独自実装の世界はこの辺りの進化が遅いような気がする。
 MW側での対応が遅いのは将にハッシュアルゴリズムの問題のように思う。表題のパスワードハッシュの作成方法とも絡むのだが、ユーザーの認証処理などを行う場合、UNIX系では割と入力されたパスワードを自分でハッシュ化するような実装が多かったように思う。今では大部分がMW経由だったりAPI経由だったりするため、ユーザーとパスワードをAPIなどに渡してOK/NGを判断しているのが大半かと思うが、そうではなく、入力されたパスワードをハッシュ化して、システムに保管されているパスワードハッシュと比較するのである。当時はその実装方法によるメリットは色々あったわけだが、ハッシュアルゴリズムが入れ替わる現在ではほぼ致命的である。アプリケーションが自身でハッシュアルゴリズムに対応する必要があるため、新しいアルゴリズムには中々対応できない。しかも独自実装。MW側の対応が中々進まないのも仕方ない気にはなる。

 ここまで基本的な話を書いてきたが、この辺りからが本番。
 まずは、パスワードハッシュの独自実装とは何のことか? これまで書いている通り、基本的なアルゴリズムは共通といってよい。SHA2にしろMD5にしろ公開されており同じものである。違うのはパスワードハッシュのフォーマットである。パスワードハッシュには必須の情報が幾つかある。ハッシュのアルゴリズムが何かと、ハッシュされた値と、ソルトである。
 ソルトとは文字通り、各個人の好みにより味つけを変えるための塩のこと、すなわちハッシュ値にバリエーションを持たすための乱数である。前記のようにパスワードとハッシュ値は1対1、ソルトがないと同じパスワードは同じハッシュ値となる。片方のパスワードを知って、別のユーザーのパスワードを推測できるようでは困るのである。そのため、パスワードハッシュを生成する場合は、パスワードと乱数のソルトを元にしてハッシュ値を計算することにより、同一のパスワードでも別のハッシュ値とするようにする。
 パスワードハッシュのフォーマットは元々全UNIXで共通であった。アルゴリズムが1つしかなかったため、必ず頭2桁がソルトで、残りがハッシュ値の13桁となっていた。しかし、様々なアルゴリズムが存在する現在、そのアルゴリズムを表すキーワードを何にするか?どのような順番にするか?などがシステムによって違うのである・・・というかAIXだけ違う・・・。

 Linuxの場合、パスワードハッシュのフォーマットは以下のようになる。

   $type$ソルト$ハッシュ値

 「$」が区切り文字で、最初がアルゴリズム、次がソルト、最後にハッシュ値である。例えばSHA512の場合、以下のようになる。ちなにみHP-HXもSolarisも同様。


   $6$1234567890123456$YfUD.j5zIFtfV6VgikPof2dzCCCZwL2YDraBX4HXi.J7iNq24667epYUCZGxExqOmHTnPWybzfYaynT29vKXJ/

 Type6がSHA512を表し、Type5はSHA256、Type1がMD5を表す。分かり易いようにソルトは全て並びの数字にしている。
 ではAIXはというと以下である。

   {ssha512}06$1234567890123456$/lorQE8P98Iw0lzrzOubFgMqe/p0tcWeC99jbL2KcWnK5jKcXx0WPYqo2aILdeH9Nrwg8A6p/InBnWiqBfWW..

 フォーマットもハッシュ値も全く違う。しかもこのフォーマットはアルゴリズムによって違う。SSHAの場合は以下のようなフォーマットとなる。

   {アルゴリズム}ストレッチング回数$ソルト$ハッシュ値

 ストレッチング回数とはハッシュを繰り返す回数のことで、前記のように、よりCPUを消費させることを目的にした実装である。ハッシュした値を更にハッシュし、この数分演算を繰り返すことで同一のアルゴリズムで数倍のCPU消費を強制することができる。ちなみにsmd5の場合、ストレッチング回数の指定はなく、ソルトは8桁となる。
 何故こんなに違うのかというとLPA(Loadable Password Algorithm)のせいである。LPAとは上記のようなパスワードハッシュのモジュールを独立させることで、システムに対してのアルゴリズムの追加、変更を容易にしようというもので、sshaをサポートするモジュールとsmd5をサポートするモジュールは全く別なのである。そうはいっても標準化しろよ、といいたくはあるが・・・
 様々なアルゴリズムが存在し、更に増えていくであろう現在、こんなにバラエティにとんだアルゴリズムをアプリ側で独自にサポートするのはさすがに無理がある。私自身、システム管理者として振出したパスワードの確認などでパスワードハッシュを作りたいと思うことが割とあるのだが、どうやって作ったものか悩んでいた。Linuxではフォーマットを標準化してcryptでもサポートしているが、AIXでもサポートしないかと思っていたら、サポートされていた。しかし、中々に情報がない上に、間違っていたり、不安定だったり・・・未だ不明な部分も多いのだが、ちょっと情報公開しておこうかと思った所以である。
 crypt関数はAIXの提供するC言語のライブラリ関数である。非常に伝統的な関数であり、元々は引数にパスワードと2桁のソルトを指定すると13桁の3DESのハッシュ値を返してくれるものであった。それが、LPAのその他の様々なアルゴリズムをサポートするようになったのである。とはいえ、それだけでは余り嬉しくはない。プログラムを書くのはいいのだが、様々な環境やシステムを弄る立場上、どこでコンパイルしたらいい? という問題が常に付きまとう。もっとどこででも簡単にと思っていたら、あった。perlである。perlにはシステムのAPIそのままの関数が様々にある。cryptも伝統的な関数なのでperlでもサポートされ、以前はよく使っていた。恐らくはそのままシステムのAPIを呼び出しているだけと思われたので、ためしにC言語のcrypt関数と同様のソルトを指定してみたところ、サポートされていた。
 perlでcrytを使う場合の基本的な使い方は以下の通り。ほんの1行、スクリプトにするまでもない。
 
   # perl -e '$hash=crypt("password","salt"); print "$hash\n";'
   sa3tHJ3/KuYvI


 デフォルトの挙動では3DESの13桁のパスワードハッシュが生成される。
では、ssha512の場合はどうなるか?

   # perl -e '$hash=crypt("password","{ssha512}06\$1234567890123456\$"); print "$hash\n";'
   {ssha512}06$1234567890123456$/lorQE8P98Iw0lzrzOubFgMqe/p0tcWeC99jbL2KcWnK5jKcXx0WPYqo2aILdeH9Nrwg8A6p/InBnWiqBfWW..


 みごとにssha512のパスワードハッシュが生成された。上記で「$」の前に「\」があるのは「"」で囲っているためエスケープしてやる必要があるためである。
ちなみにここでソルトを省略した場合、乱数のソルトを自動で生成してくれる。

   # perl -e '$hash=crypt("password","{ssha512}06\$"); print "$hash\n";'
   {ssha512}06$sL.WSbJpW2xdD2G5$FrvXYETBde5oGMkSc9VWSNwmPaLkitpGUlYf5pcKRNmM3a8aDbCKk9dtf83qBs2GrKQzbUjytXXIODNWgy.s..


 ここで指定できるアルゴリズムは以下のファイルに指定されている。

   /etc/security/pwdalg.cfg

 ここまでは綺麗に動いているようにみえるが、何が不安定なのか?

 LPAの弊害なのか、元々のcryptの仕様の問題なのか、バグなのか、全てのフォーマットエラーを無視して伝統的なcryptとして動くのである。例えばストレッチング回数。サポートされるのは4-31回だが、4回と指定してみる。

   # perl -e '$hash=crypt("password","{ssha512}4\$01234567890123456\$"); print "$hash\n";'
   ZOb9qQRD8CKi6


 なぜか13桁のパスワードハッシュが返る。これだけ見ると全くLPAをサポートしているようには見えない。しかし「4」ではなく「04」と指定すると。

   # perl -e '$hash=crypt("password","{ssha512}04\$01234567890123456\$"); print "$hash\n";'   {ssha512}04$01234567890123456$3OUe2I2xLl/nokoAYMBZG.Pprz3b/NZEVk21.mdzRyIOfCPWO56wXdFUFmKMidl.X5ZOsmqXhKOBjH6w8mmG..


 きれいにssha512のパスワードハッシュが返ってくる。また、cryptのマニュアルを見るとアルゴリズムに{SMD5}を指定できると記述されているので、{SMD5}を指定してみると。

   # perl -e '$hash=crypt("password","{SMD5}012345678\$"); print "$hash\n";'
   sa3tHJ3/KuYvI


 13桁のパスワードハッシュとなる。正しくは/etc/pwdalg.cfgの記述の通り、{smd5}。

   # perl -e '$hash=crypt("password","{smd5}012345678\$"); print "$hash\n";'
   {smd5}012345678$7nAzsz9W7rm48taUp/QYt.


 基本的にエラーは返してくれず、NULLにさえならない、間違っていると13桁になるだけ。この辺りの挙動は本当に困った。最初にマニュアルから入っていたため、純粋にサポートされていないようにしか見えなかった。しかも一般ユーザーで実行すると、全て13桁のパスワードハッシュに・・・

   $ perl -e '$hash=crypt("password","{ssha512}04\$01234567890123456\$"); print "$hash\n";'
   JQMuyS6H.AGMo


 AIXのcryptでLPAを利用する場合の注意点は以下の通り。

   ①rootで実行する
   ②指定できるアルゴリズムは/etc/security/pwdalg.cfgと正しく一致させる
   ③ストレッチング回数は2桁で指定する
   ④エラーは返してくれない。

 

 この辺りに気をつけて使えば非常に便利だと思う。
 ちなみにlinuxperlでも普通にcryptはサポートされているので、同様にソルトを指定することでType6などのパスワードハッシュを生成できる。但し、ソルトの自動生成はサポートしないようだ。