Вот фрагмент кода, который я привел на основе советов и примеров, упомянутых в вопросе и вокруг него. Я не публикую import
раздел, но это тривиально. Также я не имею дело с тегом аутентификации данных (последние 16 байтов потока), так как клиент знает длину контента. Да, я знаю, что игнорировать тег - это плохо, но мне нужен как потоковый, так и произвольный доступ. В конце концов, никто не мешает мне использовать другой метод расшифровки, когда мне не нужен произвольный доступ (что на самом деле имеет место).
Метод createGcmStreamDecryptor()
(общедоступный) блочный шифр результатов, который на самом деле является шифрованием CTR AES В качестве входных данных он принимает IV, предназначенный для шифра GCM, и соответствующим образом преобразует его. В моем случае длина IV составляет 16 байт, но она будет работать на всем, где работает подход Bouncy Castle. Я использовал БК как можно чаще, включая GCMUtil
класс.
// AES block size in bytes.
private static final int AES_BLOCK_SIZE = 16;
// Default (recommended) GCM IV size.
private static final int GCM_DEFAULT_IV_SIZE = 12;
// Perform 'inc32' operation on CTR counter.
private static byte inc32(byte[] counter) {
for (int i = counter.length - 1; i >= 0; i--) {
if (++counter[i] != 0) {
return 0;
}
}
return 1;
}
// Get GCM gHASH function result.
private static void gHASHPartial(
final GCMMultiplier multiplier, byte[] Y, byte[] b, int off, int len) {
GCMUtil.xor(Y, b, off, len);
multiplier.multiplyH(Y);
}
// Get GCM gHASH function result.
private static void gHASHBlock(
final GCMMultiplier multiplier, byte[] Y, byte[] b) {
GCMUtil.xor(Y, b);
multiplier.multiplyH(Y);
}
// Get GCM gHASH function result.
private static void gHASH(
final GCMMultiplier multiplier, byte[] Y, byte[] b, int len) {
for (int pos = 0; pos < len; pos += AES_BLOCK_SIZE)
{
final int num = Math.min(len - pos, AES_BLOCK_SIZE);
gHASHPartial(multiplier, Y, b, pos, num);
}
}
// Convert GCM initialization vector into appropriate CTR one
// so our CTR-based 'GCM decryptor' works.
// This is based on Bouncy Castle GCM block cipher implementation
// in accordance with NIST 800-38D Nov 2007 document.
private static byte[] createGcmStreamDecryptorIv(
final AESEngine aes,
byte[] gcmIv) {
final byte [] J0 = new byte[AES_BLOCK_SIZE];
if (gcmIv.length == GCM_DEFAULT_IV_SIZE) {
// In case of 12 bytes IV ieverything is simple.
System.arraycopy(gcmIv, 0, J0, 0, gcmIv.length);
J0[AES_BLOCK_SIZE - 1] = 0x01;
} else {
// For other sizes it is much more complex.
// We need to init GCM multiplier based on given
// (already initialized) AES cipher.
// Pay attention GCMMultiplier tables don't change
// unless the key changes.
final byte [] H = new byte[AES_BLOCK_SIZE];
aes.processBlock(H, 0, H, 0);
final GCMMultiplier multiplier = new Tables4kGCMMultiplier();
multiplier.init(H);
final byte [] nonce = new byte[AES_BLOCK_SIZE];
System.arraycopy(gcmIv, 0, nonce, 0, gcmIv.length);
gHASH(multiplier, J0, nonce, nonce.length);
final byte[] X = new byte[AES_BLOCK_SIZE];
Pack.longToBigEndian((long)gcmIv.length * 8, X, 8);
gHASHBlock(multiplier, J0, X);
}
inc32(J0);
return J0;
}
/**
* Create streaming block cipher to decrypt AES/GCM data.
* Actually we are taking parameters of AES/GCM encryption
* and construct CTR (SIC) cipher with converted IV to get stream
* skipping ability.
* @param key Decrypted file encryption key.
* @param iv GCM cipher initialization vector.
* @return Streaming (actually AES/CTR) cipher to decrypt file stream
*/
public static StreamBlockCipher createGcmStreamDecryptor(
final SecretKey key,
final byte[] iv) {
try {
// AES cipher is required both as basis for SIC/CTR cipher
// and for IV conversion.
final AESEngine aes = new AESEngine();
aes.init(true, new KeyParameter(key.getEncoded()));
// We convert GCM IV into appropriate CTR IV.
byte[] ctrIv = createGcmStreamDecryptorIv(aes, iv);
// Now resulting SIC cipher can be created and initialized.
StreamBlockCipher c = new SICBlockCipher(aes);
c.init(false, new ParametersWithIV(null, ctrIv));
return c;
} catch (final Exception e) {
throw new RuntimeException(e);
}
}