概要
ここではlibcryptの使い方を例と共に紹介します。
crypt
cryptとは、文字列を暗号化するための関数です。たとえば、/etc/passwd(現実には/etc/shadowなことが多いが)のパスワードフィールドを暗号化するために使われています。なお、この暗号化は一方向暗号化なため、元には戻せません。元に戻したければ、他の双方向暗号化のライブラリを使いましょう。
簡単な使い方
文字列を簡単に暗号化するには、
char salt[2]; char *str = "angouka mojiretsu"; /* 暗号化する文字列 */ ... salt[0] = "1"; salt[1] = "2"; crypt(str,salt);
のように行います。saltというのは暗号化する時に使う「調味料」ですが、これは暗号化した結果から元の文字列の逆写像が作れなくするためのものです。
と言うのも、いかにcryptが一方向暗号化の関数だとは言え、何の工夫もなければあらかじめ文字列と暗号化された文字列の対応表を作れば、わりと簡単に元の文字列が想像出来てしまいます。なぜなら、同じ文字列は同じ暗号化文字列に写像されてしまうからです。わかりやすい例で言えば、パスワードのフィールドを見た時に自分と同じ暗号化パスワードなら、自分と同じパスワードだと想像出来てしまうわけです。
そこで、元の文字列とは関係のない情報を使って、暗号化を「調理」してやります。単純な暗号化なら、saltの先頭の2文字を使うことにより、暗号化文字列を同じ元の文字列に対して、4096種類作ることが出来ます。
libcryptを使ったプログラムをリンクする時には、必ず-lcryptを指定しましょう。libcの中には入っていないので。
文字列の比較
文字列が同じかどうかを比較すには、双方の文字列を暗号化してから比較してやります。たいていは片方は既に暗号化済みなので、比較したい文字列の方を暗号化して比較します。
ここで問題になるのは、saltを使っているために、同じ文字列の暗号化文字列が必ずしも一致するとは限らないということです。暗号化文字列の比較は、saltが同じ場合にのみ有効なのです。そうなると、暗号化文字列がどんなsaltによって暗号化されたか知る必要があります。
ところが良くしたもので、暗号化文字列の先頭の2文字は、saltで指定した文字が平文でそのまま入っています。つまり、暗号化文字列の先頭の2文字を取り出して、これをsaltとして使って比較したい文字列を暗号化してから、比較すれば良いわけです。
char salt[2];
char *pass = "???"; /* 暗号化された文字列 */
char *str = "???"; /* 比較したい文字列 */
...
salt[0] = pass[0];
salt[1] = pass[1];
if ( !strcmp(pass,crypt(str,salt)) ) {
/* 一致した */
となります。
ところが、cryptではsaltは先頭の2文字しか見ませんから、この例のように一々 saltを別の文字列にしなくても、
char *pass = "???"; /* 暗号化された文字列 */
char *str = "???"; /* 比較したい文字列 */
...
if ( !strcmp(pass,crypt(str,pass)) ) {
/* 一致した */
としてやれば十分です。
計算結果は必ず13文字になります。
saltの指定
saltには[a-zA-Z0-9./]の文字を使います。これにマッチする任意の文字を2つ指定すれば良いわけです。具体的に何を選ぶかは、任意です。ですから、固定で"AA"のようなものを指定しても、動作的には問題はありません。しかし、これではsaltを使う意味があまりありませんので、たいていは再現性の著しく低い何かを種にして生成します。たとえば、実行した時間とプロセスIDを使ったりということです。あまり凝ってもしょうがないので、適当にやって構いません。ただ、再現性の低いものを使うということは忘れないで下さい。
cryptのアルゴリスム
cryptでは、DES(Data Encryption Standard)というアルゴリズムが使われています。具体的には、全てがヌル文字(つまり、 )の文字列を、暗号化する文字列の各々下7bitを8文字分集めて56bitの数値を作ったものを鍵として演算して、13文字の暗号化文字列を生成しています(先頭2文字はsaltそのもの)。つまり、暗号化文字列は2**56種類あるということになります。
つまり、根性出して2**56回調べれば、いかに一方向暗号化だとは言え、元の文字列を求めることが出来ないわけではないということです。
2**56種類ということは、7.2E16ということです。これは今時のコンピュータにとっては極端に大きな数字ではないと言えます。仮に1μ秒(10E-6)に1回の処理が出来るとするなら、7.2E10秒あれば全件調べられます。これは約2300年ということで非常に長い時間のように思われますが、この計算は完全に並列化出来ますから、コンピュータが100台あれば23年で済みますし、コンピュータの速度は3年 くらいで10倍くらい速くなりますから、最初の3年我慢すれば、その次の2年で解読出来てしまいます。つまり、現代のコンピュータにとっては、そう大きな空間ではないと言えます。現実にRSAが主催したDESを解読するコンテストでは、24時間以内に間に解けてしまいました。
MD5による暗号化
というわけで、cryptに使われているDESというアルゴリズムは、それ程安全ではないということが知られています。そこで最近のcryptは、MD5というアルゴリズムも使えるようになっています。
MD5を使うには、saltの文字列の先頭を"$1$"で始めます。"$1$"の後の8文字がsaltとして使われます。計算結果は、33文字になります。先頭の11文字が"$1$"とsaltです。
これをうっかり考えると、「偶然にもDESの時にsaltの先頭が"$1$"になっていたら」と心配する人もあるかも知れませんが、saltに指定出来る文字は、[a-zA-Z0-9./]ですから、$は含まれないはずです。ですから「偶然にも」ということはないはず です。
これら以外のことについては、DESの場合と同じです。また、比較の時には、
char *pass = "???"; /* 暗号化された文字列 */
char *str = "???"; /* 比較したい文字列 */
...
if ( !strcmp(pass,crypt(str,pass)) ) {
/* 一致した */
というようにしてやれば、DESの時もMD5の時も適当にcryptが処理してくれます から、そのままで使えます。