Skip to content

AESNativeGCM failed when buffer partially filled #2294

@sebastien-leveque

Description

@sebastien-leveque

Decrypt data failed in some cases with AESNativeGCM.

On linux (run in cloud foundry) data are retrieved from AWS S3.
This can not be reproduce on Mac, as native is not supported.
Provided test reproduce what is happening.

This issue has been mitigated by forcing java implementation by adding:

Security.setProperty("org.bouncycastle.native.cpu_variant", "java");
org.bouncycastle.crypto.internal.OutputLengthException: output len too short
	at org.bouncycastle.crypto.fips.AESNativeGCM.processBytes(Native Method)
	at org.bouncycastle.crypto.fips.AESNativeGCM.processBytes(Unknown Source)
	at org.bouncycastle.crypto.internal.io.CipherOutputStreamImpl$DirectAEADOutputStream.write(Unknown Source)
	at org.bouncycastle.crypto.UpdateOutputStream.update(Unknown Source)
	at org.bouncycastle.jcajce.provider.BaseCipher.engineUpdate(Unknown Source)
	at java.base/javax.crypto.Cipher.update(Cipher.java:1929)
	at org.bouncycastle.jcajce.io.CipherInputStream.nextChunk(Unknown Source)
	at org.bouncycastle.jcajce.io.CipherInputStream.read(Unknown Source)
	at java.base/java.io.InputStream.transferTo(InputStream.java:796)
  • bc-fips: 2.1.2
  • java: 21.0.11
  • spring boot: 3.5.14
package test;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

import org.bouncycastle.jcajce.io.CipherInputStream;
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
import org.junit.jupiter.api.Test;

public class BcFipsTest {

    static class ForceInputStream extends FilterInputStream {

        private int it = 0;

        public ForceInputStream(InputStream in) {
            super(in);
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            // Reproduce the way data are retrieved from S3
            it++;

            if (it == 16) {
                len = 487;
            } else if (it == 17) {
                len = 94;
            }

            return super.read(b, off, len);
        }
    }

    public static final Provider BC_PROVIDER = new BouncyCastleFipsProvider();

    @Test
    void forceChunk() throws Exception {
        // Failed with AESNativeGCM but not GCMBlockCipher
        fipsTest(16000, true);
    }

    @Test
    void defaultChunk() throws Exception {
        fipsTest(16000, false);
    }

    void fipsTest(
            int size,
            boolean force) throws Exception {

        byte[] salt = generateRandom(12);
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(128);
        SecretKey key = keyGenerator.generateKey();

        byte[] data = generateRandom(size);
        byte[] encryptedData = encrypt(salt, key, data);

        Cipher cipher = getCipher(salt, key, DECRYPT_MODE);

        try (
                InputStream bis = new ByteArrayInputStream(encryptedData);
                InputStream fis = force ? new ForceInputStream(bis) : bis;
                InputStream is = new CipherInputStream(fis, cipher)) {

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            is.transferTo(os);
            byte[] decryptedData = os.toByteArray();
            assertTrue(Arrays.equals(data, decryptedData));
        }
    }

    private static byte[] encrypt(byte[] salt, SecretKey key, byte[] data) throws Exception {
        Cipher cipher = getCipher(salt, key, ENCRYPT_MODE);
        return cipher.doFinal(data);
    }

    private static Cipher getCipher(byte[] salt, SecretKey key, int opmode) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", BC_PROVIDER);
        cipher.init(
                opmode,
                key,
                new GCMParameterSpec(128, salt));
        return cipher;
    }

    public static byte[] generateRandom(int length) throws Exception {
        byte[] randomBytes = new byte[length];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(randomBytes);
        return randomBytes;
    }

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions