暗号プログラミング Part1

Last modified: 2006/11/18 12:49:56

はじめに

インターネットが普及し、様々な情報がやり取りされる現在のネットワーク状況において、ここ数年、特に注目されている技術に暗号があります。例えば、SSL(Secure Sockets Layer)はHTTPにて広く使われている暗号通信方式であり、私たちは普段、URLの先頭が「https」となっているWebサイトをよく見かけます。また、かの有名なP2P(Peer to Peer)ソフトであるWinnyも、Crack対策のため、通信内容を暗号化して送受信していました。

今後、ネットワークと暗号はますます身近になり、またその研究も進むことでしょう。しかし、暗号はそもそもコンピュータやネットワークとはまったく別の学問であり、その歴史も深く、専門の学者によって日々研究が進められているものです。暗号に対する安全性や理論の証明、そしてネットワーク通信にどのように応用できるかなど、暗号と一言にいっても様々な分野があり、そのすべてを学ぶことは困難です。ましてや、プログラマやネットワークエンジニアといったコンピュータ業界の人間が、片手間に学習して身につけることができるような簡単なものでもありません。

しかし、プログラマは新たな暗号アルゴリズムを発見する必要はなく、あくまでも実装レベルでの知識を持っていればよいことも事実です。そして、暗号関連のライブラリも様々なところで作成配布されており、それらを利用して簡単に暗号を使うことが可能です。よって、今回は、暗号を利用したプログラミング方法を解説します。このテキストを読み進めていくための、暗号に関する詳細な知識は必要ありません。

ただ、このテキストは、あくまでもプログラマ向けに記述していますので、純粋な暗号分野の方から見れば、間違いや説明不測が多々あるかもしれません。しかし、その辺りはスルーしてもらえると助かります。書いてる私自身も、暗号理論自体はほとんど理解していない状態なので(^^;。

暗号の種類

コンピュータ業界でもっとも多く利用されている暗号方式は、大きく分けて2つあります。それは「共通鍵暗号」と「公開鍵暗号」です。研究レベルの話だと分かりませんが、少なくともコンピュータで使用する暗号は、この2つを知っていれば十分です。なので、とりあえずこの2つを覚えてください。

それで、よく暗号関連のテキストを読んでいて「分かりにくいなぁ」と思うので、ここでひとつはっきりさせておきます。「共通鍵暗号」は他にも「秘密鍵暗号」というような呼び方で呼ばれるらしいです。ただし、これらは同じで、要するに、「通信するそれぞれの相手が同じ鍵を持っている暗号方式」ということです。

「公開鍵暗号」は他に呼び方はないと思いますが、間違えやすいのが、公開鍵暗号の解説の中で「秘密鍵」という単語が出てくるところです。正直まぎらわしいです。「公開鍵暗号」の中で使用される「秘密鍵」という単語と、「秘密鍵暗号(共通鍵暗号)」とはまったく別物です。無関係です。とりあえずはこの2点を押さえておいてください。

ちなみに「公開鍵暗号」とは、暗号化するための鍵と、復号化するための鍵が違うという、まったくもって意味不明(というか、そんなこと可能なの?)な暗号化アルゴリズムなので、ちゃんとした知識を持って利用する必要があるようです。

では、この2つの暗号方式について、もう少し詳しく見ていくことにします。

共通鍵暗号

共通鍵暗号とは、通信を行う双方が共通の鍵を利用した暗号方式です。考え方はとても簡単です。通信するそれぞれの相手が同じ鍵を持っており、それを使って暗号化や復号化を行うわけです。コンピュータの世界では、すべては0と1で表現されますから、もちろん、平文も暗号文も鍵も0と1の羅列でしかないわけで、要するに「双方とも同じデータ(鍵)を持っていれば、暗号通信が可能ですよ」というだけの話です。

共通鍵暗号

ネットワーク上では、暗号文「CCCC」しか流れないため、鍵「aaaa」が他の人間に知られない限りは、平文が求められることはありません。仕組みとしては、特に難しいことはないでしょう。同じ鍵を持っているので、同じデータに復元できるという簡単なことです。

シンプルであり、共通鍵が「秘密」である限り、共通鍵暗号は安全です。しかし、共通鍵暗号を利用する場合、ひとつの相手に対してひとつの鍵を保持しておかなければなりません。つまり、複数のクライアントを相手にする場合、サーバ側はそのクライアントの数だけ共通鍵を用意する必要があります。同じ鍵を別々のクライアントに使うことはできませんから。というわけで、共通鍵暗号は鍵の管理が問題になります。ただ、とりあえず安全性は共通鍵が「秘密」である限り保証されます。

このような共通鍵暗号方式を使っている有名な暗号アルゴリズムにDES(Data Encryption Standard)やRC4(Rivest's Cipher 4)といったものがあります。

公開鍵暗号

共通鍵暗号と比べて、公開鍵暗号は少しややこしいです。というよりも、理解に苦しみます(^^;。共通鍵暗号とは、要するに、それぞれが同じ鍵を持っているわけですから、当然同じように暗号化、復号化ができることは普通に納得できます。

しかし、公開鍵暗号とは、暗号化と復号化を別々の鍵で行わなければなりません。つまり、「aaaa」という鍵で暗号化したデータは、同じ「aaaa」という鍵では復号化できないのです。では、何の鍵で復号化できるのかというと、それは、あらかじめ「aaaa」と対(つい)になって作成された「zzzz」という鍵です。

つまり、公開鍵暗号では、次のようなことが可能になります。

公開鍵暗号1

まずサーバが2つの対となる鍵を生成します。そして、その片方の鍵をクライアントへ送信します。クライアントはその鍵を受け取って、その鍵で平文を暗号化します。この時点で暗号文が生成されます。その暗号文をサーバへ送ります。

公開鍵暗号2

暗号文を受け取ったサーバは、その暗号文をあらかじめ対(つい)として作成していた鍵「zzzz」を使って復号化します。これによって、平文「BBBB」を得ることができます。

さて、ここで問題となるのが、ネットワークを流れる、鍵「aaaa」と暗号文「CCCC」の2つの情報から、平文を求めることができるのか? というところですが、答えはNoです。ただし、暗号化を行うことはできます。つまり、誰もがみんな、鍵「aaaa」を使って暗号化をすることはできますが、それから平文を復号化することはできないわけです。この、鍵「aaaa」のことを「公開鍵」、そして、それと対(つい)になって作成された鍵「zzzz」を「秘密鍵」と呼びます。公開鍵は暗号化のための鍵ですから、誰に教えても構いません。よって、ネットワーク上を流しても問題ありませんし、Webページで公開しても問題ありません。しかし、秘密鍵は復号化するための鍵ですから、これは自分以外誰にも知られてはならないわけです。

また、公開鍵暗号の特徴として、多くのクライアントを相手にしていても、鍵が1組(「公開鍵」と「秘密鍵」の2つ)でよいことが挙げられます。共通鍵暗号の場合は、クライアントの数だけ鍵を用意する必要がありましたが、公開鍵暗号の場合は、「公開鍵」と「秘密鍵」の1組だけでよいのです。つまり、すべてのクライアントに同じ公開鍵を渡し、それで暗号化されたデータを受信して、自分だけの秘密鍵で復号化することができます。ここは共通鍵暗号よりもすぐれたところでしょう。

ちなみに、「なぜ別々の鍵で暗号化や復号化が行えるのか」という問題に関しては、私も分かりません。暗号理論を勉強してください(^^;。どうやら素数や素因数分解などの数学的性質を利用しているという話ですが、まぁ分かりません(笑)。

このような公開鍵暗号方式を使っている有名な暗号アルゴリズムにRSA(Rivest Shamir Adleman「発案者3名の頭文字」)といったものがあります。

ハッシュ

暗号と関連した話題で「ハッシュ」というものがあります。ハッシュとは「あるデータ」を「ある計算」によって「ある値に変換させたデータ」のことです。文字にするととても分かりにくいですが、簡単に言えば、「あるデータ」を「AAAA(0x41414141)」として、「ある計算」を「1バイト単位に区切って加算する」とします。すると、これらから導かれるハッシュは「A + A + A + A = 0x41 * 4 = 0x0104」となります。つまり、「AAAA(0x41414141)を1バイト単位に区切って加算すると0x104になる」といういたって簡単なことです。 ただし、暗号とハッシュには、大きな違いがあります。それは、復号化できるか否かです。

暗号化されたデータは必ず何かしらの方法で平文を復元できなければなりません。それでなければ、そもそも暗号化の意味がありません。しかし、ハッシュは復号化できなくても良いのです。というか、むしろ復号化できてはいけません。しかし、復号化できなければ、それはただ、データを変換しただけの状態であり、何の意味もないように思えます。が、実はハッシュには十分な利用価値があります。

データを変換しただけのハッシュ値というものを、いったい何に使用するのかというと、一般的には「高速な検索」及び「データの検証」に使用されるようです。例えば、膨大な文書(仮に100ギガバイトとする)があり、それが正確なものかどうかを調べたい場合、最初の1バイトからひとつずつ、終端100ギガバイト目まで調べていくのは大変です。しかし、あらかじめこの膨大な文書のハッシュ値を計算しておけば、そのハッシュ値を調べるだけで、その文書が正確なものであるかどうかを評価することができます。MD5のハッシュ値は必ず16バイトなので、100ギガバイトを調べる必要はなく16バイトのみを評価するだけで、その文書が正確なものかどうかを確認できます。つまり、ハッシュ値とは「データの要約」と考えることができます。あくまでも「データの要約」なので、ハッシュ値から元のデータが復元できてはいけませんし、また、なるべく短いデータ列でなければいけません。そして、入力値が異なれば、当然ハッシュ値も(なるべく)異なるものになるアルゴリズムでなければならないのです。これがハッシュアルゴリズムの性質です。

現在、もっとも有名なハッシュアルゴリズムに、MD5(Message Digest 5)やSHA1(Secure Hash Algorithm 1)といったものがあります。

暗号通信

「共通鍵暗号」「公開鍵暗号」そして「ハッシュ」、プログラマが知っておくべきことは、このくらいで問題ありません。知識としても、このくらいの概念を理解しておけば大丈夫です。あとは、それっぽいライブラリを使ってそれっぽいプログラムを書いていけば、大体のことはつかめてくるはずです(ホントかよ^^;)。まぁ我々はプログラマですから、理論よりも実践、プログラムを書いてナンボです(笑)。というわけで、これらの知識をうまく利用して、現在の暗号通信の実装を考えていくことにします。

さて、実際にインターネット上で暗号通信を行う場合、「共通鍵暗号方式がよいか?」「公開鍵暗号方式がよいか?」で迷うところですが、現在もっとも一般的なものは、「最初の通信のみ公開鍵暗号方式を使い、以後の通信は共通鍵暗号方式を使う」というものです。

そもそも共通鍵暗号というのは、通信を行うそれぞれが同じ鍵を持っていなければなりません。しかし、そうなると、「そもそも、共通となる鍵をどうやって受け渡すのか?」という問題にぶつかります。例えば、田中さんと鈴木さんがインターネットを利用し、暗号通信でメールの送受信を行う場合、それぞれに一度も面識がなかったら、そもそも共通となる鍵を持つことができません。しかし、それでは、共通鍵での暗号通信が出来ません。

というわけで、どうにかして共通鍵を相手に送る必要があります。つまり、共通鍵暗号方式の最大の問題点である、「どうやって共通鍵をそれぞれが持っているという状態を作りだすか」というところに行き着くわけです。そこで、公開鍵暗号を使って、共通鍵暗号の「共通鍵」の受け渡しを行います。

暗号通信1

まずは送信側(この例では田中さん)が公開暗号の「公開鍵」と「秘密鍵」を作成します。そして、公開鍵を相手(この例では鈴木さん)へ送信します。次に、公開鍵を受け取った鈴木さんは、その鍵で、共通鍵暗号の「共通鍵」を暗号化します。つまり、共通鍵の受け渡しに公開鍵暗号方式を利用するのです。

暗号通信2

これで、田中さんと鈴木さんの双方に共通鍵「kkkk」が存在することになります。そして、これ以後、双方はこの共通鍵「kkkk」を使って暗号通信を行うことが可能となります。

暗号通信3

つまり、最初の通信時に公開鍵方式を利用し共通鍵の受け渡しを行い、以後、その受け渡しを行った共通鍵で通信を行うというわけです。これが、現在の一般的な暗号通信のようです。

さて、ここでひとつの疑問が出てきます。それは「なぜ全部の通信を公開鍵暗号方式で行わないのか?」ということです。共通鍵暗号方式の「鍵の受け渡しが困難」という欠点は分かりましたが、では、「全部の通信を公開鍵で行えばよいじゃないか?」という疑問が出てきます。確かにメールの場合はそれでもよいかもしれませんが、残念ながら公開鍵暗号方式にも欠点はあります。それは「処理速度が遅い」ということと「なりすましが可能である」という2点です。

まず、公開鍵暗号は、共通鍵暗号に比べて処理速度が数十倍も遅いのです。よって、大量の通信が発生する環境では、なるべく共通鍵暗号を利用した方がよいのです。また、公開鍵暗号はその性質上、なりすましが可能になります。公開鍵暗号方式は、暗号化を行うための公開鍵がその名の通り「公開されている」ため、それを受信した誰もが暗号化を行うことができます。よって、正規の通信相手に成り代わって、第三者が通信を行うことが可能です。公開鍵暗号は、誰もが暗号化を行えること前提で成り立っているため、このような問題が発生します。

実は、この公開鍵暗号のなりすまし問題防止のために「署名」という仕組みがあります。「署名」とは、送られてきたメッセージ(データ)が、本当に送信者本人が作ったものであるかどうかを識別する仕組みです。この署名が正しいものであれば、送られてきたデータは正式なものであると判断できます。ただ「署名」の説明は少々ややこしいので後回しにして、とりあえずは、これまで学んだ知識を使ってプログラミングを行うことにします。

一応タイトルも、暗号「プログラミング」なので(^^;。

OpenSSL

OpenSSLとは、SSL(Secure Socket Layer)とTLS(Transport Layer Security)を実装し、電子証明書の発行や管理、S/MIME関連のユーティリティなどを含んだ暗号化ライブラリである、ということらしいですが、要するに、これ使っておけば、とりあえず暗号関連はモウマンタイということです。なので素直に使っておきましょう。

ちなみに、OpenSSLはオープンソースプロジェクトで大変有難いのですが、本家のページに行くと、本当にソースコードだけで、バイナリがなかったりします。よって、自分でコンパイルする必要がありますが、そのためには、どうやらPerlとCコンパイラとASMコンパイラが必要なようです。Linux環境なら簡単ですが、Windows環境でこれらをそろえるのは少々骨が折れます。というわけで、GNUライセンスを最大限に利用して、Windows用のコンパイル済みバイナリ(DLLファイル、LIBファイル、ヘッダファイル)をアップロードしました。コンパイルがメンドクサイという方は使ってください。

./openssl.zip

もし「自分でコンパイルするよ」という方は、OpenSSLのreadme辺りにコンパイル方法が書いてありますので、それを参照してください。

共通鍵暗号プログラミング(RC4)

RC4(Rivest's Cipher 4)とは、RSA Security社のRon Rivest氏によって開発された共通鍵暗号方式のひとつで、ストリーム型と呼ばれる暗号化技術が採用されています。DESなどのような一定のブロック単位で暗号化を行うブロック暗号に対し、1ビット(もしくは1バイト)単位で暗号化を行うものをストリーム暗号と呼びます。

暗号の世界では、「ストリーム暗号」と「ブロック暗号」という言葉をよく耳にします。これらはいったい何かというと、ストリーム暗号とは、「1バイト(もしくは1ビット)の平文に対して、1バイト(もしくは1ビット)の暗号文」が出力されます。つまり、平文と暗号文のサイズは変わりません。そして、1バイト(もしくは1ビット)単位で暗号化されていきます。しかし、ブロック暗号は、平文を特定のサイズで区切って、その1区画ごとに暗号化処理を行うため、平文と暗号文のサイズが変化する場合があります。

例えば、平文のサイズが18バイトだったとして、これを8バイト単位で区切ると、8バイトのデータが2つと、2バイトのデータが1つになります。最初の2つの8バイト区画はそのまま処理すればよいですが、残りの2バイトは8バイトに満たないため暗号化処理ができません。よって、暗号アルゴリズム側が、残りの6バイトに何かしらのデータを付加(パディング)して8バイトとし、それを暗号化することになります。すると、結果的に暗号文は24バイトのデータとなります。

このように、8バイト単位で暗号化を行うブロック暗号方式だと、18バイト(17〜24バイトの範囲)の平文が24バイトに膨張することになります。このような暗号アルゴリズムをブロック暗号と呼びます(区切るバイト数は暗号アルゴリズムによって異なります)。ちなみに、当たり前ですが、復号化すると、24バイトの暗号文が18バイトの平文に復元できます。

つまり、ストリーム暗号は、1バイト(もしくは1ビット)ごとに暗号化処理がされていき、ブロック暗号は、特定のサイズごとに暗号化処理がされていくという具合です。まぁあえて言い換えるなら、ストリーム暗号は「1バイト(もしくは1ビット)単位で区切られたブロック暗号」とも言えるでしょう。

では、共通鍵暗号でストリーム暗号方式のRC4を使って、データの暗号化、復号化を行うサンプルプログラムを見てください。

-----  rc4test.cpp
#include <stdio.h>
#include <string.h>
#include <openssl/rc4.h>

#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")

int hexoutput(char *first_str, unsigned char *data, int len)
{
    int i;
    printf("%s", first_str);
    for(i=0; i < len; i++)
        printf("%02X", data[i]);
    printf("\n");
    return 0;
}

int main(int argc, char *argv[])
{
    RC4_KEY rc4key;
    unsigned char encryptdata[1024], decryptdata[1024];
    int datalen;
    
    if(argc < 3){
        fprintf(stderr, "%s <rc4 key> <rc4 data>\n", argv[0]);
        return 1;
    }
    
    datalen = strlen(argv[2]);

    hexoutput("KEY     = ", (unsigned char *)argv[1], strlen(argv[1]));
    hexoutput("PLAIN   = ", (unsigned char *)argv[2], datalen);
    
    RC4_set_key(&rc4key, strlen(argv[1]), (unsigned char *)argv[1]);
    RC4(&rc4key, datalen, (unsigned char *)argv[2], encryptdata);
    RC4_set_key(&rc4key, strlen(argv[1]), (unsigned char *)argv[1]);
    RC4(&rc4key, datalen, encryptdata, decryptdata);
    
    hexoutput("ENCRYPT = ", encryptdata, datalen);
    hexoutput("DECRYPT = ", decryptdata, datalen);
    return 0;
}
-----
-----  コマンドプロンプト
C:\>bcc32 -w rc4test.cpp
Borland C++ 5.6.4 for Win32 Copyright (c) 1993, 2002 Borland
rc4test.cpp:
Turbo Incremental Link 5.65 Copyright (c) 1997-2002 Borland
C:\>rc4test test AAAAAAAA
KEY     = 74657374
PLAIN   = 4141414141414141
ENCRYPT = EFCE6650A16B3C64
DECRYPT = 4141414141414141
C:\>
-----

第1引数に鍵となる文字列、第2引数に平文を渡すと、暗号化と復号化を行います。正確に暗号化と復号化がなされていることが分かります。OpenSSLでRC4暗号化アルゴリズムを利用するためには、RC4_set_key関数とRC4関数を使います。これらはrc4.hにて次のように定義されています。

-----  RC4_set_key関数
void RC4_set_key(
    RC4_KEY *key,              // RC4_KEY構造体アドレス
    int len,                   // 鍵データのサイズ
    const unsigned char *data  // 鍵データのアドレス
);
-----

RC4_set_key関数はRC4_KEY構造体を作成するための関数です。鍵データを入力することで、それを元にした構造体を作成することができます。一般的に、プログラム上では鍵データは構造体に変換されます。構造体が鍵としての機能を担うので、共通鍵方式では双方が同じ構造体を持っていれば通信が可能ということになります。

-----  RC4関数
void RC4(
    RC4_KEY *key,                // RC4_KEY構造体
    unsigned long len,           // 平文のサイズ
    const unsigned char *indata, // 平文のアドレス
    unsigned char *outdata       // 暗号文を格納するバッファ
);
-----

RC4関数は、実際に暗号化(復号化)を行う関数です。RC4_set_key関数で作成した構造体と、平文を渡すことで暗号文を出力してくれます。ちなみに、RC4は暗号化処理と復号化処理が同じアルゴリズムとなっています。つまり、RC4関数に暗号文を入れると、平文を出力してくれることになります。

公開鍵暗号プログラミング(RSA)

RSAとは、1977年にRon Rivest氏、Adi Shamir氏、Leonard Adleman氏の3人によって開発された公開鍵暗号方式のことです。公開鍵暗号のアルゴリズムとしては最もよく知られた方式であり、多くの製品に採用されています。事実上、暗号アルゴリズムのスタンダードだと言えます。

RSAは、整数の素因数分解などを利用して暗号化を施します(詳しくは知りません^^;)。現在も有効な解読方法が見つからないということで、もっとも実用的なアルゴリズムのようですが、処理速度が遅いという欠点もあります。次にRSA暗号を利用したサンプルプログラムを示します。

-----  rsatest.cpp
#include <stdio.h>
#include <string.h>
#include <openssl/rsa.h>

#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")

int hexoutput(char *first_str, unsigned char *data, int len)
{
    int i;
    printf("%s", first_str);
    for(i=0; i < len; i++)
        printf("%02X", data[i]);
    printf("\n");
    return 0;
}

int main(int argc, char *argv[])
{
    RSA *rsa_s, *rsa_c;
    unsigned char encryptdata[1024], decryptdata[1024];
    int datalen;
    
    if(argc < 2){
        fprintf(stderr, "%s <RSA data>\n", argv[0]);
        return 1;
    }
    
    datalen = strlen(argv[1]);

    // make private key & public key
    rsa_s = RSA_generate_key(256 * 2, RSA_F4, NULL, NULL);

    hexoutput("PLAIN   = ", (unsigned char *)argv[1], datalen);

    rsa_c = RSA_new();
    BN_hex2bn(&(rsa_c->e), BN_bn2hex(rsa_s->e));  // copy public key e
    BN_hex2bn(&(rsa_c->n), BN_bn2hex(rsa_s->n));  // copy public key n

    // encryption by public key
    datalen = RSA_public_encrypt(datalen, (unsigned char *)argv[1], 
        encryptdata, rsa_c, RSA_PKCS1_OAEP_PADDING);
    RSA_free(rsa_c);

    hexoutput("ENCRYPT = ", encryptdata, datalen);

    // decryption by private key
    datalen = RSA_private_decrypt(datalen, encryptdata, 
        decryptdata, rsa_s, RSA_PKCS1_OAEP_PADDING);
    RSA_free(rsa_s);

    hexoutput("DECRYPT = ", decryptdata, datalen);
    return 0;
}
-----
-----  コマンドプロンプト
C:\>bcc32 -w rsatest.cpp
Borland C++ 5.6.4 for Win32 Copyright (c) 1993, 2002 Borland
rsatest.cpp:
Turbo Incremental Link 5.65 Copyright (c) 1997-2002 Borland
C:\>rsatest AAAAAAAA
PLAIN   = 4141414141414141
ENCRYPT = 4456BC68D656821AE97EA4511CF7AAF042A41FBDFF3EAE8285CEE94AA9C5398EF9787F
511869E5425EB603A5349D2E84A7222DDFD2EC3201DCD4AC0B2146FDFA
DECRYPT = 4141414141414141
C:\>
-----

RSAは、最初に2つの対(つい)となる鍵を生成します。この2つの鍵(公開鍵と秘密鍵)はRSA_generate_key関数で作成します。

-----  RSA_generate_key関数
RSA *RSA_generate_key(                // 戻り値はRSA構造体ポインタ
    int bits,                         // 鍵のビット長
    unsigned long e,                  // 公開指数(RSA_3 or RSA_F4)
    void (*callback)(int,int,void *), // よく分からないのでNULL
    void *cb_arg                      // よく分からないのでNULL
);
-----

引数の説明がかなり適当で申し訳ないです。実は自分もよく分かってません。ただ、bitsは鍵のビット長(512, 1024, etc...)を指定するということと、eは何かとても重要な値(公開指数と呼ぶらしい)で、通常「3」か「65537」を指定するらしいです。他はNULLで問題ないでしょう(^^;。これで戻り値にRSA構造体のアドレスが返ります。

そして、このRSA構造体には、公開鍵と秘密鍵が格納されています。よって、構造体の中にある公開鍵を取り出して、相手に送らなければなりません。RSA構造体の中の公開鍵はeとnです。よって、eとnを取り出して別のRSA構造体に渡します。

-----  RSA_new関数
RSA *RSA_new(void);    // 戻り値はRSA構造体アドレス
-----

RSA_new関数は、空のRSA構造体を作成してくれます。この構造体に公開鍵eとnをコピーします。そして、RSA_public_encrypt関数を呼び出します。

----  RSA_public_encrypt関数
int RSA_public_encrypt(         // 戻り値は成功時に暗号文サイズ
    int flen,                   // 平文サイズ
    const unsigned char *from,  // 平文アドレス
    unsigned char *to,          // 暗号文を格納するバッファ
    RSA *rsa,                   // 公開鍵を持つRSA構造体アドレス
    int padding                 // パディング指定
);
-----

この関数は、公開鍵を使って平文を暗号化します。RSAはブロック暗号なので、パディングを指定する必要があります。一般的にはRSA_PKCS1_OAEP_PADDINGが使われるようです。ちなみに、パディング方法は、暗号化する側と復号化する側で同じものにしておかなければなりません。

-----  RSA_private_decrypt関数
int RSA_private_decrypt(        // 戻り値は成功時に平文サイズ(パディング含む)
    int flen,                   // 暗号文サイズ
    const unsigned char *from,  // 暗号文アドレス
    unsigned char *to,          // 平文を格納するバッファ
    RSA *rsa,                   // 秘密鍵を持つRSA構造体アドレス
    int padding                 // パディング指定
);
-----

引数はRSA_public_encrypt関数とほとんど同じです。この関数で、秘密鍵を利用しての復号化を行います。

-----  RSA_free関数
void RSA_free (
    RSA *r                      // RSA構造体アドレス
);
-----

RSA_generate_key関数とRSA_new関数は、共にRSA構造体のメモリ領域確保します。よって、RSA_free関数で解放してやる必要があります。これで、後始末は終了です。

ハッシュ生成プログラミング(MD5)

MD5(Message Digest 5)とは、認証や署名などに使われるハッシュ関数のひとつです。固定サイズ(16バイト)のハッシュ値を生成します。

次にMD5のハッシュ値を生成するプログラムを示します。

-----  md5test.cpp
#include <stdio.h>
#include <string.h>
#include <openssl/md5.h>

#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")

int hexoutput(char *first_str, unsigned char *data, int len)
{
    int i;
    printf("%s", first_str);
    for(i=0; i < len; i++)
        printf("%02X", data[i]);
    printf("\n");
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned char encryptdata[16];
    
    if(argc < 2){
        fprintf(stderr, "%s <input data>\n", argv[0]);
        return 1;
    }
    
    MD5((unsigned char *)argv[1], strlen(argv[1]), encryptdata);
    hexoutput("PLAIN   = ", (unsigned char *)argv[1], strlen(argv[1]));
    hexoutput("ENCRYPT = ", encryptdata, 16);
    return 0;
}
-----
-----  コマンドプロンプト
C:\>bcc32 -w md5test.cpp
Borland C++ 5.6.4 for Win32 Copyright (c) 1993, 2002 Borland
md5test.cpp:
Turbo Incremental Link 5.65 Copyright (c) 1997-2002 Borland
C:\>md5test test
PLAIN   = 74657374
ENCRYPT = 098F6BCD4621D373CADE4E832627B4F6
C:\>
-----

ハッシュの生成は、さほど難しい知識を必要としないので、プログラムも簡単です。使用するのは、MD5関数のみです。

-----  MD5関数
unsigned char *MD5(
    const unsigned char *d,    // 入力データのアドレス
    size_t n,                  // 入力データのサイズ
    unsigned char *md          // ハッシュ値の格納アドレス
);
-----

入力データには、どれだけのサイズのデータを入れても問題ありません。どんなサイズのデータを入れても、結果として出力されるハッシュ値は必ず16バイトとなります。そして、このハッシュ値は基本的にどんなデータとも重複しません。

さいごに

このように、OpenSSLを利用すると、最低限の知識で暗号を利用することが可能となります。また、OpenSSLには、RSAやRC4の他にも数多くの暗号アルゴリズムが実装されていますし、オープンソースですので、ソースコードも読み放題です。もし、暗号に興味を持ったなら、一度暗号について調べてみるのもよいかもしれません。

というわけで、今回の記事、楽しんでいただけたなら幸いです。最後になりましたが、ここまで読んでくれて本当にありがとうございます。

では、また会う日まで...


Copyright (C) 2003-2006 Kenji Aiko All Rights Reserved