OpenSSLを使ってC言語でAES暗号
前回(OpenSSLのBIGNUM関連の関数群に関するメモ)から引き続きOpenSSLです。暗号です。 C言語でOpenSSLを使ってAES暗号を扱ってみたので、テストコードを公開します。
EVPとかいうもので抽象化されていて、ちょっと煩雑な手続きが必要。 とはいえおかげで別の暗号化方式に切り替えるのは楽だから、まあ良し悪しだね。
暗号化
unsigned char* Encrypt(const char* key, const char* data, const size_t datalen, const unsigned char* iv, unsigned char* dest, const size_t destlen)
{
    EVP_CIPHER_CTX en;
    int i, f_len=0;
    int c_len = destlen;
    memset(dest, 0x00, destlen);
    EVP_CIPHER_CTX_init(&en);
    EVP_EncryptInit_ex(&en, EVP_aes_128_cbc(), NULL, (unsigned char*)key, iv);
    EVP_EncryptUpdate(&en, dest, &c_len, (unsigned char *)data, datalen);
    //EVP_EncryptFinal_ex(&en, (unsigned char *)(dest + c_len), &f_len);
    printf("c_len: %d\n", c_len);
    printf("f_len: %d\n", f_len);
    PrintBytes(dest, destlen);
    EVP_CIPHER_CTX_cleanup(&en);
    return dest;
}
鍵と暗号化したいデータ、初期ベクトルを渡すと、destに代入してくれる。
コメントアウトしているEVP_EncryptFinal_exはデータ長が16Byteの倍数でないときにだけ必要になるようで、今回は要らないので省いてあります。
中途半端な長さのデータを渡した場合、Finalを呼んだ時にパディングしてうまいことやってくれるらしい。
初期ベクトルを使用しないとき(=暗号利用モードをEBCにするとき)は
EVP_EncryptInit_ex(&en, EVP_aes_128_ecb(), NULL, (unsigned char*)key, NULL);
のようにすればおっけー。
復号
unsigned char* Decrypt(const char* key, const unsigned char* data, const size_t datalen, const unsigned char* iv, char* dest, const size_t destlen)
{
    EVP_CIPHER_CTX de;
    int f_len = 0;
    int p_len = datalen;
    memset(dest, 0x00, destlen);
    EVP_CIPHER_CTX_init(&de);
    EVP_DecryptInit_ex(&de, EVP_aes_128_cbc(), NULL, (unsigned char*)key, iv);
    EVP_DecryptUpdate(&de, (unsigned char *)dest, &p_len, data, datalen);
    //EVP_DecryptFinal_ex(&de, (unsigned char *)(dest + p_len), &f_len);
    EVP_CIPHER_CTX_cleanup(&de);
    printf("p_len: %d\n", p_len);
    printf("f_len: %d\n", f_len);
    printf("%s\n", dest);
    PrintBytes(dest, destlen);
    return dest;
}
鍵とデータと初期ベクトルを渡して、destに代入。そのまんま。
EVP_DecryptFinal_exについては暗号化の時と同じ。パディングが必要なときに呼んでください。
初期ベクトルを使用しないのもほぼ暗号化の時と同じで、
EVP_DecryptInit_ex(&de, EVP_aes_128_ecb(), NULL, (unsigned char*)key, NULL);
という感じ。
動かしてみる
#include <string.h>
#include <stdio.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
あたりをインクルード。
void PrintBytes(const unsigned char* bytes, const size_t length)
{
    int i;
    for(i=0; i<length; i++)
    {
        printf("%02x", bytes[i]);
    }
    printf("\n");
}
これはデバッグ用の関数。Encrypt、Decryptの中で使っています。
int main()
{
    const char key[] =  "abcdefghijklmnop";  // 暗号化に使う鍵。16バイト。
    const char data[] = "hello, OpenSSL! 123456789012345\0";  // 暗号化するデータ。ここでは32バイト。
    const unsigned char iv = "abcdefghijklmnop";  // 初期ベクトル。16バイト。
    unsigned char encode[32] = {'\0'};  // 暗号化したデータを入れる場所。
    char decode[32] = {'\0'};  // 複合したデータを入れる場所。
    printf("%s\n", data);
    PrintBytes(data, sizeof(data)-1);
    Encrypt(key, data, sizeof(data), iv encode, sizeof(encode));
    Decrypt(key, encode, sizeof(encode), iv, decode, sizeof(decode));
    return 0;
}
これがメイン関数。エンコードしてデコードするだけ。
楽ちんといえば楽ちんだし、そうでないといえばそうでない、かなぁ。 さくっとラッパ書いて使うほうが良いのかなぁ、という気がしないでもないです。どうだろう。
参考: C言語でAESの暗号化・復号化を、opensslとmcryptで作ってみた:プログラマー社長のブログ:ITmedia オルタナティブ・ブログ