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

luwei16 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 6341cd0d182 [refactor](tde) Add local root key metadata for TDE 
(#66948)
6341cd0d182 is described below

commit 6341cd0d182c1e3814cbfd152e0bf79129ecb31c
Author: Luwei <[email protected]>
AuthorDate: Mon Aug 24 17:48:28 2026 +0800

    [refactor](tde) Add local root key metadata for TDE (#66948)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Problem Summary: TDE root-key metadata currently cannot represent and
    validate a file-based local root key across configuration, edit-log
    serialization, and key rotation. Add a persisted local key file path and
    SHA-256 hash, keep the Base64 key in memory only, expose the
    corresponding FE configuration and rotation properties, and cover
    copying, hash verification, JSON/edit-log serialization, and
    sensitive-field exclusion.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test: Unit Test
    - ./run-fe-ut.sh --run org.apache.doris.encryption.RootKeyInfoTest (5
    tests passed)
    - Behavior changed: No. Existing root-key loading behavior remains
    unchanged; this adds metadata and configuration support for file-based
    local root keys.
    - Does this need documentation: No
---
 .../main/java/org/apache/doris/common/Config.java  |  7 +-
 .../org/apache/doris/encryption/RootKeyInfo.java   | 40 ++++++++++
 .../commands/AdminRotateTdeRootKeyCommand.java     |  6 ++
 .../apache/doris/encryption/RootKeyInfoTest.java   | 93 ++++++++++++++++++++++
 4 files changed, 145 insertions(+), 1 deletion(-)

diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java 
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index 6ba1e477efc..b8c205cf041 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -3584,9 +3584,14 @@ public class Config extends ConfigBase {
     public static String doris_tde_key_region = "";
 
     @ConfField(mutable = true, description = "The key provider identifier for 
TDE (Transparent Data Encryption). "
-            + "Recognized values include aws_kms, aliyun_kms, ranger_kms, 
gcp_kms, and azure_kms.")
+            + "Recognized values include aws_kms, aliyun_kms, ranger_kms, 
gcp_kms, azure_kms, and local. "
+            + "For local mode, doris_tde_root_key_file must be set to a key 
file path.")
     public static String doris_tde_key_provider = "";
 
+    @ConfField(description = "Path to the root key file for TDE local mode. 
The file content must be a "
+            + "Base64-encoded key.")
+    public static String doris_tde_root_key_file = "";
+
     @ConfField(mutable = true, description = "The simple authentication user 
name for TDE Hadoop KMS")
     public static String doris_tde_hadoop_user_name = "hadoop";
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java 
b/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
index d282e2a3eab..be1181186a2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/encryption/RootKeyInfo.java
@@ -19,6 +19,8 @@ package org.apache.doris.encryption;
 
 import com.google.gson.annotations.SerializedName;
 
+import java.security.MessageDigest;
+import java.util.Base64;
 import java.util.Objects;
 
 public class RootKeyInfo {
@@ -71,6 +73,9 @@ public class RootKeyInfo {
         this.ak = info.ak;
         this.sk = info.sk;
         this.password = info.password;
+        this.rootKeyFilePath = info.rootKeyFilePath;
+        this.rootKeyHash = info.rootKeyHash;
+        this.rootKeyBase64 = info.rootKeyBase64;
     }
 
     @SerializedName(value = "type")
@@ -96,4 +101,39 @@ public class RootKeyInfo {
 
     @SerializedName(value = "password")
     public String password;
+
+    // File path of the local root key. This is a persisted key reference, 
similar to cmkId for KMS.
+    @SerializedName(value = "rootKeyFilePath")
+    public String rootKeyFilePath;
+
+    // SHA-256 hash of the root key (Base64 encoded). Used to verify the root 
key on FE restart.
+    @SerializedName(value = "rootKeyHash")
+    public String rootKeyHash;
+
+    // Root key in Base64 encoding. Used for passing the key during rotation, 
not persisted to the edit log.
+    public transient String rootKeyBase64;
+
+    public void setRootKeyHashFromKey(byte[] rootKeyBytes) {
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(rootKeyBytes);
+            this.rootKeyHash = Base64.getEncoder().encodeToString(hash);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to compute root key hash", e);
+        }
+    }
+
+    public boolean verifyRootKey(byte[] rootKeyBytes) {
+        if (this.rootKeyHash == null || this.rootKeyHash.isEmpty()) {
+            return true;
+        }
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(rootKeyBytes);
+            String inputHash = Base64.getEncoder().encodeToString(hash);
+            return this.rootKeyHash.equals(inputHash);
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to verify root key", e);
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
index 469c871f3a9..e9b75c087e8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminRotateTdeRootKeyCommand.java
@@ -47,6 +47,12 @@ public class AdminRotateTdeRootKeyCommand extends Command 
implements ForwardWith
 
     public static final String DORIS_TDE_KEY_REGION = "doris_tde_key_region";
 
+    // File path containing the new key for local key rotation.
+    public static final String DORIS_TDE_KEY_NEW_KEY_FILE = 
"doris_tde_key_new_key_file";
+
+    // File path containing the original key for verification during local key 
rotation.
+    public static final String DORIS_TDE_KEY_ORIGINAL_KEY_FILE = 
"doris_tde_key_original_key_file";
+
     private final Map<String, String> properties;
 
     public AdminRotateTdeRootKeyCommand(Map<String, String> properties) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
index fd9dd89423d..4627eb9c385 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/encryption/RootKeyInfoTest.java
@@ -17,11 +17,21 @@
 
 package org.apache.doris.encryption;
 
+import org.apache.doris.encryption.EncryptionKey.Algorithm;
+import org.apache.doris.encryption.EncryptionKey.KeyType;
+import org.apache.doris.encryption.RootKeyInfo.RootKeyType;
 import org.apache.doris.persist.gson.GsonUtils;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
 public class RootKeyInfoTest {
     @Test
     public void testDeserializeGcpAndAzureKmsTypes() {
@@ -31,4 +41,87 @@ public class RootKeyInfoTest {
         RootKeyInfo azureInfo = 
GsonUtils.GSON.fromJson("{\"type\":\"AZURE_KMS\"}", RootKeyInfo.class);
         Assertions.assertEquals(RootKeyInfo.RootKeyType.AZURE_KMS, 
azureInfo.type);
     }
+
+    @Test
+    public void testLocalRootKeyInfoSerialization() {
+        RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+        String json = GsonUtils.GSON.toJson(rootKeyInfo);
+        Assertions.assertTrue(json.contains("rootKeyFilePath"));
+        Assertions.assertTrue(json.contains("rootKeyHash"));
+        Assertions.assertFalse(json.contains("rootKeyBase64"));
+        Assertions.assertFalse(json.contains(rootKeyInfo.rootKeyBase64));
+
+        RootKeyInfo restored = GsonUtils.GSON.fromJson(json, 
RootKeyInfo.class);
+        Assertions.assertEquals(RootKeyType.LOCAL, restored.type);
+        Assertions.assertEquals(rootKeyInfo.rootKeyFilePath, 
restored.rootKeyFilePath);
+        Assertions.assertEquals(rootKeyInfo.rootKeyHash, restored.rootKeyHash);
+        Assertions.assertNull(restored.rootKeyBase64);
+    }
+
+    @Test
+    public void testLocalRootKeyHashVerification() {
+        byte[] rootKey = "local-root-key".getBytes(StandardCharsets.UTF_8);
+        RootKeyInfo rootKeyInfo = new RootKeyInfo();
+
+        Assertions.assertTrue(rootKeyInfo.verifyRootKey(rootKey));
+
+        rootKeyInfo.setRootKeyHashFromKey(rootKey);
+        Assertions.assertTrue(rootKeyInfo.verifyRootKey(rootKey));
+        
Assertions.assertFalse(rootKeyInfo.verifyRootKey("different-key".getBytes(StandardCharsets.UTF_8)));
+    }
+
+    @Test
+    public void testCopyLocalRootKeyInfo() {
+        RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+        RootKeyInfo copied = new RootKeyInfo(rootKeyInfo);
+
+        Assertions.assertEquals(rootKeyInfo.rootKeyFilePath, 
copied.rootKeyFilePath);
+        Assertions.assertEquals(rootKeyInfo.rootKeyHash, copied.rootKeyHash);
+        Assertions.assertEquals(rootKeyInfo.rootKeyBase64, 
copied.rootKeyBase64);
+    }
+
+    @Test
+    public void testKeyManagerStoreSerializationKeepsLocalRootKeyReference() 
throws Exception {
+        RootKeyInfo rootKeyInfo = createLocalRootKeyInfo();
+
+        EncryptionKey masterKey = new EncryptionKey();
+        masterKey.id = "1";
+        masterKey.version = 1;
+        masterKey.parentId = "local";
+        masterKey.parentVersion = 1;
+        masterKey.type = KeyType.MASTER_KEY;
+        masterKey.algorithm = Algorithm.AES256;
+        masterKey.ciphertext = "ciphertext";
+        masterKey.crc = 1234;
+
+        KeyManagerStore store = new KeyManagerStore();
+        store.setRootKeyInfo(rootKeyInfo);
+        store.addMasterKey(masterKey);
+
+        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+        store.write(new DataOutputStream(byteOutput));
+
+        KeyManagerStore restored = KeyManagerStore.read(new DataInputStream(
+                new ByteArrayInputStream(byteOutput.toByteArray())));
+
+        Assertions.assertEquals(RootKeyType.LOCAL, 
restored.getRootKeyInfo().type);
+        Assertions.assertEquals(rootKeyInfo.rootKeyFilePath, 
restored.getRootKeyInfo().rootKeyFilePath);
+        Assertions.assertEquals(rootKeyInfo.rootKeyHash, 
restored.getRootKeyInfo().rootKeyHash);
+        Assertions.assertNull(restored.getRootKeyInfo().rootKeyBase64);
+        Assertions.assertEquals(1, restored.getMasterKeys().size());
+        Assertions.assertEquals(masterKey.ciphertext, 
restored.getMasterKeys().get(0).ciphertext);
+    }
+
+    private RootKeyInfo createLocalRootKeyInfo() {
+        RootKeyInfo rootKeyInfo = new RootKeyInfo();
+        rootKeyInfo.type = RootKeyType.LOCAL;
+        rootKeyInfo.cmkId = "local";
+        rootKeyInfo.rootKeyFilePath = 
"/opt/apache-doris/fe/conf/doris_tde_root_key";
+        rootKeyInfo.rootKeyHash = "hash";
+        rootKeyInfo.rootKeyBase64 = Base64.getEncoder().encodeToString(
+                "secret".getBytes(StandardCharsets.UTF_8));
+        return rootKeyInfo;
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to