LoginSignup
9
18

More than 5 years have passed since last update.

AESを使ってみる

Last updated at Posted at 2015-08-04

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バイトに限ってきました。しかし、実際に使用する場合にはもっと大量のデータを扱うわけですから、これだけでは不十分です。そこで、ブロック暗号化モードというものが登場します。ブロック暗号化モードなどが参考になります。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
18