2010/3/17の過去ブログの転載です。内容が古い可能性がありますのでご注意ください。
ちと必要になったのでAESについて調べてみました。
まず概略。DESに代わって規格化された共通鍵暗号。ブロック長は128ビット固定。鍵長は128ビット、192ビット、256ビットが選択可能。
AESのソースはBrian Gladman氏のサイトで公開されています。URLはhttp://fp.gladman.plus.com/
として紹介されていることが多いのですが、こちらはリンク切れでして現在は http://www.gladman.me.uk/ となっているようです。こちらにはアセンブラを使用した高速版と、C言語のみの通常版が公開されています。今回はそこまで速度にこだわっていないのと、アセンブラの環境を構築するのが面倒なので、C言語版にしました。
このソースをコンパイルすると、次のようなエラーが出ました。
cc -c aes.c
aes.c: In function 'copy_block_nn':
aes.c:335: error: lvalue required as increment operand
aes.c:335: error: lvalue required as increment operand
aes.c: In function 'aes_set_key':
aes.c:513: warning: case label value exceeds maximum value for type
*** Error code 1
問題になっているのは次の部分です。
static void copy_block_nn( void * d, const void *s, uint_8t nn )
{
while( nn-- ) {
*((uint_8t*)d)++ = *((uint_8t*)s)++;
}
}
エラーメッセージをぐぐってみたところ、gcc4から強化されたエラーチェックのようです。とりあえず次のようなコードに修正してコンパイルを通るようにしました。
static void copy_block_nn( void * d, const void *s, uint_8t nn )
{
while( nn-- ) {
//*((uint_8t*)d)++ = *((uint_8t*)s)++;
*((uint_8t*)d) = *((uint_8t*)s);
d += sizeof(uint_8t*);
s += sizeof(uint_8t*);
}
}
もう一つの513行目の警告は次のようなコードです。
return_type aes_set_key( const unsigned char key[], length_type keylen, aes_cont
ext ctx[1] )
{
uint_8t cc, rc, hi;
switch( keylen )
{
case 16:
case 128:
keylen = 16;
break;
case 24:
case 192:
keylen = 24;
break;
case 32:
case 256:
keylen = 32;
break;
default:
ctx->rnd = 0;
return -1;
}
unsigned charであるkeylenに対して256で比較しているのが警告になっているわけですね。コードを見る限り動作に問題はなさそうなので放置します。
まずは、こんな感じのテストプログラムを書いて動作を確認してみます。
#include <stdio.h>
#include <string.h>
#include "aes.h"
void dump( char *label, unsigned char *block )
{
int i;
printf( "%s: ", label );
for( i = 0; i < 16; i++ )
printf( "%02x", block[i] );
printf( "\n" );
}
int main()
{
unsigned char key[] = "0123456789ABCDEF";
unsigned char data[] = "abcdefghijklmnop";
aes_context ctx[1];
unsigned char encrypted[N_BLOCK], decrypted[N_BLOCK];
aes_set_key( key, 16, ctx );
dump( "DATA", data );
aes_encrypt( data, encrypted, ctx );
dump( "ENCRYPTED", encrypted );
aes_decrypt( encrypted, decrypted, ctx );
dump( "DECRYPTED", decrypted );
return( 0 );
}
実行結果は次の通りです。
$ ./test
DATA: 6162636465666768696a6b6c6d6e6f70
ENCRYPTED: b4f487a200158388e17ec6bbcc231d65
DECRYPTED: 6162636465666768696a6b6c6d6e6f70
無事、元通りに復号できていることが確認できました。
次に、perlでも試してみます。
#!/usr/bin/perl
use strict;
use warnings;
use Crypt::OpenSSL::AES;
sub dump {
my( $label, $block ) = @_;
printf( "%s: ", $label );
printf( "%s\n", unpack( "H*", $block ) );
}
my $key = '0123456789ABCDEF';
my $data = 'abcdefghijklmnop';
my( $encrypted, $decrypted );
my $cipher = new Crypt::OpenSSL::AES( $key );
&dump( "DATA", $data );
$encrypted = $cipher->encrypt( $data );
&dump( "ENCRYPTED", $encrypted );
$decrypted = $cipher->decrypt( $encrypted );
&dump( "DECRYPTED", $decrypted );
実行します。
$ ./aes.pl
DATA: 6162636465666768696a6b6c6d6e6f70
ENCRYPTED: b4f487a200158388e17ec6bbcc231d65
DECRYPTED: 6162636465666768696a6b6c6d6e6f70
当然ですが、C言語版と同じ結果ですね。
これまでは暗号化するデータを128ビット=16バイトに限ってきました。しかし、実際に使用する場合にはもっと大量のデータを扱うわけですから、これだけでは不十分です。そこで、ブロック暗号化モードというものが登場します。ブロック暗号化モードなどが参考になります。
ブロック暗号化モードには
* CBC - 暗号文ブロック連鎖モード(Cipher Block Chaining)
* OFB - 出力フィードバックモード(Oftput Feed Back)
* CFB - 暗号フィードバックモード(Cipher Feed Back)
* ECB - 暗号ブックモード(Electric Code Book)
などがあります。一番単純なのがECBで、これは入力データを16バイトごとに区切って暗号化するものです。しかし、このような利用の仕方ではBirthday Attackに弱いという欠点がありますので、あまり使用するべきではありません。
ということで、CBCモードを用いて暗号化と復号を試してみます。
#!/usr/bin/perl
use strict;
use warnings;
use Crypt::CBC;
sub dump {
my( $label, $block ) = @_;
printf( "%s: ", $label );
printf( "%s\n", unpack( "H*", $block ) );
}
my $key = '0123456789ABCDEF';
my $data = 'abcdefghijklmnop';
my( $encrypted, $decrypted );
my $cipher = new Crypt::CBC( -key=>$key, -cipher=>'Crypt::Rijndael' );
&dump( "DATA", $data );
$encrypted = $cipher->encrypt( $data );
&dump( "ENCRYPTED", $encrypted );
$decrypted = $cipher->decrypt( $encrypted );
&dump( "DECRYPTED", $decrypted );
実行結果は次の通りです。
$ ./aes2.pl
DATA: 6162636465666768696a6b6c6d6e6f70
ENCRYPTED: 53616c7465645f5fbab6ccd1ce672cfd3a323d2b7a2b1c1013b6653fa6eb33886f85c52582f6d33ba57dab83bd0a906f
DECRYPTED: 6162636465666768696a6b6c6d6e6f70
PHPでも試してみました。
function dump( $label, $data ) {
print "$label: " . bin2hex( $data ) . "\n";
}
$key = '0123456789ABCDEF';
$data = 'abcdefghijklmnop';
dump( 'DATA', $data );
srand();
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $
iv);
dump( 'ENCRYPTED', $encrypted );
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_C
BC, $iv);
dump( 'DECRYPTED', $decrypted );
実行結果は次の通りです。
$ php test.php
DATA: 6162636465666768696a6b6c6d6e6f70
ENCRYPTED: c051918bf93d8fffd09379c0c6f4db75
DECRYPTED: 6162636465666768696a6b6c6d6e6f70