2

AES/GCM/NoPadding でエンコードされたデータを読み取る方法を見つけようとしています。私が扱っているデータは任意に大きくなり、チャンクで読み取ることを望んでいますが、それがどのように達成されるかを理解するのに苦労しています. 私が今いる場所の例を次に示します。

@Test
public void chunkDecrypt() throws Exception {
    key = MessageDigest.getInstance("MD5").digest("som3C0o7p@s5".getBytes());
    iv = Hex.decode("EECE34808EF2A9ACE8DF72C9C475D751");
    byte[] ciphertext = Hex
            .decode("EF26839493BDA6DA6ABADD575262713171F825F2F477FDBB53029BEADB41928EA5FB46737D7A94D5BE74B6049008443664F0E0D883943D0EFBEA09DB");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));

    byte[] fullDecryptedPlainText = cipher.doFinal(ciphertext);
    assertThat(new String(fullDecryptedPlainText),
            is("The quick brown fox jumps over the lazy dogs"));

    byte[] first32 = Arrays.copyOfRange(ciphertext, 0, 32);
    byte[] final28 = Arrays.copyOfRange(ciphertext, 32, 60);
    byte[] decryptedChunk = new byte[32];

    int num = cipher.update(first32, 0, 32, decryptedChunk);
    assertThat(num, is(16));
    assertThat(new String(decryptedChunk, 0, 16), is("The quick brown "));

    num = cipher.update(first32, 0, 32, decryptedChunk);
    assertThat(num, is(32));
    assertThat(new String(decryptedChunk, 0, 16), is("fox jumps over t"));

    num = cipher.update(final28, 0, 24, decryptedChunk);
    assertThat(num, is(44));
    assertThat(new String(decryptedChunk, 0, 12), is("he lazy dogs"));
}

最初の assert を問題なく通過したので、データを一度にデコードできることに注意してください。また、次の 2 セットのアサート (最初の 32 バイトを 16 バイトのチャンクにデコード) は「正しく」動作しますが、試行錯誤の末にこの式にたどり着きました。それらについて、私が理解できないことがいくつかあります。

  • 16 バイトのチャンクで読み取っていますが、数値はすべて 32 の倍数である必要があるようです。次のコードに変更すると、cipher.update() の最初の呼び出しが失敗し、戻り値 0 が返されます。

    byte[] first16 = Arrays.copyOfRange(ciphertext, 0, 16);
    byte[] decryptedChunk = new byte[16];
    
    int num = cipher.update(first16, 0, 16, decryptedChunk);
    
  • 入力側を 32 に戻し、16 バイトの出力バッファーを使用すると、最初の呼び出しは成功し、期待されるデータが返されますが、cipher.update() の 2 回目の呼び出しで ArrayIndexOutOfBoundsException がスローされます。

    byte[] first32 = Arrays.copyOfRange(ciphertext, 0, 32);
    byte[] decryptedChunk = new byte[16];
    
    int num = cipher.update(first32, 0, 32, decryptedChunk);
    num = cipher.update(first32, 0, 32, decryptedChunk);
    
  • したがって、コードを元の例 (decryptedChunk のサイズは 32 バイト) に戻すと、3 回目の cipher.update() の呼び出しで 16 の値が返され (どういう意味ですか?)、decryptedChunk にはガベージ データが含まれます。

  • また、最後の cipher.update() の呼び出しを cipher.doFinal() の呼び出しに置き換えてみました。

    decryptedChunk = cipher.doFinal(final28);
    assertThat(new String(decryptedChunk, 0, 12), is("he lazy dogs"));
    

しかし、これは BadPaddingException で失敗します (GCM での mac チェックに失敗しました)。

助言がありますか?


ソリューションで更新

Ebbe M. Pedersen から提案されたコードでいくつか遊んだ後、次の解決策をまとめることができました。

@Test
public void chunkDecrypt() throws Exception {
    byte[] key = MessageDigest.getInstance("MD5").digest("som3C0o7p@s5".getBytes());
    byte[] iv = Hex.decode("EECE34808EF2A9ACE8DF72C9C475D751");
    byte[] ciphertext = Hex
            .decode("EF26839493BDA6DA6ABADD575262713171F825F2F477FDBB53029BEADB41928EA5FB46737D7A94D5BE74B6049008443664F0E0D883943D0EFBEA09DB");

    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));

    int chunkSize = 16;
    byte[] inBuffer = new byte[chunkSize];
    int outBufferSize = ((chunkSize + 15) / 16) * 16;
    byte[] outBuffer = new byte[outBufferSize];

    for (int i = 0; i < ciphertext.length; i += chunkSize) {
        int thisChunkSize = Math.min(chunkSize, ciphertext.length - i);
        System.arraycopy(ciphertext, i, inBuffer, 0, thisChunkSize);
        int num = cipher.update(inBuffer, 0, thisChunkSize, outBuffer);
        if (num > 0) {
            logger.debug("update #" + ((i / chunkSize) + 1) + " - data <"
                    + new String(outBuffer, 0, num) + ">");
        }
    }
    int num = cipher.doFinal(inBuffer, chunkSize, 0, outBuffer);
    logger.debug("doFinal - data <" + new String(outBuffer, 0, num) + ">");
}

これは、選択した任意の値に対して適切に機能しますchunkSize。その回答を承認済みとしてマークしました。助けてくれてありがとう。

4

1 に答える 1

3

ブロック暗号 [ed: in Bouncy Castle] には、更新を続ける内部バッファーがあり、完全なブロックに十分なデータがある場合にのみ、復号化が行われ、復号化されたデータのチャンクが返されます。

次のように一度に 1 バイトずつ復号化してみると、これを見ることができます。

    byte[] buffer = new byte[32];
    for (int i = 0; i < ciphertext.length; i++) {
        int num = cipher.update(ciphertext, i, 1, buffer);
        if (num > 0) {
            System.out.println("update #" + (i + 1) + " - data <" + new String(buffer, 0, num) + ">");
        }
    }
    int num = cipher.doFinal(ciphertext, ciphertext.length, 0, buffer);
    System.out.println("doFinal - data <" + new String(buffer, 0, num) + ">");

これにより、暗号化されたデータで次の出力が得られます。

update #32 - data <The quick brown >
update #48 - data <fox jumps over t>
doFinal - data <he lazy dogs>

最後のデータを取得するには、doFinal() を実行する必要があることに注意してください。


これは、少なくともバージョン 1.50 までの Bouncy Castle 実装に固有のものであることに注意してください。CTR モードでは、データの暗号化/復号化に使用されるキー ストリームのブロックを事前に計算できます (OTP 暗号化の類似物である XOR による)。したがって、原則として、各バイトまたはビットを独自に暗号化/復号化できます。

于 2014-06-20T20:07:27.640 に答える