概要
AESは、DESおよび3DESに代わって規格化された共通鍵暗号方式である。
ブロック長は128ビット、鍵長は128ビット・192ビット・256ビットが選択できる。
AESのソースコードは、Brian Gladman氏のWebサイトで公開されている。
アセンブラで記述されたもの(高速)とC言語で記述されたものが公開されている。
また、GithubにQt AESライブラリが公開されている。
ブロック暗号化モード
AESには、ブロック暗号化モードという機能がある。
最も単純なのがECBモードであり、データを16[byte]ごとに区切って暗号化する。
しかし、Birthday Attack等に弱いという欠点があるため、ECBモードは使用するべきではない。
- CBC
- 暗号文ブロック連鎖モード(Cipher Block Chaining)
- OFB
- 出力フィードバックモード(Oftput Feed Back)
- CFB
- 暗号フィードバックモード(Cipher Feed Back)
- ECB
- 暗号ブックモード(Electric Code Book)
この他にも、PCBCやCounter Method等の新しい暗号利用モードも考案されている。
ECBモード
ECBモードは、1ブロックずつ単純に処理する。
ただし、ECBモードはブロック単位処理の裏をかいた暗号文一致攻撃をに弱い欠点がある。
暗号ブロックが一致した場合は復号した平文は一致するため、重要な秘密が漏れる可能性が高いと考えられる。
特定の暗号ブロックに対応する平文ブロックが1度でも知られると、同一の値を持つ暗号ブロックは全て解読される。
実際には、暗号文全体のうち部分的な平文は状況的に推測できるということは少なくないため、長い平文を暗号化する場合は非推奨である。
また、ブロック単位の差し替え等の暗号文改竄攻撃にも弱いという問題がある。
改竄攻撃を成功させるために、鍵を入手したり平文を正確に把握する必要が無いため、注意が必要である。
AESを使用した商用ソフトウェアにおいて、ECBモードが使用されることはほぼ無い。
CBCモード
CBCモードは、前の平文ブロックを暗号化した結果を、次の平文にXOR演算によって重ね合わせ、その結果に対して暗号化処理を行う。
最初のブロックを暗号化する場合、前の暗号文の最後のブロックを利用するか、または外部から与えた初期ベクトルを使用する。
前の暗号化結果が次のブロックに連鎖されるので連鎖モードと呼ぶ。
同一の平文から同一の暗号文が生成される可能性は極めて低いため、同一の平文が続く場合も安心して使用できる。
CBCモードに対する暗号文一致攻撃は、以下のように考えられる。
暗号文ブロック に対応する平文が とする時、偶然にも、 であった場合、以下の攻撃が成立する。
それぞれの前の暗号文をCk_1、Cj_1とする時、以下が成立する。
また、CBCモードの手順から、以下が成立する。
すると、2つの平文の間に生じる差分は入手できてしまう。
しかし、このような攻撃が成功する確率は非常に稀であるといえる。
CBCモードの初期値は、攻撃者から見えても構わないことになっているが、毎回違う値を利用することが推奨されている。
CBCモードは、暗号化がランダムアクセスに適さないため、暗号化は先頭から順次行う必要がある。
また、復号がランダムアクセスに適しており、復号はランダムに選択した着目ブロックに対して操作できる。(1つ前の暗号文ブロックは必要である)
OFBモード
OFBモードは、初期ベクトルを暗号化して、それをまた暗号化して、次々と乱数を生成する。
その乱数列を、XOR演算によって平文に重ね合わせて、暗号化処理を行う。
したがって、ブロック暗号をストリーム暗号のように使用する。
OFBモードの初期値は、攻撃者から見えても構わないことになっているが、同じ鍵を使用する場合、必ず、初期値は毎回違う値を使用する。
同一の初期値と鍵の組み合わせから生じる乱数列は常に同一であるため、
平文と暗号文の組が1組でも攻撃者の手に渡ってしまうと、そこから乱数列が解ってしまい、
同一の初期値と鍵で暗号化した全ての暗号文が解読されてしまう。
OFBモードは、暗号化も復号化もランダムアクセスに適していないため、どちらも先頭から順次行う必要がある。
最近、人気のある暗号化モードにカウンタモード(またはカウンタメソッド)がある。
カウンタモードは、先頭からのブロック番号を求めてその値を暗号化して、その数値を乱数として使用する。
カウンタモードであれば、安全性が若干損なう可能性があるが、暗号化も復号もランダムに行えて便利である。
暗号化ライブラリ
Crypto++ Libraryのダウンロード
Crypto++ Libraryの公式Webサイトにアクセスして、暗号化ライブラリのソースコードをダウンロードする。
ダウンロードしたソースコードをビルドする。
make -j 8 static dynamic CXX=/<GCCのインストールディレクトリ>/g++ make test make install DESTDIR=<暗号化ライブラリのインストールディレクトリ>
暗号化ライブラリの使用手順は、以下のWebサイトを参照すること。
https://www.cryptopp.com/wiki/Advanced_Encryption_Standard
Crypto++ Libraryの概要
AES(またはその他のブロック暗号)を使用する時は、通常、ブロック暗号のインスタンスを持つモードを使用する。
例えば、CFBモードの場合は、以下のように記述する。
CFB_Mode<AES>::Encryption Encrypt; // CFB_Modeは、ブロック暗号をテンプレートパラメータとして受け取る
ブロック暗号の参照
外部のブロック暗号オブジェクトのインスタンスではなく、そのオブジェクトへの参照を保持するモードオブジェクトを作成することもできる。
以下の例では、外部のAESオブジェクトでCFBモードを使用している。
AES::Encryption aesEncryption(key, AES::DEFAULT_KEYLENGTH);
CFB_Mode_ExternalCipher::Encryption cfbEncryption(aesEncryption, iv);
ExternalCipherモードは、WindowsでFIPSをサポートするために追加されたものであり、必要がない限り避けるべきである。
ECBおよびCBCモードの備考
ECBおよびCBCモードでは、データをブロックサイズの倍数で処理する必要がある。
または、StreamTransformationFilter
をモードオブジェクトにラッピングして、フィルタオブジェクトとして使用することもできる。
StreamTransformationFilter
は、データをブロックにバッファリングして、必要に応じてパディングを行う。
もし、フルブロックサイズで処理する(パディングを行わない)場合は、第4引数にStreamTransformationFilter
のNO_PADDING
を指定する。
StringSource(data, true, new StreamTransformationFilter(encryptor, new StringSink(result), NO_PADDING))
サンプルコード
以下の例では、AESが使用する鍵長の最小値、最大値、標準値をダンプしている。(2番目と3番目はパイプラインのフィルターを使用している)
パイプラインは高レベルの抽象化であり、入力のバッファリング、出力のバッファリング、パディングを処理する。
std::cout << "鍵長 : " << AES::DEFAULT_KEYLENGTH << std::endl;
std::cout << "鍵長(最小) : " << AES::MIN_KEYLENGTH << std::endl;
std::cout << "鍵長(最大) : " << AES::MAX_KEYLENGTH << std::endl;
std::cout << "ブロックサイズ : " << AES::BLOCKSIZE << std::endl;
// 出力
// 標準値の鍵長は128ビット(16バイト)
鍵長 : 16
鍵長(最小) : 16
鍵長(最大) : 32
ブロックサイズ : 16
以下の例では、CFBモードを使用して、暗号化および復号を行っている。
ECBやCBCではないため、データ長をAESのブロックサイズの倍数にする必要は無い。
AutoSeededRandomPool rnd;
// Generate a random key
SecByteBlock key(0x00, AES::DEFAULT_KEYLENGTH);
rnd.GenerateBlock(key, key.size());
// Generate a random IV
SecByteBlock iv(AES::BLOCKSIZE);
rnd.GenerateBlock(iv, iv.size());
byte plainText[] = "Hello! How are you.";
size_tmessageLen = std::strlen((char*)plainText) + 1;
// Encrypt
CFB_Mode<AES>::Encryption cfbEncryption(key, key.size(), iv);
cfbEncryption.ProcessData(plainText, plainText, messageLen);
// Decrypt
CFB_Mode<AES>::Decryption cfbDecryption(key, key.size(), iv);
cfbDecryption.ProcessData(plainText, plainText, messageLen);
ここでは、外部で生成された鍵と初期化ベクトル(IV)を使用して、暗号化器と復号器を取得する例を記述する。
その後の処理は、上記の例と同様に記述する。
SecByteBlock aes_key(16);
SecByteBlock iv(16);
// stub for how you really get it, e.g. reading it from a file, off of a
// network socket encrypted with an asymmetric cipher, or whatever
read_key(aes_key, aes_key.size());
// stub for how you really get it, e.g. filling it with random bytes or
// reading it from the other side of the socket since both sides have
// to use the same IV as well as the same key
read_initialization_vector(iv);
// the final argument is specific to CFB mode, and specifies the refeeding size in bytes.
// This invocation corresponds to Java's Cipher.getInstance("AES/CFB8/NoPadding")
auto enc = new CFB_Mode<AES>::Encryption(aes_key, sizeof(aes_key), iv, 1);
// the final argument is specific to CFB mode, and specifies the refeeding size in bytes.
// This invocation corresponds to Java's Cipher.getInstance("AES/CFB8/NoPadding")
auto dec = new CFB_Mode<AES>::Decryption(aes_key, sizeof(aes_key), iv, 1);
Qt AESライブラリ
以下の例では、Qt AESライブラリを使用して、鍵長256[bit]のCBCモードで暗号化している。
#include <QCryptographicHash>
#include "QAESEncryption.h"
void Encrypt(const QString &plainText)
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC);
QString key("your-string-key"); // AES256は32[byte]の鍵長
QString iv("your-IV-vector"); // AESのブロックサイズは16[byte]の固定長のため、初期化ベクトルも16[byte]
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Md5);
QByteArray encodeText = encryption.encode(plainText.toLocal8Bit(), hashKey, hashIV);
return;
}
以下の例では、Qt AESライブラリを使用して、鍵長256[bit]のCBCモードで復号している。
#include <QCryptographicHash>
#include "QAESEncryption.h"
void Decrypt(const QByteArray &encodeText)
{
QAESEncryption encryption(QAESEncryption::AES_256, QAESEncryption::CBC);
QString key("your-string-key"); // 暗号化と同じ鍵
QString iv("your-IV-vector"); // 暗号化と同じ初期化ベクトル
QByteArray hashKey = QCryptographicHash::hash(key.toLocal8Bit(), QCryptographicHash::Sha256);
QByteArray hashIV = QCryptographicHash::hash(iv.toLocal8Bit(), QCryptographicHash::Md5);
QByteArray decodeText = encryption.decode(encodeText, hashKey, hashIV);
QString decodedString = QString(encryption.removePadding(decodeText));
return;
}