Web就活日記

愛と夢と人生について書きます

OpenSSLの暗号処理が爆速な件

OpenSSL―暗号・PKI・SSL/TLSライブラリの詳細―

OpenSSL―暗号・PKI・SSL/TLSライブラリの詳細―

目次

  1. OpenSSLによる暗号
  2. 実行環境
  3. OpenSSLによる暗号化速度
  4. ECBとCBCの違い
  5. PHP
    • OpenSSLとMcrypt関数のalgorithms比較
    • OpenSSLとmcrypt関数のDES,AESの速度比較
    • Mcryptのゼロpaddingの癖
  6. C
    • DES暗号
    • AES暗号
    • OpenSSLとMcryptのDES,AESの速度比較

OpenSSLによる暗号

OpenSSL日本語サイト: The Open Source toolkit for SSL/TLS はてなブックマーク - OpenSSL日本語サイト: The Open Source toolkit for SSL/TLS
あどてくやっている@yutakikucです。
今日はOpenSSLの共通鍵暗号について調査した内容を纏めます。OpenSSLについて特にC言語での日本語ドキュメントが少なく、あったとしても内容が古くてあまり参考にならなかったりするので色々とサンプルを上げて行きます。PHPについて記載します。
結論を先に書いておくとC,PHPともにMcryptでは無くOpenSSLを使って暗号化した方が処理効率がはるかに向上します。以下がECBモードで100万回の暗号化/復号化処理時間(sec)の表になります。速度以外の点としてはMcryptはPadding(後述)の仕様が厄介です。みなさん、OpenSSLを使いましょう!※下で挙げているソースコードにはほとんどError処理が書いていないので、コピペする人はその辺に気をつけてくださいね。コードはgithubにも上げたのでご自由にどうぞ。
Crypto/openssl at master · yutakikuchi/Crypto はてなブックマーク - Crypto/openssl at master · yutakikuchi/Crypto

暗号ライブラリ 暗号メソッド 言語 実行回数 暗号化処理時間 復号化処理時間 備考
OpenSSL DES-ECB PHP 1000000 1.7591309 1.9514858 爆速
Mcrypt DES-ECB PHP 1000000 231.3662681 218.4248890 爆遅
OpenSSL AES-128-ECB PHP 1000000 1.1707689  1.51384592 爆速
Mcrypt AES-128-ECB PHP 1000000 93.1640918 86.3522748 爆遅
OpenSSL DES-ECB C 1000000 1.160 1.240 爆速
Mcrypt DES-ECB C 1000000 214.830 194.970 爆遅
OpenSSL AES-128-ECB C 1000000 0.620 0.720 爆速
Mcrypt AES-128-ECB C 1000000 62.850 62.430 爆遅

実行環境

このドキュメントは以下の環境およびパッケージで実行しています。

$ cat /etc/system-release 
CentOS release 6.4 (Final)

$ uname -a                                                   
Linux localhost.localdomain 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

$ rpm -qa | grep "openssl"
rpm -qa | grep -i "openssl"
openssl-1.0.1e-16.el6_5.4.x86_64
openssl-devel-1.0.1e-16.el6_5.4.x86_64
pyOpenSSL-0.10-2.el6.x86_64
openssl098e-0.9.8e-17.el6.centos.2.x86_64

$ rpm -qa | grep "php"
php-cli-5.4.25-2.el6.remi.x86_64
php-mcrypt-5.4.25-2.el6.remi.x86_64
php-common-5.4.25-2.el6.remi.x86_64
php-5.4.25-2.el6.remi.x86_64

$ rpm -qa | grep "python"
python-libs-2.6.6-52.el6.x86_64
python-iniparse-0.3.1-2.1.el6.noarch
python-pycurl-7.19.0-8.el6.x86_64
newt-python-0.52.11-3.el6.x86_64
python-2.6.6-52.el6.x86_64
python-devel-2.6.6-52.el6.x86_64
python-setuptools-0.6.10-3.el6.noarch
rpm-python-4.8.0-32.el6.x86_64
python-urlgrabber-3.9.1-8.el6.noarch

OpenSSLによる暗号化速度

DES,3DES,AESあたりがよく使われる共通鍵暗号方式のメソッドだと思いますが、それぞれどの程度の速度で処理されているかをopensslコマンドにより比較する事が出来ます。3秒間にそれぞれの固定サイズのデータを何回暗号化出来るかというspeed勝負です。DESの暗号化強度が不安なのでより強度の高いAESが開発されたという歴史の流れがありますが、処理速度はDESよりAES-128の方が速いという結果になります。(僕の環境ではAES-128とAES-192がDESより速いという結果。DESはkey長が8Byte(64bit)、AESは16Byte(128bit)/24Byte(192bit)/32Byte(258bit)なのでAESの方が遅いと予想されがちですが、AES-256だけがDESより遅くなりました。)3DESは名前の通りDESの3倍近く遅いですね。
一番下の表は1secあたりの処理キロバイト数を計算したものになります。

$ openssl speed des aes
Doing des cbc for 3s on 16 size blocks: 9642571 des cbc's in 2.98s
Doing des cbc for 3s on 64 size blocks: 2527420 des cbc's in 2.99s
Doing des cbc for 3s on 256 size blocks: 592201 des cbc's in 2.98s
Doing des cbc for 3s on 1024 size blocks: 122972 des cbc's in 2.93s
Doing des cbc for 3s on 8192 size blocks: 16906 des cbc's in 2.97s
Doing des ede3 for 3s on 16 size blocks: 3467602 des ede3's in 2.98s
Doing des ede3 for 3s on 64 size blocks: 910272 des ede3's in 2.98s
Doing des ede3 for 3s on 256 size blocks: 192319 des ede3's in 2.97s
Doing des ede3 for 3s on 1024 size blocks: 49983 des ede3's in 2.96s
Doing des ede3 for 3s on 8192 size blocks: 5577 des ede3's in 2.98s
Doing aes-128 cbc for 3s on 16 size blocks: 13118363 aes-128 cbc's in 2.96s
Doing aes-128 cbc for 3s on 64 size blocks: 3854938 aes-128 cbc's in 2.98s
Doing aes-128 cbc for 3s on 256 size blocks: 939551 aes-128 cbc's in 2.97s
Doing aes-128 cbc for 3s on 1024 size blocks: 509362 aes-128 cbc's in 2.97s
Doing aes-128 cbc for 3s on 8192 size blocks: 59653 aes-128 cbc's in 2.97s
Doing aes-192 cbc for 3s on 16 size blocks: 10306361 aes-192 cbc's in 2.83s
Doing aes-192 cbc for 3s on 64 size blocks: 3184489 aes-192 cbc's in 2.97s
Doing aes-192 cbc for 3s on 256 size blocks: 757061 aes-192 cbc's in 2.97s
Doing aes-192 cbc for 3s on 1024 size blocks: 428066 aes-192 cbc's in 2.97s
Doing aes-192 cbc for 3s on 8192 size blocks: 37443 aes-192 cbc's in 2.97s
Doing aes-256 cbc for 3s on 16 size blocks: 4759675 aes-256 cbc's in 3.02s
Doing aes-256 cbc for 3s on 64 size blocks: 2045784 aes-256 cbc's in 2.97s
Doing aes-256 cbc for 3s on 256 size blocks: 541610 aes-256 cbc's in 2.97s
Doing aes-256 cbc for 3s on 1024 size blocks: 315557 aes-256 cbc's in 2.98s
Doing aes-256 cbc for 3s on 8192 size blocks: 46577 aes-256 cbc's in 2.96s

(略)

The 'numbers' are in 1000s of bytes per second processed.
type                  16 bytes        64 bytes       256 bytes      1024 bytes    8192 bytes
des cbc             51772.19k    54098.62k    50873.64k    42977.25k    46630.96k
des ede3           18618.00k    19549.47k    16576.99k    17291.42k    15331.14k
aes-128 cbc      70910.07k    82790.61k    80984.87k   175618.41k   164537.84k
aes-192 cbc      58269.18k    68621.99k    65255.09k   147589.09k   103277.12k
aes-256 cbc      25216.82k    44084.23k    46684.23k   108433.01k   128904.99k

ECBとCBCの違い

ブロック暗号モード(block cipher mode) はてなブックマーク - ブロック暗号モード(block cipher mode)
上のサイトに詳しく書いてあります。ECBは平文を固定ブロックに分割して1ブロック毎に暗号化を行い連結して暗号文を作成する。CBCも平文を固定ブロックに分割し暗号化を行いますが、前回の暗号結果が次回の暗号結果にも連鎖して利用される方式になります。CBCは前回の結果を反映するため、初回の暗号結果としてIV(初期ベクトル)を必要とします。CBCモードを選択している時にプログラム上でIVが無いと怒られるのはこの為ですね。

PHP

OpenSSLとMcrypt関数のalgorithms比較

まずはサポートされている暗号化メソッドですが、圧倒的にOpenSSLの方が充実しています。以下のコマンドを叩いていただければ表示されます。※OpenSSLは重複している暗号メソッドも含まれます。

#openssl
$ php -r "print_r(openssl_get_cipher_methods());"
Array
(
    [0] => AES-128-CBC
    [1] => AES-128-CFB
    [2] => AES-128-CFB1
    [3] => AES-128-CFB8
    [4] => AES-128-CTR
    [5] => AES-128-ECB
    [6] => AES-128-OFB
    [7] => AES-128-XTS
    [8] => AES-192-CBC
    [9] => AES-192-CFB
    [10] => AES-192-CFB1
 ()
    [165] => rc4
    [166] => rc4-40
    [167] => rc4-hmac-md5
    [168] => seed-cbc
    [169] => seed-cfb
    [170] => seed-ecb
    [171] => seed-ofb

)

#mcrypt
$ php -r 'print_r(mcrypt_list_algorithms());'                          
Array
(
    [0] => cast-128
    [1] => gost
    [2] => rijndael-128
    [3] => twofish
    [4] => arcfour
    [5] => cast-256
    [6] => loki97
    [7] => rijndael-192
    [8] => saferplus
    [9] => wake
    [10] => blowfish-compat
    [11] => des
    [12] => rijndael-256
    [13] => serpent
    [14] => xtea
    [15] => blowfish
    [16] => enigma
    [17] => rc2
    [18] => tripledes
)
OpenSSLとMcrypt関数のDES,AESの速度比較

まずはDESの速度比較です。IVを必要としないECBモードで実行します。結果はOpenSSLの圧勝でした。Mcryptは遅い!encryptで131倍、decryptで111倍も時間が掛かって今います。今mcrypt使って頻繁にDES暗号化処理をしている人は乗り換えした方が良いレベルですね。

<?php

$n = 1000000;
$message = '魔法少女まどか☆マギカ';
$key = 'Soul Gem';
$method = 'DES-ECB';

//openssl
$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $crypto = openssl_encrypt( $message, $method, $key );
}
echo "encrypt message = {$crypto} \n";
echo "openssl {$n} encrypt time = " . ( microtime(true) - $start ) . "\n";

$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $message = openssl_decrypt( $crypto, $method, $key );
}
echo "decrypt message = {$message} \n";
echo "openssl {$n} decrypt time = " . ( microtime(true) - $start ) . "\n";

//mcrypto
$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $crypto = base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, $message, MCRYPT_MODE_ECB));
}
echo "encrypt message = {$crypto} \n";
echo "mcrypt {$n} encrypt time = " . ( microtime(true) - $start ) . "\n";

$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $message = mcrypt_decrypt(MCRYPT_DES, $key, base64_decode($crypto), MCRYPT_MODE_ECB);
}

echo "decrypt message = {$message} \n";
echo "mcrypt {$n} decrypt time = " . ( microtime(true) - $start ) . "\n";
$ php des.php
encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGa0KXi6EbHF4w== 
openssl 1000000 encrypt time = 1.7591309547424
decrypt message = 魔法少女まどか☆マギカ 
openssl 1000000 decrypt time = 1.9514858722687
encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGZW+smvwY2mXw== 
mcrypt 1000000 encrypt time = 231.36626815796
decrypt message = 魔法少女まどか☆マギカ 
mcrypt 1000000 decrypt time = 218.42488908768

次にAES-128-ECBで比較します。結果はDES同様にOpenSSLの圧勝でした。encryptで79倍、decryptで57倍速度に差が出ています。Mcryptは何故そんなにも重いんでしょうか...前職でめっちゃMcryptに頼っていたコードを今から書き換えたい...

<?php

$n = 1000000;
$message = '魔法少女まどか☆マギカ';
$key = 'Soul GemSoul Gem';
$method = 'AES-128-ECB';

//openssl
$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $crypto = openssl_encrypt( $message, $method, $key );
}
echo "encrypt message = {$crypto} \n";
echo "openssl {$n} encrypt time = " . ( microtime(true) - $start ) . "\n";

$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $message = openssl_decrypt( $crypto, $method, $key );
}
echo "decrypt message = {$message} \n";
echo "openssl {$n} decrypt time = " . ( microtime(true) - $start ) . "\n";

//mcrypto
$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $crypto = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $message, MCRYPT_MODE_ECB));
}
echo "encrypt message = {$crypto} \n";
echo "mcrypt {$n} encrypt time = " . ( microtime(true) - $start ) . "\n";

$start = microtime(true);
for($i=0; $i<$n; ++$i){
  $message = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($crypto), MCRYPT_MODE_ECB);
}
echo "decrypt message = {$message} \n";
echo "mcrypt {$n} decrypt time = " . ( microtime(true) - $start ) . "\n";
$ php aes.php 
encrypt message = jkSF8YDL4Bi/JDC6z/5aoosGSBRqcEeRri2ZH5Qs+PMb+HceqdCgV5iMH/DziDzB 
openssl 1000000 encrypt time = 1.1707689762115
decrypt message = 魔法少女まどか☆マギカ 
openssl 1000000 decrypt time = 1.5138459205627
encrypt message = jkSF8YDL4Bi/JDC6z/5aoosGSBRqcEeRri2ZH5Qs+PPPZdMXB4Zn5TSdDI1WVm86 
mcrypt 1000000 encrypt time = 93.164091825485
decrypt message = 魔法少女まどか☆マギカ 
mcrypt 1000000 decrypt time = 86.352274894714
mcryptのゼロpaddingの癖

mcrypt関数は処理速度が遅いだけではなく、暗号化をする際に文字列を固定ブロック長に分割するのですが、分割後に足りないByte数を埋め合せるPadding処理をNULLで行うという癖があります。上の例も実はopensslとmcryptで生成された暗号化文に差異が発生しています。暗号化には通常はpkcs#5というPaddingが使われる事が多いです。php-mcryptで暗号化、復号化を他言語で行おうとするとこのPaddingの違いによりエラーが出る場合があるので注意が必要です。通常はphp-mcryptに自前でpkcs5_pad、pkcs5_unpadの関数を用意してencrypt前にブロック長に対して不足している長さを特定の文字で埋めるpaddingを先に行います。以下はそのサンプルです。この実行でopensslとmcyrptで暗号化文の差異が発生しなくなります。

<?php

function pkcs5_pad($text, $blocksize) { 
    $pad = $blocksize - (strlen($text) % $blocksize); 
    return $text . str_repeat(chr($pad), $pad); 
}
function pkcs5_unpad($text) { 
    $pad = ord($text{strlen($text)-1}); 
    if ($pad > strlen($text)) return false; 
    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; 
    return substr($text, 0, -1 * $pad); 
}  

$message = '魔法少女まどか☆マギカ';
$key = 'Soul Gem';

$encrypt = base64_encode(mcrypt_encrypt(MCRYPT_DES, $key, pkcs5_pad($message, mcrypt_get_block_size(MCRYPT_DES, 'ecb')), MCRYPT_MODE_ECB));
echo "encrypt message = {$encrypt} \n";
$message = pkcs5_unpad(mcrypt_decrypt(MCRYPT_DES, $key, base64_decode($encrypt), MCRYPT_MODE_ECB));
echo "decrypt message = {$message} \n";
$ php mcrypt_des.php 
encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGa0KXi6EbHF4w== 
decrypt message = 魔法少女まどか☆マギカ

C

DES,AES暗号

OpenSSLの暗号化をCで実装した日本語ドキュメントがあまりにも少ないのでソースコードを残す事を目的として書きます。opensslのevp.hとbio.hを使って暗号化/復号化/Base64エンコード処理を行います。具体的な処理はint main関数の中に記載していますが、EVP_CIPHER_CTX_init:初期化、EVP_EncryptInit_ex:暗号化メソッドとkey指定、EVP_EncryptUpdate:暗号化、EVP_EncryptFinal_ex:ブロック長を超えたデータの調整、EVP_CIPHER_CTX_cleanup:お掃除のような処理になります。Decryptはその逆になります。他言語よりコード量が多いですが、まぁCなのでそこはしようがないです。
CBCモードでもIVが必要とされるぐらいで他の処理は基本的にECB暗号と同じ処理になります。下のコードでは復号時にIVが分かっている事を前提に記載していますが、一般的な利用の場合は別途IVの共有が必要になります。IVをRAND_bytes関数にてblocksizeと同じ長さのランダム文字列を生成しています。RAND_bytes関数を利用するにはopenssl/rand.hをincludeします。base64系の処理は上と同じなので省略します。
※現在原因を調査中ですがBIOを使ったbase64_encode,base64_decodeのコストがとてつもなく大きいので、下のサンプルは注意して使ってください。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/rand.h>
#define ITERATION 1000000

int base64_encode(const char *message, char **buffer) {
    BIO *bio, *b64;
    FILE* stream;
    int encodedSize = 4*ceil((double)strlen(message)/3);
    *buffer = (char *)malloc(encodedSize+1);
    stream = fmemopen(*buffer, encodedSize+1, "w");
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new_fp(stream, BIO_NOCLOSE);
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    BIO_write(bio, message, strlen(message));
    BIO_flush(bio);
    BIO_free_all(bio);
    fclose(stream);
    return 0;
}

int calc_decode_length(const char *b64input) {
    int len = strlen(b64input);
    int padding = 0;
    if (b64input[len-1] == '=' && b64input[len-2] == '=') {
        padding = 2;
    } else if (b64input[len-1] == '=') { 
        padding = 1;
    }
    return (int)len*0.75 - padding;
}

int base64_decode(char *b64message, char **buffer) {
    BIO *bio, *b64;
    int decodeLen = calc_decode_length(b64message),len = 0;
    *buffer = (char*)malloc(decodeLen+1);
    FILE* stream = fmemopen(b64message, strlen(b64message), "r");
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new_fp(stream, BIO_NOCLOSE);
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    len = BIO_read(bio, *buffer, strlen(b64message));
    (*buffer)[len] = '\0';
    BIO_free_all(bio);
    fclose(stream);
    return 0;
}

int create_iv(unsigned char **iv, size_t blocksize) {
    *iv = calloc(blocksize+1, sizeof(char));
    RAND_bytes(*iv, blocksize);
}

int openssl_encrypt(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *method, unsigned char *key, unsigned char *iv, const char *message, unsigned char **cipher, unsigned int cipher_len) {
    unsigned int out_len=0;
    EVP_CIPHER_CTX_init(ctx);
    EVP_EncryptInit_ex(ctx,method,NULL,(unsigned char *)key, iv);
    EVP_EncryptUpdate(ctx,(unsigned char *)*cipher,&cipher_len,(unsigned char *)message,strlen(message));
    EVP_EncryptFinal_ex(ctx,(unsigned char *)(*cipher+cipher_len),&out_len);
    EVP_CIPHER_CTX_cleanup(ctx);
    return 0;
}

int openssl_decrypt(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *method, unsigned char *key, unsigned char *iv, char *dectext, unsigned char **plain, unsigned int plain_len) {
    unsigned int out_len=0;
    EVP_CIPHER_CTX_init(ctx);
    EVP_DecryptInit_ex(ctx,method,NULL,(unsigned char *)key,iv);
    EVP_DecryptUpdate(ctx,(unsigned char *)*plain,&plain_len,(unsigned char *)dectext,strlen(dectext));
    EVP_DecryptFinal_ex(ctx,(unsigned char *)(*plain+plain_len),&out_len);
    EVP_CIPHER_CTX_cleanup(ctx);
    return 0;
}

int main(int argc,char *argv[]){
    
    EVP_CIPHER_CTX ctx;
    unsigned char *iv, *cipher, *plain, *key="Soul Gem", *message="魔法少女まどか☆マギカ";
    unsigned int blocksize = 8, cipher_len, plain_len, i;
    char *enctext, *dectext;

    //dec-ecb-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len,sizeof(char));
    openssl_encrypt(&ctx, EVP_des_ecb(), key, NULL, message, &cipher, cipher_len);
    base64_encode( (const char *)cipher, &enctext ); 
    printf( "dec-ecb-encrypt message = %s\n", enctext );
    free(cipher);

    //des-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len,sizeof(char));
    openssl_decrypt(&ctx, EVP_des_ecb(), key, NULL, dectext, &plain, plain_len); 
    printf( "dec-ecb-decrypt message = %s\n", plain );
    free(plain);

    //iv生成
    create_iv(&iv, blocksize);

    //des-cbc-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len,sizeof(char));
    openssl_encrypt(&ctx, EVP_des_cbc(), key, iv, message, &cipher, cipher_len);
    base64_encode( (const char *)cipher, &enctext ); 
    printf( "dec-cbc-encrypt message = %s\n", enctext );
    free(cipher);
    cipher = '\0';

    //dec-cbc-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len,sizeof(char));
    openssl_decrypt(&ctx, EVP_des_cbc(), key, iv, dectext, &plain, plain_len); 
    printf( "dec-cbc-decrypt message = %s\n", plain );
    free(plain);
    free(iv);
    return 0;
}
$ gcc -lm -I/usr/include/openssl -L/usr/lib64 -lcrypto openssl_des.c -o openssl_des
$ ./openssl_des
dec-ecb-encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGa0KXi6EbHF4w==
dec-ecb-decrypt message = 魔法少女まどか☆マギカ
dec-cbc-encrypt message = C7ZA6Br7+b6Bv33cOkUSbpJA6SDtVOygKAQNzPDn0OSWzgZnnwzYYQ==
dec-cbc-decrypt message = 魔法少女まどか☆マギカ
AES暗号

基本的に上のDESコードと同じになります。DESからの変更は暗号化メソッド名と、必要なkey長に応じたByte数を確保します。ここではAES-ECBの128bitモード(16Byte)の例を書きます。256bitモード(32Byte)の場合もメソッド名の数値とblocksizeを書き換えるぐらいで対応できます。main関数以外の処理は上と同じなので省略します。

int main(int argc,char *argv[]){
    
    EVP_CIPHER_CTX ctx;
    unsigned char *iv, *cipher, *plain, *key="Soul GemSoul Gem", *message="魔法少女まどか☆マギカ";
    unsigned int blocksize = 16, cipher_len, plain_len, i;
    char *enctext, *dectext;

    //aes-ecb-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len,sizeof(char));
    openssl_encrypt(&ctx, EVP_aes_128_ecb(), key, NULL, message, &cipher, cipher_len);
    base64_encode( (const char *)cipher, &enctext ); 
    printf( "aes-ecb-encrypt message = %s\n", enctext );
    free(cipher);

    //aes-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len,sizeof(char));
    openssl_decrypt(&ctx, EVP_aes_128_ecb(), key, NULL, dectext, &plain, plain_len); 
    printf( "aes-ecb-decrypt message = %s\n", plain );
    free(plain);
    
    //iv生成
    create_iv(&iv, blocksize);

    //aes-ecb-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len,sizeof(char));
    openssl_encrypt(&ctx, EVP_aes_128_cbc(), key, iv, message, &cipher, cipher_len);
    base64_encode( (const char *)cipher, &enctext ); 
    printf( "aes-cbc-encrypt message = %s\n", enctext );
    free(cipher);

    //aes-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len,sizeof(char));
    openssl_decrypt(&ctx, EVP_aes_128_cbc(), key, iv, dectext, &plain, plain_len); 
    printf( "aes-cbc-decrypt message = %s\n", plain );
    free(plain);
    
    //free
    free(iv);
    return 0;
}
$ gcc -lm -I/usr/include/openssl -L/usr/lib64 -lcrypto openssl_aes.c -o openssl_aes
$ ./openssl_aes
aes-ecb-encrypt message = jkSF8YDL4Bi/JDC6z/5aoosGSBRqcEeRri2ZH5Qs+PMb+HceqdCgV5iMH/DziDzB
aes-ecb-decrypt message = 魔法少女まどか☆マギカ
aes-cbc-encrypt message = ruD0dtX9DBV3Agy+WMLRg5kpHxQJiLFEmxxKXyG26YC60xuTIqBPf7ta41oSUccc
aes-cbc-decrypt message = 魔法少女まどか☆マギカ
OpenSSLとMcryptのDES,AESの速度比較

PHP同様にCでもMcryptを使って暗号化をする事ができます。mcrypt関数の方がopensslよりも直感的に使い易く少ないコード量で実現できますが、例のごとくPaddingがイケてないので下ではPaddingをpkcs5にするようコードを加えています。速度の評価ですがDES-ECBの暗号化でMcryptの方が185倍、復号化で157倍、AES-ECBの暗号化で101倍、復号化で86倍も遅い事が分かりました。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <mcrypt.h>
#include <time.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/rand.h>
#define ITERATION 1000000

int base64_encode(const char *message, char **buffer) {
    BIO *bio, *b64;
    FILE* stream;
    int encodedSize = 4*ceil((double)strlen(message)/3);
    *buffer = (char *)malloc(encodedSize+1);
    stream = fmemopen(*buffer, encodedSize+1, "w");
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new_fp(stream, BIO_NOCLOSE);
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    BIO_write(bio, message, strlen(message));
    BIO_flush(bio);
    BIO_free_all(bio);
    fclose(stream);
    return 0;
}

int calc_decode_length(const char *b64input) {
    int len = strlen(b64input);
    int padding = 0;
    if (b64input[len-1] == '=' && b64input[len-2] == '=') {
        padding = 2;
    } else if (b64input[len-1] == '=') { 
        padding = 1;
    }
    return (int)len*0.75 - padding;
}

int base64_decode(char *b64message, char **buffer) {
    BIO *bio, *b64;
    int decodeLen = calc_decode_length(b64message),len = 0;
    *buffer = (char*)malloc(decodeLen+1);
    FILE* stream = fmemopen(b64message, strlen(b64message), "r");
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new_fp(stream, BIO_NOCLOSE);
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    len = BIO_read(bio, *buffer, strlen(b64message));
    (*buffer)[len] = '\0';
    BIO_free_all(bio);
    fclose(stream);
    return 0;
}

int create_iv(unsigned char **iv, size_t blocksize) {
    *iv = calloc(blocksize+1, sizeof(char));
    RAND_bytes(*iv, blocksize);
    return 0;
}

int openssl_encrypt(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *method, unsigned char *key, unsigned char *iv, const char *message, unsigned char **cipher, unsigned int cipher_len) {
    unsigned int out_len=0;
    EVP_CIPHER_CTX_init(ctx);
    EVP_EncryptInit_ex(ctx,method,NULL,(unsigned char *)key, iv);
    EVP_EncryptUpdate(ctx,(unsigned char *)*cipher,&cipher_len,(unsigned char *)message,strlen(message));
    EVP_EncryptFinal_ex(ctx,(unsigned char *)(*cipher+cipher_len),&out_len);
    EVP_CIPHER_CTX_cleanup(ctx);
    return 0;
}

int openssl_decrypt(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *method, unsigned char *key, unsigned char *iv, char *dectext, unsigned char **plain, unsigned int plain_len) {
    unsigned int out_len=0;
    EVP_CIPHER_CTX_init(ctx);
    EVP_DecryptInit_ex(ctx,method,NULL,(unsigned char *)key,iv);
    EVP_DecryptUpdate(ctx,(unsigned char *)*plain,&plain_len,(unsigned char *)dectext,strlen(dectext));
    EVP_DecryptFinal_ex(ctx,(unsigned char *)(*plain+plain_len),&out_len);
    EVP_CIPHER_CTX_cleanup(ctx);
    return 0;
}

int *mcrypt_encrypt(unsigned char *method, unsigned char *mode, unsigned char *key, unsigned char *cipher, unsigned int cipher_len) {
    MCRYPT td = mcrypt_module_open(method, NULL, mode, NULL);
    mcrypt_generic_init(td, (void *)key, strlen(key), NULL);
    mcrypt_generic(td, cipher, cipher_len);
    mcrypt_generic_deinit(td);
    mcrypt_module_close(td);
    return 0;
}

int *mcrypt_decrypt(unsigned char *method, unsigned char *mode, unsigned char *key, unsigned char *plain, unsigned int plain_len) {
    MCRYPT td = mcrypt_module_open(method, NULL, mode, NULL);
    mcrypt_generic_init(td, (void *)key, strlen(key), NULL);
    mdecrypt_generic(td, plain, plain_len);
    mcrypt_generic_deinit(td);
    mcrypt_module_close(td);
    return 0;
}

int *pkcs5_padding(unsigned char **cipher, unsigned int mod, const char *message, unsigned int blocksize) {
    if( mod != 0 ) {
        unsigned int pad = blocksize - mod, i;
        char p[1];
        sprintf(p, "%c", pad);
        for(i=0; i<pad; i++) {
            strcat(*cipher, p);
        }
    }
    return 0;
}

int *pkcs5_unpadding(unsigned char **plain, unsigned int plain_len) {
    unsigned char dp[1], int_dp[1];
    sprintf(dp, "%c", *(*plain + plain_len -1));
    sprintf(int_dp, "%d", *dp);
    int j = atoi(int_dp);
    if(dp != NULL && j != 0) {
        if(j < plain_len) {
            int i;
            for(i=plain_len - j; i<plain_len; i++) {
                *(*plain + i) = '\0';
            }
        }
    }
    return 0;
}

int main(int argc,char *argv[]){

    EVP_CIPHER_CTX ctx;
    unsigned char *iv, *cipher, *plain, *key="Soul Gem", *message="魔法少女まどか☆マギカ";
    unsigned int blocksize = 8, cipher_len, plain_len, mod, i, j;
    char *enctext, *dectext;
    clock_t start, end;

    //dec-ecb-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len+1,sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        openssl_encrypt(&ctx, EVP_des_ecb(), key, NULL, message, &cipher, cipher_len);
    }
    base64_encode((const char *)cipher, &enctext);
    end = clock();
    printf("dec-ecb-encrypt message = %s\n", enctext);
    printf("openssl %d encrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(cipher);
    cipher = '\0';

    //des-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len+1,sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        openssl_decrypt(&ctx, EVP_des_ecb(), key, NULL, dectext, &plain, plain_len);
    }
    end = clock();
    printf("dec-ecb-decrypt message = %s\n", plain );
    printf("openssl %d decrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(plain);
    plain = '\0';

    blocksize = 16;
    key = "Soul GemSoul Gem";

    //aes-ecb-encrypt
    cipher_len=strlen(message)+EVP_MAX_BLOCK_LENGTH;
    cipher=(unsigned char *)calloc(cipher_len+1,sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        openssl_encrypt(&ctx, EVP_aes_128_ecb(), key, NULL, message, &cipher, cipher_len);
    }
    base64_encode((const char *)cipher, &enctext);
    end = clock();
    printf("aes-ecb-encrypt message = %s\n", enctext);
    printf("openssl %d encrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(cipher);
    cipher = '\0';

    //aes-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len=strlen(dectext)+EVP_MAX_BLOCK_LENGTH;
    plain=(unsigned char *)calloc(plain_len+1,sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        openssl_decrypt(&ctx, EVP_aes_128_ecb(), key, NULL, dectext, &plain, plain_len);
    }
    end = clock();
    printf("aes-ecb-decrypt message = %s\n", plain );
    printf("openssl %d decrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(plain);
    plain = '\0';

    blocksize = 8;
    key = "Soul Gem";

    //des-cbc-encrypt
    mod = strlen(message) % blocksize;
    cipher_len = strlen(message) + ( blocksize - mod );
    cipher = (char *)calloc(cipher_len+1, sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        strcpy(cipher, message);
        pkcs5_padding(&cipher, mod, message, blocksize);
        mcrypt_encrypt(MCRYPT_DES, MCRYPT_ECB, key, cipher, strlen(cipher));
    }
    end = clock();
    base64_encode( (const char *)cipher, &enctext );
    printf("des-ecb-encrypt message = %s\n", enctext);
    printf("mcrypt %d encrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);

    free(cipher);
    cipher = '\0';

    //dec-cbc-decrypt
    base64_decode(enctext, &dectext);
    plain_len = strlen(dectext);
    plain = (char *)calloc(plain_len+1, sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        strcpy(plain,dectext);
        mcrypt_decrypt(MCRYPT_DES, MCRYPT_ECB, key, plain, strlen(plain));
        pkcs5_unpadding(&plain, strlen(plain));
    }

    end = clock();
    printf("des-ecb-decrypt message = %s\n", plain);
    printf("mcrypt %d decrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);

    free(plain);
    plain = '\0';

    blocksize = 16;
    key = "Soul GemSoul Gem";
    mod = strlen(message) % blocksize;
    cipher_len = strlen(message) + ( blocksize - mod );
    cipher = (char *)calloc(cipher_len+1, sizeof(char));

    //aes-ecb-encrypt
    start = clock();
    for(i=0; i<ITERATION; ++i){
        strcpy(cipher, message);
        pkcs5_padding(&cipher, mod, message, blocksize);
        mcrypt_encrypt(MCRYPT_RIJNDAEL_128, MCRYPT_ECB, key, cipher, strlen(cipher));
    }
    end = clock();
    base64_encode((const char *)cipher, &enctext);
    printf("aes-ecb-encrypt message = %s\n", enctext);
    printf("mcrypt %d encrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(cipher);
    cipher = '\0';

    //aes-ecb-decrypt
    base64_decode(enctext, &dectext);
    plain_len = strlen(dectext);
    plain = (char *)calloc(plain_len+1, sizeof(char));
    start = clock();
    for(i=0; i<ITERATION; ++i){
        strcpy(plain,dectext);
        mcrypt_decrypt(MCRYPT_RIJNDAEL_128, MCRYPT_ECB, key, plain, strlen(plain));
        pkcs5_unpadding(&plain, strlen(plain));
    }

    end = clock();
    printf("aes-ecb-decrypt message = %s\n", plain);
    printf("mcrypt %d decrypt time = %8.7f\n", ITERATION, (double)(end-start)/CLOCKS_PER_SEC);
    free(plain);
    plain = '\0';
    return 0;
}
$ gcc -lm -I/usr/include/openssl -L/usr/lib64 -lcrypto -lmcrypt openssl_vs_mcrypt.c -o openssl_vs_mcrypt
$ ./openssl_vs_mcrypt
dec-ecb-encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGa0KXi6EbHF4w==
openssl 1000000 encrypt time = 1.1600000
dec-ecb-decrypt message = 魔法少女まどか☆マギカ
openssl 1000000 decrypt time = 1.2400000
aes-ecb-encrypt message = jkSF8YDL4Bi/JDC6z/5aoosGSBRqcEeRri2ZH5Qs+PMb+HceqdCgV5iMH/DziDzB
openssl 1000000 encrypt time = 0.6200000
aes-ecb-decrypt message = 魔法少女まどか☆マギカ
openssl 1000000 decrypt time = 0.7200000
des-ecb-encrypt message = s3TrEokf/1xTgoHIimEtUi8s5hNgultndusqMud+XGar
mcrypt 1000000 encrypt time = 214.8300000
des-ecb-decrypt message = 魔法少女まどか☆マギカ
mcrypt 1000000 decrypt time = 194.9700000
aes-ecb-encrypt message = jkSF8YDL4Bi/JDC6z/5aoosGSBRqcEeRri2ZH5Qs+POr
mcrypt 1000000 encrypt time = 62.8500000
aes-ecb-decrypt message = 魔法少女まどか☆マギカ
mcrypt 1000000 decrypt time = 62.4300000

スポンサーリンク