Codreanu George
Codreanu George

Reputation: 65

How can I decrypt using BouncyCastle, a string using a GCM Tag , IV string and Key string, all of them in HEX?

I amn trying to replicate the AES-Decrypt function from CyberChef, using GCM mode and HEX input. Screenshot of the operation:

CyberChefAesDecrypt

So far, I've written the following code:

package decryption;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;

public class Main {
    public static final int GCM_TAG_LENGTH = 32;

    public static void main(String[] args) throws Exception {
        String IV = "9092d522e11120919fce8492";
        String input = "90fab0";
        String GCMTag = "02883e111ad6f79cd53674b5f833abab";
        String key = "8cda92dcb3283da821daa275359642c7a05d60a4badb5769618193a930c1cdec";

        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), 0, key.getBytes(StandardCharsets.UTF_8).length, "AES");
        System.out.println(decrypt(input.getBytes(StandardCharsets.UTF_8), secretKey, IV.getBytes(StandardCharsets.UTF_8)));
    }

    public static String decrypt(byte[] cipherText, SecretKeySpec key, byte[] IV) throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, IV);
        cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
        byte[] decryptedText = cipher.doFinal(cipherText);
        return new String(decryptedText);
    }
}

For the above code I am getting:

Exception in thread "main" org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$InvalidKeyOrParametersException: Invalid value for MAC size: 256
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(Unknown Source)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1442)
    at java.base/javax.crypto.Cipher.init(Cipher.java:1375)
    at decryption.Main.decrypt(Main.java:29)
    at decryption.Main.main(Main.java:19)

Fairly certain I am way off, but I did not find any articles/tutorials on how the GCM Tag can be used as input.

Upvotes: 3

Views: 3907

Answers (2)

Sumant Shanbag
Sumant Shanbag

Reputation: 59

While doing a little bit of research on CBC vs. GCM schemes of encrypting data, I stumbled upon this post and thought of doing the exercise myself. Hope it helps someone in the future.

From what I read, GCM is a much better and FASTER encryption scheme especially when it comes to avoiding 'parallel stall' problem with CBC scheme. GCM does not use any 'padding' (one less confusion point in having to choose between PKCS1Padding/PKCS5Padding/PKCS7Padding commonly used as AES/CBC padding schemes)

    @Test
    public void aesGcmNoPaddingTest() throws Exception {
        // Used (https://mvnrepository.com/artifact/org.projectlombok/lombok) for logging to console
        // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api for this test to run

        // Some constants (only the right thing to do)
        int AES_KEY_LENGTH = 32 * 8; // 256 bits
        int GCM_TAG_LENGTH = 16 * 8; // 128 bits
        int PBKDF2_ITERATIONS = 1_000_000; // 1 Million
        String KEY_GENERATION_ALGO = "PBKDF2WithHmacSHA256";
        String CIPHER_XFORMATION_SCHEME = "AES/GCM/NoPadding";

        // Generate AES-256 Bit key (https://mvnrepository.com/artifact/org.bouncycastle [bcpkix, bcprov])
        byte[] rawPassword = generateRandom(32); // 32 byte password
        byte[] rawSalt = generateRandom(16); // 16 byte salt (nounce)
        byte[] rawIv = generateRandom(12); // 96 bit IV (a GCM scheme recommendation)
        SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GENERATION_ALGO, BouncyCastleProvider.PROVIDER_NAME);
        KeySpec spec = new PBEKeySpec(new String(rawPassword).toCharArray(), rawSalt, PBKDF2_ITERATIONS, AES_KEY_LENGTH);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

        // Generate large arbitrary data (https://mvnrepository.com/artifact/net.datafaker/datafaker)
        StringBuilder dataBuilder = new StringBuilder();
        IntStream.rangeClosed(1, 1000).forEach(i -> {
            dataBuilder.append(faker.ancient().titan()).append(" ");
        });
        String clearText = dataBuilder.toString();

        // Init Encryption Cipher and encrypt data
        Cipher encCipher = Cipher.getInstance(CIPHER_XFORMATION_SCHEME, BouncyCastleProvider.PROVIDER_NAME);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, rawIv);
        encCipher.init(Cipher.ENCRYPT_MODE, secret, gcmParameterSpec);
        byte[] cipherBytes = encCipher.doFinal(clearText.getBytes());
        log.info("GCM encrypted: {}", Hex.toHexString(cipherBytes));

        // Init Decryption Cipher and decrypt data
        Cipher decCipher = Cipher.getInstance(CIPHER_XFORMATION_SCHEME, BouncyCastleProvider.PROVIDER_NAME);
        GCMParameterSpec gcmParameterSpec1 = new GCMParameterSpec(GCM_TAG_LENGTH, rawIv);
        decCipher.init(Cipher.DECRYPT_MODE, secret, gcmParameterSpec1);
        byte[] decryptedCipherBytes = decCipher.doFinal(cipherBytes);
        log.info("GCM decrypted: {}", new String(decryptedCipherBytes));
    }

    private byte[] generateRandom(int byteCount) {
        SecureRandom randomNumberGenerator = new SecureRandom();
        byte bytes[] = new byte[byteCount];
        randomNumberGenerator.nextBytes(bytes);
        return bytes;
    }    

Upvotes: -1

Topaco
Topaco

Reputation: 49425

The length of the GCM tag used here is not 32, but 16 bytes.

Furthermore, the BC provider expects ciphertext and tag in concatenated form (ciphertext|tag).

And you have to hex decode key, IV, ciphertext and tag. Since you are running BouncyCastle, you can use org.bouncycastle.util.encoders.Hex.decode(...).

Overall:

import org.bouncycastle.util.encoders.Hex;
...
public static final int GCM_TAG_LENGTH = 16;
... 
SecretKeySpec secretKey = new SecretKeySpec(Hex.decode(key), "AES");
System.out.println(decrypt(Hex.decode(input + GCMTag), secretKey, Hex.decode(IV))); // 985

It is not clear from your code whether you are using a static IV/nonce. If so, you should be aware that using a static IV/nonce for GCM is a serious problem, s. e.g. here.
Instead, for each encryption, a random (non-secret) IV/nonce is generated, concatenated with the ciphertext (and tag), and sent together to the decrypting side, which can separate the IV/nonce based on the known IV/nonce size (12 bytes for GCM).


AES/GCM is also supported by the SunJCE provider (at least as of Java 8, s. e.g. here), so you may not need BouncyCastle. For hex decoding you can then use a solution from this post. Starting with Java 17 there is a built-in support.

Upvotes: 3

Related Questions