AES-GCM encryption and decryption for Python, Java, and Typescript

Bharath R
3 min readJun 25, 2023

--

AES-GCM is a block cipher mode of operation that provides high speed of authenticated encryption and data integrity. Todays, the level of privacy protection is insufficient and make the data is been hacked easily.

Java:

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;

public class GCM {

private static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String FACTORY_INSTANCE = "PBKDF2WithHmacSHA512";
private static final int TAG_LENGTH = 16;
private static final int IV_LENGTH = 12;
private static final int SALT_LENGTH = 16;
private static final int KEY_LENGTH = 32;
private static final int ITERATIONS = 65535;

public static void main(String[] args) throws Exception {
System.out.println("AES GCMC 256 String decryption with PBKDF2 derived key");

String masterKey = "your_secure_key";
String encryptedData = encrypt(masterKey, "Your_plain_text");
System.out.println("Encrypted: " + encryptedData);
String decryptedText = decrypt(encryptedData, masterKey);
System.out.println("Decrypted: " + decryptedText);
}

private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return secret;
}

public static String decrypt(String cipherContent, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherContent.getBytes(UTF_8));
ByteBuffer byteBuffer = ByteBuffer.wrap(decode);

byte[] salt = new byte[SALT_LENGTH];
byteBuffer.get(salt);

byte[] iv = new byte[IV_LENGTH];
byteBuffer.get(iv);

byte[] content = new byte[byteBuffer.remaining()];
byteBuffer.get(content);

Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
SecretKey aesKeyFromPassword = getAESKeyFromPassword(password.toCharArray(), salt);
cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH * 8, iv));
byte[] plainText = cipher.doFinal(content);
return new String(plainText, UTF_8);
}

public static String encrypt(String password, String plainMessage) throws Exception {
byte[] salt = getRandomNonce(SALT_LENGTH);
SecretKey secretKey = getSecretKey(password, salt);

byte[] iv = getRandomNonce(IV_LENGTH);

Cipher cipher = initCipher(Cipher.ENCRYPT_MODE, secretKey, iv);

byte[] encryptedMessageByte = cipher.doFinal(plainMessage.getBytes(UTF_8));

byte[] cipherByte = ByteBuffer.allocate(salt.length + iv.length + encryptedMessageByte.length)
.put(salt)
.put(iv)
.put(encryptedMessageByte)
.array();
return Base64.getEncoder().encodeToString(cipherByte);
}

public static byte[] getRandomNonce(int length) {
byte[] nonce = new byte[length];
new SecureRandom().nextBytes(nonce);
return nonce;
}

public static SecretKey getSecretKey(String password, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH * 8);

SecretKeyFactory factory = SecretKeyFactory.getInstance(FACTORY_INSTANCE);
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}

private static Cipher initCipher(int mode, SecretKey secretKey, byte[] iv) throws InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(mode, secretKey, new GCMParameterSpec(TAG_LENGTH * 8, iv));
return cipher;
}

}

Python:

import base64
import hashlib

from Cryptodome.Cipher import AES # from pycryptodomex v-3.10.4
from Cryptodome.Random import get_random_bytes

HASH_NAME = "SHA512"
IV_LENGTH = 12
ITERATION_COUNT = 65535
KEY_LENGTH = 32
SALT_LENGTH = 16
TAG_LENGTH = 16


def encrypt(password, plain_message):
salt = get_random_bytes(SALT_LENGTH)
iv = get_random_bytes(IV_LENGTH)

secret = get_secret_key(password, salt)

cipher = AES.new(secret, AES.MODE_GCM, iv)

encrypted_message_byte, tag = cipher.encrypt_and_digest(
plain_message.encode("utf-8")
)
cipher_byte = salt + iv + encrypted_message_byte + tag

encoded_cipher_byte = base64.b64encode(cipher_byte)
return bytes.decode(encoded_cipher_byte)


def decrypt(password, cipher_message):
decoded_cipher_byte = base64.b64decode(cipher_message)

salt = decoded_cipher_byte[:SALT_LENGTH]
iv = decoded_cipher_byte[SALT_LENGTH : (SALT_LENGTH + IV_LENGTH)]
encrypted_message_byte = decoded_cipher_byte[
(IV_LENGTH + SALT_LENGTH) : -TAG_LENGTH
]
tag = decoded_cipher_byte[-TAG_LENGTH:]
secret = get_secret_key(password, salt)
cipher = AES.new(secret, AES.MODE_GCM, iv)

decrypted_message_byte = cipher.decrypt_and_verify(encrypted_message_byte, tag)
return decrypted_message_byte.decode("utf-8")


def get_secret_key(password, salt):
return hashlib.pbkdf2_hmac(
HASH_NAME, password.encode(), salt, ITERATION_COUNT, KEY_LENGTH
)


outputFormat = "{:<25}:{}"
secret_key = "your_secure_key"
plain_text = "Your_plain_text"

print("------ AES-GCM Encryption ------")
cipher_text = encrypt(secret_key, plain_text)
print(outputFormat.format("encryption input", plain_text))
print(outputFormat.format("encryption output", cipher_text))

decrypted_text = decrypt(secret_key, cipher_text)

print("\n------ AES-GCM Decryption ------")
print(outputFormat.format("decryption input", cipher_text))
print(outputFormat.format("decryption output", decrypted_text))

Typescript:

import * as crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const SALT_LENGTH = 16;
const IV_LENGTH = 12;
const TAG_LENGTH = 16;
const KEY_LENGTH = 32;
const ITERATION = 65535;
const ENCRYPTED_POSITION = SALT_LENGTH + IV_LENGTH;

export class GCM {
constructor(private secret: string) { }

getKey(salt: Buffer) {
return crypto.pbkdf2Sync(this.secret, salt, ITERATION, KEY_LENGTH, 'sha512');
}

encrypt(plainText: string) {
const salt = crypto.randomBytes(SALT_LENGTH);
const iv = crypto.randomBytes(IV_LENGTH);

const key = this.getKey(salt);

const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(String(plainText), 'utf8'),
cipher.final(),
]);

const tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
}

decrypt(cipherText: string) {
const stringValue = Buffer.from(String(cipherText), 'base64');

const salt = stringValue.slice(0, SALT_LENGTH);
const iv = stringValue.slice(SALT_LENGTH, ENCRYPTED_POSITION);
const encrypted = stringValue.slice(ENCRYPTED_POSITION, stringValue.length - TAG_LENGTH);
const tag = stringValue.slice(-TAG_LENGTH);

const key = this.getKey(salt);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);

return decipher.update(encrypted) + decipher.final('utf8');
}
}

let secretKey = 'your_secure_key';
let plainText = 'Your_plain_text';

let gcm = new GCM(secretKey);

let encryptedValue = gcm.encrypt(plainText);
let decryptedvalue = gcm.decrypt(encryptedValue);

console.table({
encryptedValue,
decryptedvalue
});

--

--