Roman Nikitchenko
Roman Nikitchenko

Reputation: 13046

Can I decrypt GCM AES stream in Bouncy Castle using anything having SkippingCipher interface?

I have working AES GCM solution with Bouncy Castle (native API) exposing stream interface (CipherInputStream class). I know that GCM mode can be considered like CTR one so if I do not need authentication I should be able to decrypt stream from random place (if I know position) but what mode cipher can I use so it can decrypt AES/GCM stream and have SkippingCipherinterface?

Any related code example would be even better.

Upvotes: 1

Views: 1874

Answers (1)

Roman Nikitchenko
Roman Nikitchenko

Reputation: 13046

Here is the code fragment I have resulted based on the advice and examples referred in question and around it. I do not post import section but it is trivial. Also I do not deal with data authentication tag (last 16 bytes of the stream) as client knows the content length. Yes, I know ignoring tag is bad but I need both streaming and random access. At the end nobody prevents me from other decryption approach when I do not need random access (which is actually the case).

Method createGcmStreamDecryptor() (public one) results block cipher which is actually CTR cipher wrapping AES. As an input it takes IV intended for GCM cipher and converts it appropriately. In my case IV length is 16 bytes but it will work on anything where Bouncy Castle approach works. I have reused BC as much as possible including GCMUtil class.

// 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);
    }
}

Upvotes: 2

Related Questions