1. 概要 

このチュートリアルでは、初期化ベクトル(IV)を暗号化アルゴリズムで使用する方法について説明します。 IVを使用する際のベストプラクティスについても説明します。

この記事は、暗号化の基本的な理解を前提としています。

すべての例で、さまざまなモードAESアルゴリズムを使用します。

2. 暗号化アルゴリズム

暗号化アルゴリズムは、暗号化されたテキストまたは暗号文を生成するために、データまたはプレーンテキストとキーを使用します。 また、生成された暗号文と同じキーを使用して、復号化されたデータまたは元の平文を生成します。

たとえば、ブロック暗号アルゴリズムは、固定長ブロックを暗号化および復号化することでセキュリティを提供します。 さまざまな暗号化モードを使用して、データ全体にアルゴリズムを繰り返し適用し、使用するIVのタイプを指示します。

ブロック暗号の場合、同じサイズのブロックを使用します。 平文のサイズがブロックサイズよりも小さい場合は、パディングを使用します。 一部のモードでは、ブロック暗号をストリーム暗号として使用するため、パディングを使用しません。

3. 初期化ベクトル(IV)

暗号化アルゴリズムのIVを開始状態として使用し、これを暗号に追加して、暗号化されたデータのパターンを非表示にします。 これにより、呼び出しのたびに新しいキーを再発行する必要がなくなります。

3.1. IVの特性

ほとんどの暗号化モードでは、一意のシーケンスまたはIVを使用します。 また、同じキーで同じIVを再利用しないでください。 これにより、同じキーで複数回暗号化した場合でも、同じ平文暗号化に対して別個の暗号文が保証されます。

暗号化モードに応じて、IV特性のいくつかを見てみましょう。

  • 繰り返さないようにする必要があります
  • 暗号化モードに基づいて、それもランダムである必要があります
  • 秘密にする必要はありません
  • ノンスである必要があります
  • AESのIVは、キーの長さに関係なく常に128ビットです。

3.2. IVの生成

Cipherクラスから直接IVを取得できます。

byte[] iv = cipher.getIV();

デフォルトの実装がわからない場合は、いつでもメソッドを記述してIVを生成できます。 明示的なIVを提供しない場合は、 Cipher .getIV()を使用して暗黙的にIVを取得します。 上記のプロパティに準拠している限り、任意の方法を使用してIVを生成できます。

まず、SecureRandomを使用してランダムIVを作成しましょう。

public static IvParameterSpec getIVSecureRandom(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] iv = new byte[Cipher.getInstance(algorithm).getBlockSize()];
    random.nextBytes(iv);
    return new IvParameterSpec(iv);
}

次に、Cipherクラスからパラメーターを取得してIVを作成します。

public static IvParameterSpec getIVInternal(Cipher cipher) throws InvalidParameterSpecException {
    AlgorithmParameters params = cipher.getParameters();
    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
    return new IvParameterSpec(iv);
}

上記の方法のいずれかを使用して、ランダムで予測不可能なIVを生成できます。 ただし、 GCMなどの一部のモードでは、IVをカウンターと一緒に使用します。 このような場合、最初の数バイトを使用します。ほとんどの場合、IVには12バイト、カウンターには次の4バイトを使用します。

public static byte[] getRandomIVWithSize(int size) {
    byte[] nonce = new byte[size];
    new SecureRandom().nextBytes(nonce);
    return nonce;
}

この場合、カウンターを繰り返さないようにし、IVも一意であることを確認する必要があります。

最後に、はお勧めしませんが、ハードコードされたIVを使用することもできます。

4. 異なるモードでのIVの使用

ご存知のように、暗号化の主な機能は、攻撃者が推測できないように平文をマスクすることです。 したがって、暗号文内のパターンをマスクするためにさまざまな暗号モードを使用します。

ECB、CBC、OFB、CFB、CTR、CTS、XTSなどのモードは機密性を提供します。 ただし、これらのモードは改ざんや変更から保護しません。 検出用にメッセージ認証コード(MAC)またはデジタル署名を追加できます。 認証付き暗号化(AE)の下で複合モードを提供するさまざまな実装を使用します。 CCM、GCM、CWC、EAX、IAPM、およびOCBはいくつかの例です。

4.1. 電子コードブック(ECB)モード

電子コードブックモードは、キーを使用して各ブロックを個別に暗号化します。 これは常に同一の平文を同一の暗号文ブロックに暗号化するため、パターンをうまく隠すことができません。 したがって、暗号化プロトコルには使用しません。 復号化は、リプレイ攻撃に対しても同様に脆弱です。

ECBモードでデータを暗号化するには、次を使用します。

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
ciphertext = cipher.doFinal(data);

ECBモードでデータを復号化するには、次のように記述します。

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
plaintext = cipher.doFinal(cipherText);

IVは使用していないため、同じ平文でも同じ暗号文になり、攻撃に対して脆弱になります。 ECBモードは最も脆弱ですが、それでも多くのプロバイダーのデフォルトの暗号化モードです。 したがって、暗号化モードを明示的に設定することについて、より注意を払う必要があります。

4.2. サイバーブロックチェーン(CBC)モード

サイバーブロック連鎖モードは、IVを使用して、同じ平文が同じ暗号文になるのを防ぎます。 IVが確実にランダムまたは一意になるように注意する必要があります。 それ以外の場合は、ECBモードと同じ脆弱性があります

getIVSecureRandomを使用してランダムIVを取得しましょう。

IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES");

まず、IVを使用して、CBCモードを使用してデータを暗号化します。

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);

次に、復号化にIvParameterSpecオブジェクトを使用して同じIVを渡します。

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

4.3. サイバーフィードバック(CFB)モード

サイバーフィードバックモードは最も基本的なストリーミングモードです。 これは、自己同期型のストリーム暗号のようなものです。 CBCモードとは異なり、ここではパディングは必要ありません。 CFBモードでは、暗号によって生成されるストリームのソースとしてIVを使用します。 繰り返しますが、異なる暗号化に同じIVを使用すると、暗号文に類似点が現れる可能性があります。 ここでも、CBCモードと同様に、IVはランダムである必要があります。 IVが予測可能である場合、機密性を失います。

CFBモードのランダムIVを生成してみましょう。

IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES/CFB/NoPadding");

もう1つの極端なケースは、すべてゼロのIVを使用する場合、CFB-8モードでは、一部のキーがすべてゼロのIVとすべてゼロのプレーンテキストを生成する場合があります。 この場合、1/256キーは暗号化を生成しません。 これにより、平文が暗号文として返されます。

CBCおよびCFBモードの場合、IVを再利用すると、2つのメッセージで共有される共通ブロックに関する情報が明らかになります。

4.4. カウンター(CTR) および出力フィードバック(OFB)モード

カウンターモード出力フィードバックモードは、ブロック暗号を同期ストリーム暗号にします。 各モードはキーストリームブロックを生成します。 この場合、特定のIVで暗号を初期化します。 これは主に、IVに12バイト、カウンターに4バイトを割り当てるために行います。 このようにして、長さ2^32ブロックのメッセージを暗号化できます。

ここで、IVを作成しましょう。

IvParameterSpec ivSpec = CryptoUtils.getIVSecureRandom("AES");

CTRモードの場合、 最初のビットストリームはIVとキーに依存します。 ここでも、IVを再利用すると、キービットストリームが再利用されます。 これにより、セキュリティが破られます

IVが一意でない場合、カウンターは、繰り返されるカウンターブロックに対応するブロックに期待される機密性を提供できない可能性があります。 ただし、他のデータブロックは影響を受けません。

4.5. ガロア/カウンター(GCM)モード

Galois / Counterモードは、暗号化のAEADモードです。 カウンターモード暗号化と認証メカニズムを組み合わせています。 また、プレーンテキストと追加の認証済みデータ(AAD)の両方を保護します。

ただし、GCMでのこの認証は、IVの一意性に依存します。 IVとしてナンスを使用します。 IVを1つでも繰り返す場合、実装は攻撃に対して脆弱である可能性があります。

GCMは暗号化にAESを使用するため、IVまたはカウンターは16バイトです。 したがって、最初の12バイトをIVとして使用し、最後の4バイトのナンスをカウンターとして使用します。

GCMモードでIVを作成するには、GCMParameterSpecを設定する必要があります。 IVを作成しましょう:

byte[] iv = CryptoUtils.getRandomIVWithSize(12);

まず、 Cipher のインスタンスを取得し、IVを使用して初期化します。

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));

次に、復号化のためにCipherをIVで作成して初期化します。

cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));

ここでも、一意のIVが必要です。そうでない場合は、平文を解読できます。

4.6. IVの要約

次の表は、さまざまなモードに必要なIVのタイプをまとめたものです。

これまで見てきたように、同じキーでIVを再利用すると、セキュリティが失われます。 可能であれば、GCMなどのより高度なモードを使用する必要があります。 また、CCMなどの一部のモードは、標準のJCEディストリビューションでは使用できません。 この場合、 Bouncy CastleAPIを使用して実装できます。

5. 結論

この記事では、さまざまな暗号化モードでIVを使用する方法を示しました。 また、IVを使用する際の問題とベストプラクティスについても説明しました。

いつものように、GitHubでソースコードを見つけることができます。