This is an automated email from the ASF dual-hosted git repository.

shenghang pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 6af6ff4238 [Feature][Transform-V2] Support AES_GCM algorithm in 
FieldEncrypt (#10603)
6af6ff4238 is described below

commit 6af6ff4238c9c64ae059f6e67e5fe018c11f783a
Author: dy102 <[email protected]>
AuthorDate: Wed Mar 18 10:04:29 2026 +0900

    [Feature][Transform-V2] Support AES_GCM algorithm in FieldEncrypt (#10603)
---
 docs/en/transforms/encrypt.md                      |  13 ++-
 docs/zh/transforms/encrypt.md                      |  14 ++-
 .../field_decrypt_transform_multi_table.conf       |   1 +
 .../field_encrypt_transform_multi_table.conf       |   1 +
 .../transform/encrypt/FieldEncryptTransform.java   |   6 +-
 .../encrypt/FieldEncryptTransformConfig.java       |   7 +-
 .../encrypt/encryptor/AbstractAesEncryptor.java    |  55 +++++++++
 .../encrypt/encryptor/AesCbcEncryptor.java         |  38 +-----
 .../{AesCbcEncryptor.java => AesGcmEncryptor.java} |  61 +++-------
 .../encrypt/FieldEncryptTransformTest.java         |   9 +-
 .../encrypt/encryptor/AesGcmEncryptorTest.java     | 127 +++++++++++++++++++++
 11 files changed, 235 insertions(+), 97 deletions(-)

diff --git a/docs/en/transforms/encrypt.md b/docs/en/transforms/encrypt.md
index cfcd479d85..780fb6d27c 100644
--- a/docs/en/transforms/encrypt.md
+++ b/docs/en/transforms/encrypt.md
@@ -11,20 +11,27 @@ The Encrypt transform plugin is used to encrypt or decrypt 
specified fields in r
 | name        | type   | required | default value | description                
       |
 
|-------------|--------|----------|---------------|-----------------------------------|
 | `fields`    | Array  | Yes      | -             | List of fields to 
encrypt/decrypt |
-| `algorithm` | String | No       | `AES_CBC`     | Encryption algorithm       
       |
+| `algorithm` | String | No       | `AES_GCM`     | Encryption algorithm       
       |
 | `key`       | String | Yes      | -             | Base64-encoded encryption 
key     |
 | `mode`      | String | No       | `ENCRYPT`     | `ENCRYPT`or `DECRYPT`      
       |
 
 ### algorithm [string]
 
 Encryption algorithm used by this transform.
-Currently, only `AES_CBC` is supported.
+
+Supported values:
+- `AES_GCM`: default, AES in GCM mode with authentication tag
+- `AES_CBC`: AES in CBC mode with PKCS5 padding
+
+`AES_GCM` provides authenticated encryption and is recommended for better 
security.
+
+If not specified, `AES_GCM` is used by default.
 
 ### key [string]
 
 The encryption key must be provided in Base64-encoded format.
 Make sure the key length matches the requirements of the selected algorithm.
-For `AES_CBC`, valid key lengths are 16, 24, or 32 bytes (corresponding to 
AES-128, AES-192, or AES-256).
+For both `AES_GCM` and `AES_CBC`, valid key lengths are 16, 24, or 32 bytes 
(corresponding to AES-128, AES-192, or AES-256).
 
 **Example**
 - `base64:AAAAAAAAAAAAAAAAAAAAAA==`
diff --git a/docs/zh/transforms/encrypt.md b/docs/zh/transforms/encrypt.md
index 5600f6fb13..a4d64360da 100644
--- a/docs/zh/transforms/encrypt.md
+++ b/docs/zh/transforms/encrypt.md
@@ -17,16 +17,22 @@ Encrypt Transform 插件用于使用对称加密算法,对记录中指定的
 
 ### algorithm [string]
 
-本 Transform 使用的加密算法。
-目前仅支持 `AES_CBC`。
+用于指定该 transform 所使用的加密算法。
+
+支持的值:
+- `AES_GCM`:默认值。采用 GCM 模式并包含认证标签(Authentication Tag)的 AES 加密。
+- `AES_CBC`:采用 CBC 模式及 PKCS5 填充(Padding)的 AES 加密。
+
+`AES_GCM` 提供认证加密(Authenticated Encryption),安全性更高,推荐使用。
+
+如果未明确指定,系统将默认使用 `AES_GCM`。
 
 ### key [string]
 
 加密密钥必须以 Base64 编码格式提供。
 请确保密钥长度符合所选加密算法的要求。
 
-对于 `AES_CBC`,支持的密钥长度为 16、24 或 32 字节
-(分别对应 AES-128、AES-192 和 AES-256)。
+对于 `AES_GCM` 和 `AES_CBC`,支持的密钥长度为 16、24 或 32 字节 (分别对应 AES-128、AES-192 和 
AES-256)。
 
 **示例**
 
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_decrypt_transform_multi_table.conf
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_decrypt_transform_multi_table.conf
index 5fdb1c28c8..76e7c7957b 100644
--- 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_decrypt_transform_multi_table.conf
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_decrypt_transform_multi_table.conf
@@ -107,6 +107,7 @@ transform {
         table_path = "test.abc"
         fields = ["name", "address"]
         key = "base64:AAAAAAAAAAAAAAAAAAAAAA=="
+        algorithm = "AES_CBC"
         mode = "DECRYPT"
       }
     ]
diff --git 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_encrypt_transform_multi_table.conf
 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_encrypt_transform_multi_table.conf
index 3bb1b9a7c8..7250a14484 100644
--- 
a/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_encrypt_transform_multi_table.conf
+++ 
b/seatunnel-e2e/seatunnel-transforms-v2-e2e/seatunnel-transforms-v2-e2e-part-2/src/test/resources/field_encrypt_transform_multi_table.conf
@@ -107,6 +107,7 @@ transform {
         table_path = "test.abc"
         fields = ["name", "address"]
         key = "base64:AAAAAAAAAAAAAAAAAAAAAA=="
+        algorithm = "AES_CBC"
         mode = "ENCRYPT"
       }
     ]
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransform.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransform.java
index ee599d5b49..19b870eb59 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransform.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransform.java
@@ -17,8 +17,6 @@
 
 package org.apache.seatunnel.transform.encrypt;
 
-import org.apache.seatunnel.shade.org.apache.commons.lang3.StringUtils;
-
 import org.apache.seatunnel.api.configuration.ReadonlyConfig;
 import org.apache.seatunnel.api.table.catalog.CatalogTable;
 import org.apache.seatunnel.api.table.catalog.Column;
@@ -109,9 +107,7 @@ public class FieldEncryptTransform extends 
AbstractCatalogSupportMapTransform {
                         "Field length exceeds the maximum limit of " + 
maxFieldLength);
             }
 
-            if (StringUtils.isNotBlank(value)) {
-                outputRow.setField(index, action.apply(value));
-            }
+            outputRow.setField(index, action.apply(value));
         }
         return outputRow;
     }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformConfig.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformConfig.java
index 584f6490ed..fb07e5828c 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformConfig.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformConfig.java
@@ -19,7 +19,7 @@ package org.apache.seatunnel.transform.encrypt;
 
 import org.apache.seatunnel.api.configuration.Option;
 import org.apache.seatunnel.api.configuration.Options;
-import org.apache.seatunnel.transform.encrypt.encryptor.AesCbcEncryptor;
+import org.apache.seatunnel.transform.encrypt.encryptor.AesGcmEncryptor;
 
 import java.util.List;
 
@@ -33,8 +33,9 @@ public class FieldEncryptTransformConfig {
     public static final Option<String> ALGORITHM =
             Options.key("algorithm")
                     .stringType()
-                    .defaultValue(AesCbcEncryptor.IDENTIFIER)
-                    .withDescription("The encryption algorithm, support 
AES_CBC.");
+                    .defaultValue(AesGcmEncryptor.IDENTIFIER)
+                    .withDescription(
+                            "The encryption algorithm, Supported values: 
AES_CBC (default), AES_GCM");
 
     public static final Option<String> KEY =
             
Options.key("key").stringType().noDefaultValue().withDescription("The 
encryption key.");
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AbstractAesEncryptor.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AbstractAesEncryptor.java
new file mode 100644
index 0000000000..efa94208bb
--- /dev/null
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AbstractAesEncryptor.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.transform.encrypt.encryptor;
+
+import org.apache.seatunnel.common.exception.CommonError;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import java.util.Base64;
+
+public abstract class AbstractAesEncryptor implements Encryptor {
+    protected SecretKeySpec buildAesKey(String key) {
+        if (key == null || key.trim().isEmpty()) {
+            throw CommonError.illegalArgument(key, "Encryption key cannot be 
null or empty");
+        }
+
+        String base64 = key;
+        if (key.startsWith("base64:")) {
+            base64 = key.substring("base64:".length());
+        }
+        base64 = base64.trim();
+
+        byte[] keyBytes;
+        try {
+            keyBytes = Base64.getDecoder().decode(base64);
+        } catch (IllegalArgumentException e) {
+            throw CommonError.illegalArgument(key, "Invalid Base64 encoding in 
encryption key");
+        }
+
+        if (!(keyBytes.length == 16 || keyBytes.length == 24 || 
keyBytes.length == 32)) {
+            throw CommonError.illegalArgument(
+                    key,
+                    "Invalid AES key length: "
+                            + keyBytes.length
+                            + ". Expected 16, 24, or 32 bytes");
+        }
+
+        return new SecretKeySpec(keyBytes, "AES");
+    }
+}
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
index 33b137d5ed..5c04d8469b 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
@@ -31,7 +31,7 @@ import java.security.SecureRandom;
 import java.util.Base64;
 
 @AutoService(Encryptor.class)
-public class AesCbcEncryptor implements Encryptor {
+public class AesCbcEncryptor extends AbstractAesEncryptor {
     public static final String IDENTIFIER = "AES_CBC";
 
     private static final int IV_SIZE = 16;
@@ -63,7 +63,7 @@ public class AesCbcEncryptor implements Encryptor {
             cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
             encrypted = 
cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
         } catch (Exception e) {
-            throw TransformCommonError.encryptionError("plaintext length:" + 
plainText.length(), e);
+            throw TransformCommonError.encryptionError("Encryption failed", e);
         }
 
         byte[] encryptedWithIv = new byte[IV_SIZE + encrypted.length];
@@ -78,7 +78,7 @@ public class AesCbcEncryptor implements Encryptor {
         byte[] decoded = Base64.getDecoder().decode(cipherText);
         byte[] iv = new byte[IV_SIZE];
         if (decoded.length < IV_SIZE) {
-            throw CommonError.illegalArgument(cipherText, "Invalid encrypted 
value");
+            throw CommonError.illegalArgument(cipherText, "Invalid encrypted 
value (too short)");
         }
         byte[] encrypted = new byte[decoded.length - IV_SIZE];
 
@@ -93,39 +93,9 @@ public class AesCbcEncryptor implements Encryptor {
             cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
             original = cipher.doFinal(encrypted);
         } catch (Exception e) {
-            throw TransformCommonError.encryptionError(
-                    "ciphertext length:" + cipherText.length(), e);
+            throw TransformCommonError.encryptionError("Decryption failed", e);
         }
 
         return new String(original, StandardCharsets.UTF_8);
     }
-
-    private SecretKeySpec buildAesKey(String key) {
-        if (key == null || key.trim().isEmpty()) {
-            throw CommonError.illegalArgument(key, "Encryption key cannot be 
null or empty");
-        }
-
-        String base64 = key;
-        if (key.startsWith("base64:")) {
-            base64 = key.substring("base64:".length());
-        }
-        base64 = base64.trim();
-
-        byte[] keyBytes;
-        try {
-            keyBytes = Base64.getDecoder().decode(base64);
-        } catch (IllegalArgumentException e) {
-            throw CommonError.illegalArgument(key, "Invalid Base64 encoding in 
encryption key");
-        }
-
-        if (!(keyBytes.length == 16 || keyBytes.length == 24 || 
keyBytes.length == 32)) {
-            throw CommonError.illegalArgument(
-                    key,
-                    "Invalid AES key length: "
-                            + keyBytes.length
-                            + ". Expected 16, 24, or 32 bytes");
-        }
-
-        return new SecretKeySpec(keyBytes, "AES");
-    }
 }
diff --git 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptor.java
similarity index 62%
copy from 
seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
copy to 
seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptor.java
index 33b137d5ed..4980af24a7 100644
--- 
a/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesCbcEncryptor.java
+++ 
b/seatunnel-transforms-v2/src/main/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptor.java
@@ -23,7 +23,7 @@ import 
org.apache.seatunnel.transform.exception.TransformCommonError;
 import com.google.auto.service.AutoService;
 
 import javax.crypto.Cipher;
-import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
 import java.nio.charset.StandardCharsets;
@@ -31,12 +31,14 @@ import java.security.SecureRandom;
 import java.util.Base64;
 
 @AutoService(Encryptor.class)
-public class AesCbcEncryptor implements Encryptor {
-    public static final String IDENTIFIER = "AES_CBC";
+public class AesGcmEncryptor extends AbstractAesEncryptor {
+    public static final String IDENTIFIER = "AES_GCM";
+
+    private static final int IV_SIZE = 12;
+    private static final int TAG_BIT_LENGTH = 128;
 
-    private static final int IV_SIZE = 16;
     private static final SecureRandom SECURE_RANDOM = new SecureRandom();
-    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
+    private static final String ALGORITHM = "AES/GCM/NoPadding";
 
     private SecretKeySpec keySpec;
 
@@ -55,15 +57,15 @@ public class AesCbcEncryptor implements Encryptor {
         byte[] iv = new byte[IV_SIZE];
         SECURE_RANDOM.nextBytes(iv);
 
-        IvParameterSpec ivSpec = new IvParameterSpec(iv);
+        GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
 
         byte[] encrypted;
         try {
             Cipher cipher = Cipher.getInstance(ALGORITHM);
-            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
             encrypted = 
cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
         } catch (Exception e) {
-            throw TransformCommonError.encryptionError("plaintext length:" + 
plainText.length(), e);
+            throw TransformCommonError.encryptionError("Encryption failed", e);
         }
 
         byte[] encryptedWithIv = new byte[IV_SIZE + encrypted.length];
@@ -76,56 +78,29 @@ public class AesCbcEncryptor implements Encryptor {
     @Override
     public String decrypt(String cipherText) {
         byte[] decoded = Base64.getDecoder().decode(cipherText);
-        byte[] iv = new byte[IV_SIZE];
-        if (decoded.length < IV_SIZE) {
-            throw CommonError.illegalArgument(cipherText, "Invalid encrypted 
value");
+
+        if (decoded.length < IV_SIZE + (TAG_BIT_LENGTH / 8)) {
+            throw CommonError.illegalArgument(cipherText, "Invalid encrypted 
value (too short)");
         }
+
+        byte[] iv = new byte[IV_SIZE];
         byte[] encrypted = new byte[decoded.length - IV_SIZE];
 
         System.arraycopy(decoded, 0, iv, 0, IV_SIZE);
         System.arraycopy(decoded, IV_SIZE, encrypted, 0, encrypted.length);
 
-        IvParameterSpec ivSpec = new IvParameterSpec(iv);
+        GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
 
         byte[] original;
         try {
             Cipher cipher = Cipher.getInstance(ALGORITHM);
-            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, spec);
             original = cipher.doFinal(encrypted);
         } catch (Exception e) {
             throw TransformCommonError.encryptionError(
-                    "ciphertext length:" + cipherText.length(), e);
+                    "Decryption failed (possible tampering or wrong key)", e);
         }
 
         return new String(original, StandardCharsets.UTF_8);
     }
-
-    private SecretKeySpec buildAesKey(String key) {
-        if (key == null || key.trim().isEmpty()) {
-            throw CommonError.illegalArgument(key, "Encryption key cannot be 
null or empty");
-        }
-
-        String base64 = key;
-        if (key.startsWith("base64:")) {
-            base64 = key.substring("base64:".length());
-        }
-        base64 = base64.trim();
-
-        byte[] keyBytes;
-        try {
-            keyBytes = Base64.getDecoder().decode(base64);
-        } catch (IllegalArgumentException e) {
-            throw CommonError.illegalArgument(key, "Invalid Base64 encoding in 
encryption key");
-        }
-
-        if (!(keyBytes.length == 16 || keyBytes.length == 24 || 
keyBytes.length == 32)) {
-            throw CommonError.illegalArgument(
-                    key,
-                    "Invalid AES key length: "
-                            + keyBytes.length
-                            + ". Expected 16, 24, or 32 bytes");
-        }
-
-        return new SecretKeySpec(keyBytes, "AES");
-    }
 }
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformTest.java
index 3f85c822c2..0b6dc1ce95 100644
--- 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformTest.java
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/FieldEncryptTransformTest.java
@@ -34,12 +34,14 @@ import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 class FieldEncryptTransformTest {
-    public static final String KEY = "base64:AAAAAAAAAAAAAAAAAAAAAA==";
+    public static final String KEY =
+            "base64:" + 
Base64.getEncoder().encodeToString("0123456789abcdef".getBytes());
     private static CatalogTable catalogTable;
     private static Object[] values;
     private static Object[] original;
@@ -157,10 +159,7 @@ class FieldEncryptTransformTest {
 
         Object[] valuesWithEmpty = new Object[] {"value1", "", "   ", 
"value4", "value5"};
         SeaTunnelRow input = new SeaTunnelRow(valuesWithEmpty);
-        SeaTunnelRow output = fieldEncryptTransform.transformRow(input);
-
-        Assertions.assertEquals("", output.getField(1));
-        Assertions.assertEquals("   ", output.getField(2));
+        Assertions.assertDoesNotThrow(() -> 
fieldEncryptTransform.transformRow(input));
     }
 
     @Test
diff --git 
a/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptorTest.java
 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptorTest.java
new file mode 100644
index 0000000000..c2582c7265
--- /dev/null
+++ 
b/seatunnel-transforms-v2/src/test/java/org/apache/seatunnel/transform/encrypt/encryptor/AesGcmEncryptorTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.transform.encrypt.encryptor;
+
+import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.Base64;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AesGcmEncryptorTest {
+
+    private AesGcmEncryptor encryptor;
+
+    private static final String TEST_KEY =
+            "base64:" + 
Base64.getEncoder().encodeToString("1234567890123456".getBytes());
+
+    @BeforeEach
+    void setUp() {
+        encryptor = new AesGcmEncryptor();
+        encryptor.init(TEST_KEY);
+    }
+
+    @Test
+    void testEncryptAndDecrypt() {
+        String plain = "test-text";
+
+        String cipher = encryptor.encrypt(plain);
+        String decrypted = encryptor.decrypt(cipher);
+
+        assertEquals(plain, decrypted);
+    }
+
+    @Test
+    void testEncryptProducesDifferentCipherText() {
+        String plain = "same-text";
+
+        String cipher1 = encryptor.encrypt(plain);
+        String cipher2 = encryptor.encrypt(plain);
+
+        // GCM uses random IV so ciphertext should differ
+        assertNotEquals(cipher1, cipher2);
+    }
+
+    @Test
+    void testDecryptTamperedCipherText() {
+        String plain = "secure-text";
+
+        String cipher = encryptor.encrypt(plain);
+
+        byte[] decoded = Base64.getDecoder().decode(cipher);
+
+        // tamper with ciphertext
+        decoded[decoded.length - 1] ^= 1;
+
+        String tampered = Base64.getEncoder().encodeToString(decoded);
+
+        assertThrows(SeaTunnelRuntimeException.class, () -> 
encryptor.decrypt(tampered));
+    }
+
+    @Test
+    void testInvalidCipherTextTooShort() {
+        String invalid = Base64.getEncoder().encodeToString(new byte[5]);
+
+        SeaTunnelRuntimeException ex =
+                assertThrows(SeaTunnelRuntimeException.class, () -> 
encryptor.decrypt(invalid));
+
+        assertTrue(ex.getMessage().contains("Invalid encrypted value (too 
short)"));
+    }
+
+    @Test
+    void testDecryptWithWrongKey() {
+        String plain = "hello";
+
+        String cipher = encryptor.encrypt(plain);
+
+        AesGcmEncryptor another = new AesGcmEncryptor();
+
+        String otherKey =
+                "base64:" + 
Base64.getEncoder().encodeToString("abcdefabcdefabcd".getBytes());
+
+        another.init(otherKey);
+
+        SeaTunnelRuntimeException ex =
+                assertThrows(SeaTunnelRuntimeException.class, () -> 
another.decrypt(cipher));
+        assertTrue(ex.getMessage().contains("Decryption failed (possible 
tampering or wrong key)"));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"", " ", "  ", "\t", "\n"})
+    void testEmptyOrWhitespaceString(String plain) {
+        String cipher = encryptor.encrypt(plain);
+        String decrypt = encryptor.decrypt(cipher);
+
+        assertEquals(plain, decrypt);
+    }
+
+    @Test
+    void testSupportAlgorithm() {
+        assertTrue(encryptor.support(AesGcmEncryptor.IDENTIFIER));
+        assertFalse(encryptor.support(AesCbcEncryptor.IDENTIFIER));
+    }
+}

Reply via email to