AESを使ってみる

**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 );

&amp;dump( "DATA", $data );
$encrypted = $cipher->encrypt( $data );
&amp;dump( "ENCRYPTED", $encrypted );
$decrypted = $cipher->decrypt( $encrypted );
&amp;dump( "DECRYPTED", $decrypted );
```

実行します。

```
$ ./aes.pl
DATA: 6162636465666768696a6b6c6d6e6f70
ENCRYPTED: b4f487a200158388e17ec6bbcc231d65
DECRYPTED: 6162636465666768696a6b6c6d6e6f70
```

当然ですが、C言語版と同じ結果ですね。

これまでは暗号化するデータを128ビット=16バイトに限ってきました。しかし、実際に使用する場合にはもっと大量のデータを扱うわけですから、これだけでは不十分です。そこで、ブロック暗号化モードというものが登場します。[ブロック暗号化モード](http://www.triplefalcon.com/Lexicon/Encryption-Block-Mode-1.htm)などが参考になります。

ブロック暗号化モードには
* 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
```