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

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