高度な暗号化規格AESの動作モード(ECB、CBC、CFB、OFB)

27725 ワード

最近、再構築前に書かれたHTTPエージェントは、エージェントクライアントとエージェントサービス側で構成されており、両者はSSLを使用して通信コンテンツが仲介者(MITM)から攻撃されないことを保証していた.新しい実装はSSLを削除しようとしているが、SSLの握手のオーバーヘッドが大きすぎて、特にクライアントとサービス端の間に太平洋が隔てられている一方、今月中旬にGoogleセキュリティチームはSSLv 3が安全ではないことを証明し、TLSにアップグレードする必要があるが、TLSにも握手のオーバーヘッドがある.新しいインプリメンテーションでは、クライアントとサービス間の通信はAES暗号化を使用し、各接続は独立したランダムに生成された鍵と初期化ベクトルを使用する.クライアントは、非対称暗号化アルゴリズムRSAを使用して、サービス側に接続を開始した後、鍵と初期化ベクトルを暗号化してサービス側に送信し、サービス側は鍵と初期化ベクトルを受信した後、すべてAESを使用して通信を暗号化し、通信内容が盗聴されないことを保証する(ただし、改ざんされる可能性がある).本論文では,学習パケット暗号化モードのいくつかの体験と理解をメモとして記録した.AESは、パケット暗号化アルゴリズムとして、異なるセキュリティ要件および伝送要件に適応するために、様々な異なる暗号化モードで動作することを可能にするために、本明細書では、OpenSSLオープンソースライブラリおよびC++言語を記述するために、ECB、CBC、CFBおよびOFBの4つの暗号化モードにのみ関連する.
高度な暗号化規格AES
高度な暗号化規格(Advanced Encryption Standard:AES)は、米国の国家規格・技術研究院(NIST)が2001年に電子データの暗号化規格を確立したものである.これはパケット暗号化規格であり、各暗号化ブロックのサイズは128ビットであり、許可される鍵の長さは128、192、256ビットである.以下に、OpenSSLにおけるAESの最も主要な関数を示す.
1
2
3
4
int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
void AES_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);

これらの関数は、ファイルにおいて宣言され、AES_set_encryp_keyおよびAES_set_decrypt_keyは、暗号化器および復号化器に鍵を設定するために使用され、AES_encryptは、単一ブロックデータ(128ビット)を暗号化するために使用され、AES_decryptは、単一ブロックデータを復号化するために使用される.OpenSSLでは,ECB,CBC,CFB,OFBなどの暗号化モードはいずれもこの2つの関数に対するカプセル化である.説明のために、2つのC++関数from_hex_stringおよびto_hex_stringを容易に導入するために、前者は16進数文字列を対応する2進数ベクトルに変換するために使用され、後者は2進数ベクトルを16進数文字列にシーケンス化するために使用される.
1
2
std::vector<unsigned char> from_hex_string(const std::string& hex);
std::string to_hex_string(const std::vector<unsigned char>& vec);

AESを使用して単純にデータブロックを暗号化し、復号化する例は以下の通りである.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
auto key = from_hex_string("2B7E151628AED2A6ABF7158809CF4F3C");
auto plan_vec = from_hex_string("6BC1BEE22E409F96E93D7E117393172A");
std::vector<unsigned char> cipher_vec(16);
std::vector<unsigned char> decrypt_vec(16);

// aes encrypt
AES_KEY aes_enc_ctx;
AES_set_encrypt_key(key.data(), 128, &aes_enc_ctx);
AES_encrypt(plan_vec.data(), cipher_vec.data(), &aes_enc_ctx);

// aes decrypt
AES_KEY aes_dec_ctx;
AES_set_decrypt_key(key.data(), 128, &aes_dec_ctx);
AES_decrypt(cipher_vec.data(), decrypt_vec.data(), &aes_dec_ctx);

std::cout << "plan : " << to_hex_string(plan_vec) << std::endl;
std::cout << "cipher : " << to_hex_string(cipher_vec) << std::endl;
std::cout << "decrypt: " << to_hex_string(decrypt_vec) << std::endl;

8行目は128ビット鍵2B7E151628AED2A6ABF7158809CF4F3C、9行目はAES_encryptは128ビットのデータブロック6BC1BEE22E409F96E93D7E117393172Aを暗号化し、13、14行目は暗号化時と同じ鍵で復号する.しゅつりょく
1
plan   : 6BC1BEE22E409F96E93D7E117393172A
cipher : 3AD77BB40D7A3660A89ECAF32466EF97
decrypt: 6BC1BEE22E409F96E93D7E117393172A

ECBモード(電子暗号本モード:Electronic codebook)
ECBは最も簡単なブロック暗号暗号化モードであり、暗号化前にAESが128ビットのような暗号化ブロックサイズに基づいていくつかのブロックに分けられ、その後、各ブロックを同じ鍵で単独で暗号化し、同理を復号する.
ECB暗号化プロセス(画像はウィキペディアから)
ECB復号プロセス(画像はウィキペディアから)
OpenSSLではECBモードに対してAES_という名前がありますecb_encryptの関数で、その実現はこのようなものです(実はAES_encryptとAES_decryptがベストをセットしています).
1
2
3
4
5
6
7
8
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,const AES_KEY *key, const int enc) {
assert(in && out && key);
assert((AES_ENCRYPT == enc)||(AES_DECRYPT == enc));
if (AES_ENCRYPT == enc)
AES_encrypt(in, out, key);
else
AES_decrypt(in, out, key);
}

ECBモードは、各ブロックデータの暗号化が独立しているため、暗号化と復号化を並列に計算することができ、ECBモードの最大の欠点は、同じ明文ブロックが同じ暗号ブロックに暗号化されることであり、この方法は、ある環境では厳格なデータ機密性を提供できないことである.
CBCモード(パスワードパケットリンク:Cipher-block chaining)
CBCモードは、暗号化される各暗号ブロックについて、暗号化前に前の暗号ブロックの暗号文と異なるか、暗号化器で暗号化される.最初の明文ブロックは、 というデータブロックとは異なる.
CBC暗号化プロセス(画像はウィキペディアから)
CBC解読プロセス(画像はウィキペディアから)
OpenSSLでAES-CBC暗号化に用いられる関数はAES_cbc_encrypt
1
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, size_t length, const AES_KEY *key, unsigned char *ivec, const int enc);

各パラメータの意味
  • in:暗号化される明文データ
  • out:暗号出力バッファ
  • length:明文データ長(バイト)
  • key:encがAES_である場合ENCRYPTの場合このパラメータはAES_set_encrypt_key初期化、encがAES_の場合DECRYPTの場合はAES_set_decrypt_key初期化
  • enc:暗号化AES_ENCRYPT解読AES_DECRYPT

  • AES_cbc_encryptはlengthが16(128ビット)の整数倍ではなく、不足している部分が0で埋め込まれ、常に16の整数倍が出力されます.暗号化または復号化が完了すると、初期化ベクトルIVが更新される.例として
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    auto key = from_hex_string("2B7E151628AED2A6ABF7158809CF4F3C");
    auto i_vec = from_hex_string("000102030405060708090A0B0C0D0E0F");
    auto plan_vec = from_hex_string("6B");

    AES_KEY aes_enc_ctx;
    AES_set_encrypt_key(key.data(), 128, &aes_enc_ctx);
    std::vector<unsigned char> cipher_vec(16);
    AES_cbc_encrypt(plan_vec.data(), cipher_vec.data(), 1, &aes_enc_ctx, i_vec.data(), AES_ENCRYPT);

    std::cout << "plan : " << to_hex_string(plan_vec) << std::endl;
    std::cout << "cipher : " << to_hex_string(cipher_vec) << std::endl;
    std::cout << "ivec : " << to_hex_string(i_vec) << std::endl;

    AES_KEY aes_dec_ctx;
    AES_set_decrypt_key(key.data(), 128, &aes_dec_ctx);
    std::vector<unsigned char> decrypt_vec(16);
    i_vec = from_hex_string("000102030405060708090A0B0C0D0E0F");
    AES_cbc_encrypt(cipher_vec.data(), decrypt_vec.data(), 16, &aes_dec_ctx, i_vec.data(), AES_DECRYPT);

    std::cout << "decrypt: " << to_hex_string(decrypt_vec) << std::endl;

    しゅつりょく
    1
    plan   : 6B
    cipher : F05F94CA1B1459C236C2C35A4BCA72ED
    ivec   : F05F94CA1B1459C236C2C35A4BCA72ED
    decrypt: 6B000000000000000000000000000000

    CBCモードはECBよりも機密性が高いが,各データブロックに対する暗号化は前のデータブロックとの暗号化に依存するため,暗号化は並列化できない.ECBと同様に暗号化前にデータを埋め込む必要があり,対流データの暗号化にはあまり適していない.
    CFBモード(暗号フィードバック:Cipher feedback)
    ECBおよびCBCモードがブロックデータのみを暗号化できるのとは異なり、CFBはブロック暗号文(Block Cipher)をストリーム暗号文(Stream Cipher)に変換することができる.
    CFB暗号化プロセス(画像はウィキペディアから)
    CFB復号プロセス(画像はウィキペディアから)
    注意:CFB、OFB、CTRモードでの復号も、復号ではなく暗号化器で使用されます.CFBの暗号化作業は2つの部分に分けられる.
  • 前段暗号化された暗号文を再暗号化する.
  • は、第1のステップで暗号化されたデータを現在のセグメントの明文と異ならせる.

  • 暗号化プロセスと復号化プロセスでブロック暗号化器によって暗号化されたデータは、前の暗号文であるため、暗号化ブロックサイズの整数倍でなくても、暗号化前後でデータ長が同じであることが保証される.このモードを128ビットと呼ぶCFBモード(CFB 128とも呼ばれる)OpenSSLでこのような復号化を行うための関数はAES_cfb128_encryptである
    1
    void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out, size_t length, const AES_KEY *key, unsigned char *ivec, int *num, const int enc)

    この関数のほとんどのパラメータはAES_cbc_encryptは同じで、
  • key:暗号化でも復号化でもこのkeyはAES_set_encrypt_key初期化
  • num:前回暗号化されてから関数が戻るまでに処理された明文データの長さ(バイト)を返すには、次の分析
  • を参照してください.
  • enc:暗号化AES_ENCRYPT解読AES_DECRYPT

  • この関数を使い始めたばかりの頃はnumというパラメータに疑問を抱いていたが、Googleは答えを見つけることができず、ソースコードに直接行って実現を見た.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* The input and output encrypted as though 128bit cfb mode is being
    * used. The extra state information to record how much of the
    * 128bit block we have used is contained in *num;
    */


    void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
    size_t length, const AES_KEY *key,
    unsigned char *ivec, int *num, const int enc)
    {


    CRYPTO_cfb128_encrypt(in,out,length,key,ivec,num,enc,(block128_f)AES_encrypt);
    }

    上記のコード注釈では、このパラメータの用途について説明していますが、見てもよくわかりません.次はCRYPTO_cfb128_encryptの実装(重要でないコードを削除した).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    void CRYPTO_cfb128_encrypt(const unsigned char *in, unsigned char *out,
    size_t len, const void *key,
    unsigned char ivec[16], int *num,
    int enc, block128_f block)

    {
    unsigned int n;
    size_t l = 0;
    assert(in && out && key && ivec && num);
    n = *num;
    if (enc) {
    while (l
    if (n == 0) {
    (*block)(ivec, ivec, key);
    }
    out[l] = ivec[n] ^= in[l];
    ++l;
    n = (n+1) % 16;
    }
    *num = n;
    }
    else {
    while (l
    unsigned char c;
    if (n == 0) {
    (*block)(ivec, ivec, key);
    }
    out[l] = ivec[n] ^ (c = in[l]); ivec[n] = c;
    ++l;
    n = (n+1) % 16;
    }
    *num=n;
    }
    }

    このコードを見て分かるように、128ビットのCFBは、前のデータの暗号化用ブロック暗号化器を暗号化してIVに保存した後、この128ビットのデータで後から来た128ビットのデータと異なるか、numは前回暗号化器を呼び出してから処理したデータ長(バイト)を記録するために使用され、numが再び0になったときに暗号化器が呼び出され、すなわち、処理128ビットごとに暗号化器が呼び出される.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    auto key = from_hex_string("2B7E151628AED2A6ABF7158809CF4F3C");
    auto i_vec = from_hex_string("000102030405060708090A0B0C0D0E0F");
    auto plan_vec = from_hex_string("6BC1BEE22E409F96E93D7E117393172A52"); // 136 bits 17 bytes

    AES_KEY aes_enc_ctx;
    AES_set_encrypt_key(key.data(), 128, &aes_enc_ctx);
    std::vector<unsigned char> cipher_vec(plan_vec.size());
    int num = 0;
    for(std::size_t index = 0; index < plan_vec.size(); ++index) {
    AES_cfb128_encrypt(&plan_vec[index], &cipher_vec[index], 1, &aes_enc_ctx, i_vec.data(), &num, AES_ENCRYPT);
    std::cout << "i_vec: " << to_hex_string(i_vec) << " num: " << num << std::endl;
    }
    std::cout << "cipher : " << to_hex_string(cipher_vec) << std::endl;

    AES_KEY aes_dec_ctx;
    AES_set_encrypt_key(key.data(), 128, &aes_dec_ctx);
    std::vector<unsigned char> decrypt_vec(cipher_vec.size());
    i_vec = from_hex_string("000102030405060708090A0B0C0D0E0F"); // reset i_vec
    num = 0;
    AES_cfb128_encrypt(cipher_vec.data(), decrypt_vec.data(), cipher_vec.size(), &aes_dec_ctx, i_vec.data(), &num, AES_DECRYPT);

    std::cout << "decrypt: " << to_hex_string(decrypt_vec) << std::endl;

    しゅつりょく
    1
    i_vec: 3BFE67CC996D32B6DA0937E99BAFEC60 num: 1
    i_vec: 3B3F67CC996D32B6DA0937E99BAFEC60 num: 2
    i_vec: 3B3FD9CC996D32B6DA0937E99BAFEC60 num: 3
    i_vec: 3B3FD92E996D32B6DA0937E99BAFEC60 num: 4
    i_vec: 3B3FD92EB76D32B6DA0937E99BAFEC60 num: 5
    i_vec: 3B3FD92EB72D32B6DA0937E99BAFEC60 num: 6
    i_vec: 3B3FD92EB72DADB6DA0937E99BAFEC60 num: 7
    i_vec: 3B3FD92EB72DAD20DA0937E99BAFEC60 num: 8
    i_vec: 3B3FD92EB72DAD20330937E99BAFEC60 num: 9
    i_vec: 3B3FD92EB72DAD20333437E99BAFEC60 num: 10
    i_vec: 3B3FD92EB72DAD20333449E99BAFEC60 num: 11
    i_vec: 3B3FD92EB72DAD20333449F89BAFEC60 num: 12
    i_vec: 3B3FD92EB72DAD20333449F8E8AFEC60 num: 13
    i_vec: 3B3FD92EB72DAD20333449F8E83CEC60 num: 14
    i_vec: 3B3FD92EB72DAD20333449F8E83CFB60 num: 15
    i_vec: 3B3FD92EB72DAD20333449F8E83CFB4A num: 0
    i_vec: 348BCF60BEB005A35354A201DAB36BDA num: 1
    cipher : 3B3FD92EB72DAD20333449F8E83CFB4A34
    decrypt: 6BC1BEE22E409F96E93D7E117393172A52

    CFB 128は、処理ごとに128ビットのデータが呼び出される暗号化器である.また、2つの一般的なCFBはCFB 8とCFB 1であり、前者は処理ごとに8ビットで暗号化器が呼び出され、後者は処理ごとに1ビットで暗号化器が呼び出され、演算量としてCFB 1はCFB 8の8倍であり、CFB 128の128倍である.CFB 8およびCFB 1については、IVをシフトレジスタとする必要がある.
    CFB 8の暗号化プロセス
  • 暗号化器を用いてIVのデータを暗号化する.
  • 明文の最高8位とIVの最高8位を異ならせるか、8位の密文を得る.
  • IVデータを8ビット左にシフトし、最低8ビットは計算したばかりの8ビット暗号文で補完する.

  • 1~3を繰り返します.
    CFB 1の暗号化プロセス
  • 暗号化器を用いてIVのデータを暗号化する.
  • 明文の最高1位とIVの最高1位を異ならせるか、1位の密文を得る.
  • IVデータを左に1ビットシフトし,最低1ビットは計算したばかりの1ビット暗号文で補う.

  • 1~3を繰り返します.
    OpenSSLのAES_cfb8_encryptおよびAES_cfb1_encryptは、それぞれ、CFB 8およびCFB 1を復号するために使用される.この2つの関数のパラメータとAES_cfb128_encryptは全く同じですがnumとlengthの意味は少し違います.
  • num:常に0でなければ、断言
  • がトリガーされます.
  • length:CFB 8単位byte CFB 1単位bit CFBモードは対流データの暗号化に非常に適しており、復号は並列計算可能である.

  • OFBモード(出力フィードバック:Output feedback)
    OFBは,まずブロック暗号化器で鍵ストリーム(Keystream)を生成し,その後鍵ストリームを明文ストリームと異ならせたり密文ストリームを得たりし,復号はブロック暗号化器で鍵ストリームを生成し,鍵ストリームを密文ストリームと異ならせたり明文を得たりするが,異ならば操作の対称性から暗号化と復号化の流れは全く同じである.
    OFB暗号化プロセス(画像はウィキペディアから)OFB復号プロセス(画像はウィキペディアから)
    OpenSSLでAES-OFBを復号するための関数はAES_ofb128_encryptであり、そのパラメータと各パラメータの意味とAES_cfb128_encryptは全く同じですが、OFB暗号化と復号化は対称なのでパラメータencはありません.OFBはCFBと同様に対流データの暗号化に非常に適しており,OFBは暗号化も復号化も前のセグメントデータに依存するため,暗号化も復号も並列にはできない.