From 5f1316330573dac6f0632ad73a3640a950d1321a Mon Sep 17 00:00:00 2001 From: tanishq-chugh Date: Tue, 6 Jan 2026 01:48:17 +0530 Subject: [PATCH 1/2] HIVE-29392: Add Support for CTR and GCM cipher transformations in AES UDFs --- .../org/apache/hadoop/hive/conf/HiveConf.java | 9 ++- .../ql/udf/generic/GenericUDFAesBase.java | 70 +++++++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 4a4a470c0d1f..9cfae4aa3abc 100644 --- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -5860,7 +5860,14 @@ public static enum ConfVars { HIVE_OTEL_RETRY_BACKOFF_MULTIPLIER("hive.otel.retry.backoff.multiplier", 5f, "The multiplier applied to the backoff interval for retries in the OTEL exporter." - + "This determines how much the backoff interval increases after each failed attempt, following an exponential backoff strategy."); + + "This determines how much the backoff interval increases after each failed attempt, following an exponential backoff strategy."), + + HIVE_UDF_AES_CIPHER_TRANSFORMATION("hive.udf.aes.cipher.transformation", "AES", + new StringSet("AES", "AES/CTR/NoPadding", "AES/GCM/NoPadding"), + "This specifies the cipher transformation used by AES UDFs." + + "The default is the Electronic Codebook transformation (AES/ECB/PKCS5Padding) which encrypts each block " + + "independently whereas Counter (CTR) & Galois/Counter Mode (GCM) provide stronger security using a " + + "generated Initialization Vector (IV)."); public final String varname; public final String altName; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java index 0311b4f500ba..b86f0af56558 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java @@ -22,14 +22,20 @@ import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.ql.exec.UDFArgumentException; import org.apache.hadoop.hive.ql.metadata.HiveException; +import org.apache.hadoop.hive.ql.session.SessionState; import org.apache.hadoop.hive.serde2.objectinspector.ConstantObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorConverters.Converter; @@ -52,6 +58,13 @@ public abstract class GenericUDFAesBase extends GenericUDF { protected transient boolean isKeyConstant; protected transient Cipher cipher; protected transient SecretKey secretKey; + private boolean isGCM; + private boolean isCTR; + private static final int GCM_IV_LENGTH = 12; + private static final int CTR_IV_LENGTH = 16; + private static final int GCM_TAG_LENGTH = 128; + private static final String AES_GCM_NOPADDING = "AES/GCM/NoPadding"; + private static final String AES_CTR_NOPADDING = "AES/CTR/NoPadding"; @Override public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { @@ -104,8 +117,14 @@ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumen secretKey = getSecretKey(key, keyLength); } + HiveConf hiveConf = SessionState.getSessionConf(); + String cipherTransform = HiveConf.getVar(hiveConf, HiveConf.ConfVars.HIVE_UDF_AES_CIPHER_TRANSFORMATION); + + this.isGCM = AES_GCM_NOPADDING.equalsIgnoreCase(cipherTransform); + this.isCTR = AES_CTR_NOPADDING.equalsIgnoreCase(cipherTransform); + try { - cipher = Cipher.getInstance("AES"); + cipher = Cipher.getInstance(cipherTransform); } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } @@ -186,9 +205,52 @@ protected SecretKey getSecretKey(byte[] key, int keyLength) { protected byte[] aesFunction(byte[] input, int inputLength, SecretKey secretKey) { try { - cipher.init(getCipherMode(), secretKey); - byte[] res = cipher.doFinal(input, 0, inputLength); - return res; + if (isGCM || isCTR) { + int ivLen = isGCM ? GCM_IV_LENGTH : CTR_IV_LENGTH; + + if (getCipherMode() == Cipher.ENCRYPT_MODE) { + byte[] iv = new byte[ivLen]; + new SecureRandom().nextBytes(iv); + + AlgorithmParameterSpec paramSpec; + if (isGCM) { + paramSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); + } else { + paramSpec = new IvParameterSpec(iv); + } + + cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); + byte[] cipherText = cipher.doFinal(input, 0, inputLength); + + byte[] output = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, output, 0, iv.length); + System.arraycopy(cipherText, 0, output, iv.length, cipherText.length); + + return output; + + } else { + int minLen = isGCM ? (ivLen + (GCM_TAG_LENGTH / 8)) : (ivLen + 1); + if (inputLength < minLen) { + return null; + } + + byte[] iv = new byte[ivLen]; + System.arraycopy(input, 0, iv, 0, ivLen); + + AlgorithmParameterSpec paramSpec; + if (isGCM) { + paramSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); + } else { + paramSpec = new IvParameterSpec(iv); + } + + cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); + return cipher.doFinal(input, ivLen, inputLength - ivLen); + } + } else { + cipher.init(getCipherMode(), secretKey); + return cipher.doFinal(input, 0, inputLength); + } } catch (GeneralSecurityException e) { return null; } From c4584d23fc8738273ab6227fd7036eb57e9b94dc Mon Sep 17 00:00:00 2001 From: tanishq-chugh Date: Wed, 7 Jan 2026 22:45:09 +0530 Subject: [PATCH 2/2] Update Logic to have a third optional UDF argument instead of hive configuration --- .../org/apache/hadoop/hive/conf/HiveConf.java | 9 +-------- .../ql/udf/generic/GenericUDFAesBase.java | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 9cfae4aa3abc..4a4a470c0d1f 100644 --- a/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ b/common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -5860,14 +5860,7 @@ public static enum ConfVars { HIVE_OTEL_RETRY_BACKOFF_MULTIPLIER("hive.otel.retry.backoff.multiplier", 5f, "The multiplier applied to the backoff interval for retries in the OTEL exporter." - + "This determines how much the backoff interval increases after each failed attempt, following an exponential backoff strategy."), - - HIVE_UDF_AES_CIPHER_TRANSFORMATION("hive.udf.aes.cipher.transformation", "AES", - new StringSet("AES", "AES/CTR/NoPadding", "AES/GCM/NoPadding"), - "This specifies the cipher transformation used by AES UDFs." - + "The default is the Electronic Codebook transformation (AES/ECB/PKCS5Padding) which encrypts each block " - + "independently whereas Counter (CTR) & Galois/Counter Mode (GCM) provide stronger security using a " - + "generated Initialization Vector (IV)."); + + "This determines how much the backoff interval increases after each failed attempt, following an exponential backoff strategy."); public final String varname; public final String altName; diff --git a/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java b/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java index b86f0af56558..92ce86068d39 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/udf/generic/GenericUDFAesBase.java @@ -50,8 +50,8 @@ * */ public abstract class GenericUDFAesBase extends GenericUDF { - protected transient Converter[] converters = new Converter[2]; - protected transient PrimitiveCategory[] inputTypes = new PrimitiveCategory[2]; + protected transient Converter[] converters = new Converter[3]; + protected transient PrimitiveCategory[] inputTypes = new PrimitiveCategory[3]; protected final BytesWritable output = new BytesWritable(); protected transient boolean isStr0; protected transient boolean isStr1; @@ -68,7 +68,7 @@ public abstract class GenericUDFAesBase extends GenericUDF { @Override public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException { - checkArgsSize(arguments, 2, 2); + checkArgsSize(arguments, 2, 3); checkArgPrimitive(arguments, 0); checkArgPrimitive(arguments, 1); @@ -117,8 +117,18 @@ public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumen secretKey = getSecretKey(key, keyLength); } - HiveConf hiveConf = SessionState.getSessionConf(); - String cipherTransform = HiveConf.getVar(hiveConf, HiveConf.ConfVars.HIVE_UDF_AES_CIPHER_TRANSFORMATION); + String cipherTransform = "AES"; + + if (arguments.length == 3) { + checkArgPrimitive(arguments, 2); + checkArgGroups(arguments, 2, inputTypes, STRING_GROUP); + + String userDefinedMode = getConstantStringValue(arguments, 2); + if (userDefinedMode != null) { + cipherTransform = userDefinedMode; + } + + } this.isGCM = AES_GCM_NOPADDING.equalsIgnoreCase(cipherTransform); this.isCTR = AES_CTR_NOPADDING.equalsIgnoreCase(cipherTransform);