This is an automated email from the ASF dual-hosted git repository. yasith pushed a commit to branch feat/aes-gcm-encryption in repository https://gitbox.apache.org/repos/asf/airavata.git
commit f7a364faea2b775395ffc5f89cdc7f66a3ede4c6 Author: yasithdev <[email protected]> AuthorDate: Mon Mar 30 16:12:41 2026 -0400 security: upgrade credential encryption from AES/CBC to AES/GCM Replace AES/CBC/PKCS5Padding with AES/GCM/NoPadding for credential store encryption. The old code used a static zero IV which is insecure. GCM provides authenticated encryption with random IVs prepended to the ciphertext. Extracted from #556. --- .../credential/repository/db/CredentialsDAO.java | 10 ++-- .../airavata/security/util/SecurityUtil.java | 59 +++++++++------------- .../airavata/security/util/SecurityUtilTest.java | 13 +++-- 3 files changed, 35 insertions(+), 47 deletions(-) diff --git a/airavata-api/src/main/java/org/apache/airavata/credential/repository/db/CredentialsDAO.java b/airavata-api/src/main/java/org/apache/airavata/credential/repository/db/CredentialsDAO.java index 44f050c384..1757d03cbe 100644 --- a/airavata-api/src/main/java/org/apache/airavata/credential/repository/db/CredentialsDAO.java +++ b/airavata-api/src/main/java/org/apache/airavata/credential/repository/db/CredentialsDAO.java @@ -414,8 +414,9 @@ public class CredentialsDAO extends ParentDAO { try { // decrypt the data first if (encrypt()) { - data = SecurityUtil.decrypt( - this.keyStorePath, this.secretKeyAlias, this.keyStorePasswordCallback, data); + var key = SecurityUtil.getSymmetricKey( + this.keyStorePath, this.secretKeyAlias, this.keyStorePasswordCallback); + data = SecurityUtil.decrypt(data, key); } objectInputStream = new ObjectInputStream(new ByteArrayInputStream(data)); @@ -464,8 +465,9 @@ public class CredentialsDAO extends ParentDAO { if (encrypt()) { byte[] array = byteArrayOutputStream.toByteArray(); try { - return SecurityUtil.encrypt( - this.keyStorePath, this.secretKeyAlias, this.keyStorePasswordCallback, array); + var key = SecurityUtil.getSymmetricKey( + this.keyStorePath, this.secretKeyAlias, this.keyStorePasswordCallback); + return SecurityUtil.encrypt(array, key); } catch (GeneralSecurityException e) { throw new CredentialStoreException("Error encrypting data", e); } catch (IOException e) { diff --git a/airavata-api/src/main/java/org/apache/airavata/security/util/SecurityUtil.java b/airavata-api/src/main/java/org/apache/airavata/security/util/SecurityUtil.java index 5aa1049d0f..e3f2088d4b 100644 --- a/airavata-api/src/main/java/org/apache/airavata/security/util/SecurityUtil.java +++ b/airavata-api/src/main/java/org/apache/airavata/security/util/SecurityUtil.java @@ -20,10 +20,12 @@ package org.apache.airavata.security.util; import java.io.*; +import java.nio.ByteBuffer; import java.security.*; import java.security.cert.CertificateException; +import java.util.Arrays; import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.GCMParameterSpec; import org.apache.airavata.common.server.KeyStorePasswordCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,52 +37,37 @@ public class SecurityUtil { public static final String PASSWORD_HASH_METHOD_PLAINTEXT = "PLAINTEXT"; public static final String CHARSET_ENCODING = "UTF-8"; - public static final String PADDING_MECHANISM = "AES/CBC/PKCS5Padding"; + public static final String CIPHER_NAME = "AES/GCM/NoPadding"; + public static final int GCM_IV_BYTES = 12; // 96 bits + public static final int GCM_TAG_BITS = 128; private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class); - public static byte[] encryptString( - String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback, String value) - throws GeneralSecurityException, IOException { - return encrypt(keyStorePath, keyAlias, passwordCallback, value.getBytes(CHARSET_ENCODING)); - } - - public static byte[] encrypt( - String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback, byte[] value) - throws GeneralSecurityException, IOException { - - Key secretKey = getSymmetricKey(keyStorePath, keyAlias, passwordCallback); - - Cipher cipher = Cipher.getInstance(PADDING_MECHANISM); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); - return cipher.doFinal(value); - } - - private static Key getSymmetricKey(String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback) + public static Key getSymmetricKey(String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException { KeyStore ks = SecurityUtil.loadKeyStore(keyStorePath, passwordCallback); return ks.getKey(keyAlias, passwordCallback.getSecretKeyPassPhrase(keyAlias)); } - public static byte[] decrypt( - String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback, byte[] encrypted) - throws GeneralSecurityException, IOException { - - Key secretKey = getSymmetricKey(keyStorePath, keyAlias, passwordCallback); - - Cipher cipher = Cipher.getInstance(PADDING_MECHANISM); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); - - return cipher.doFinal(encrypted); + public static byte[] encrypt(byte[] data, Key key) throws GeneralSecurityException { + var cipher = Cipher.getInstance(CIPHER_NAME); + cipher.init(Cipher.ENCRYPT_MODE, key); + var iv = cipher.getIV(); + var encryptedData = cipher.doFinal(data); + return ByteBuffer.allocate(iv.length + encryptedData.length) + .put(iv) + .put(encryptedData) + .array(); } - public static String decryptString( - String keyStorePath, String keyAlias, KeyStorePasswordCallback passwordCallback, byte[] encrypted) - throws GeneralSecurityException, IOException { - - byte[] decrypted = decrypt(keyStorePath, keyAlias, passwordCallback, encrypted); - return new String(decrypted, CHARSET_ENCODING); + public static byte[] decrypt(byte[] tag, Key key) throws GeneralSecurityException { + var iv = Arrays.copyOfRange(tag, 0, GCM_IV_BYTES); + var encryptedData = Arrays.copyOfRange(tag, GCM_IV_BYTES, tag.length); + var cipher = Cipher.getInstance(CIPHER_NAME); + var spec = new GCMParameterSpec(GCM_TAG_BITS, iv); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + return cipher.doFinal(encryptedData); } public static KeyStore loadKeyStore(String keyStoreFilePath, KeyStorePasswordCallback passwordCallback) diff --git a/airavata-api/src/test/java/org/apache/airavata/security/util/SecurityUtilTest.java b/airavata-api/src/test/java/org/apache/airavata/security/util/SecurityUtilTest.java index a8ae9d48ff..e066198a9b 100644 --- a/airavata-api/src/test/java/org/apache/airavata/security/util/SecurityUtilTest.java +++ b/airavata-api/src/test/java/org/apache/airavata/security/util/SecurityUtilTest.java @@ -39,12 +39,10 @@ public class SecurityUtilTest { @Test public void testEncryptString() throws Exception { - String stringToEncrypt = "Test string to encrypt"; - byte[] encrypted = - SecurityUtil.encryptString(keyStorePath, "mykey", new TestKeyStoreCallback(), stringToEncrypt); - - String decrypted = SecurityUtil.decryptString(keyStorePath, "mykey", new TestKeyStoreCallback(), encrypted); + var key = SecurityUtil.getSymmetricKey(keyStorePath, "mykey", new TestKeyStoreCallback()); + byte[] encrypted = SecurityUtil.encrypt(stringToEncrypt.getBytes(StandardCharsets.UTF_8), key); + String decrypted = new String(SecurityUtil.decrypt(encrypted, key), StandardCharsets.UTF_8); assertEquals(stringToEncrypt, decrypted); } @@ -52,8 +50,9 @@ public class SecurityUtilTest { public void testEncryptBytes() throws Exception { String stringToEncrypt = "Test string to encrypt"; byte[] plaintext = stringToEncrypt.getBytes(StandardCharsets.UTF_8); - byte[] encrypted = SecurityUtil.encrypt(keyStorePath, "mykey", new TestKeyStoreCallback(), plaintext); - byte[] decrypted = SecurityUtil.decrypt(keyStorePath, "mykey", new TestKeyStoreCallback(), encrypted); + var key = SecurityUtil.getSymmetricKey(keyStorePath, "mykey", new TestKeyStoreCallback()); + byte[] encrypted = SecurityUtil.encrypt(plaintext, key); + byte[] decrypted = SecurityUtil.decrypt(encrypted, key); assertArrayEquals(plaintext, decrypted); }
