MongoDB本地MasterKey轮换失败:报Unexpected field: 'key'错误
问题:本地MasterKey轮换时调用rewrapManyDataKey报错"Unexpected field: 'key'"
我的应用使用本地masterKey解密MongoDB服务器中的数据,计划每年手动触发独立API轮换密钥文件。尝试用Java 8(依赖mongodb-driver-sync-5.0.0、mongodb-crypt-1.8.0)和Node.js(Mongosh 2.3.1)创建新本地masterKey文件并重加密keyVault集合中的DEK,但均触发相同错误,不确定实现是否正确。
原Java代码
kmsProviders.put("local", newlyCreatedMasterKey); ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() .keyVaultMongoClientSettings(MongoClientSettings.builder() .applyConnectionString(new ConnectionString(connectionString)) .build()) .keyVaultNamespace(keyVaultDb + "." + keyVaultColl) .kmsProviders(kmsProviders) .build(); MongoCollection<Document> keyVault = mongoClient.getDatabase(keyVaultDb).getCollection(keyVaultColl); for (Document dataKeyDoc : keyVault.find()) { Binary id = dataKeyDoc.get("_id", Binary.class); BsonBinary dataKeyId = new BsonBinary(id.getType(), id.getData()); clientEncryption.rewrapManyDataKey( Filters.eq("_id", dataKeyId), new RewrapManyDataKeyOptions() .provider("local") .masterKey(new BsonDocument("key", new BsonBinary(newlyCreatedMasterKey))) ); }
原Node.js代码
// Creating a new key const key = require("crypto").randomBytes(96); fs.writeFileSync('keyfile.txt', key); // Connection options var autoEncryptionOpts = { "keyVaultNamespace" : "encryption.__keyvault", "kmsProviders" : { "local" : { "key" : BinData(0, key.toString("base64")) } } } // Created the encrypted client const client = new Mongo(uri, autoEncryptionOpts); const keyVault = client.getKeyVault(); const result = keyVault.rewrapManyDataKey({}, { provider: 'local', masterKey: { keyMaterial: BinData(0, key.toString("base64")) } })
错误信息
Error rewrapping data keys: Unexpected field: 'key' Exception in thread "main" com.mongodb.MongoClientException: Exception in encryption library: Unexpected field: 'key' at com.mongodb.client.internal.Crypt.wrapInMongoException(Crypt.java:375) at com.mongodb.client.internal.Crypt.rewrapManyDataKey(Crypt.java:260) at com.mongodb.client.internal.ClientEncryptionImpl.rewrapManyDataKey(ClientEncryptionImpl.java:173) at RotateMasterKey.reEncryptDataKeys(RotateMasterKey.java:122) at RotateMasterKey.main(RotateMasterKey.java:56) Caused by: com.mongodb.crypt.capi.MongoCryptException: Unexpected field: 'key' at com.mongodb.crypt.capi.MongoCryptContextImpl.throwExceptionFromStatus(MongoCryptContextImpl.java:156) at com.mongodb.crypt.capi.MongoCryptImpl.configure(MongoCryptImpl.java:376) at com.mongodb.crypt.capi.MongoCryptImpl.createRewrapManyDatakeyContext(MongoCryptImpl.java:311) at com.mongodb.client.internal.Crypt.rewrapManyDataKey(Crypt.java:251) ... 3 more
问题分析
错误核心是本地KMS的masterKey参数格式错误。与云KMS(如AWS KMS)不同,MongoDB本地KMS不需要将密钥包装在带字段名的文档中,直接传入二进制密钥数据即可。代码中错误地给masterKey添加了key或keyMaterial字段,导致加密库无法识别。
修正后的代码
Java版本
import com.mongodb.client.*; import com.mongodb.client.model.Filters; import org.bson.BsonBinary; import org.bson.Document; import com.mongodb.client.vault.ClientEncryption; import com.mongodb.client.vault.ClientEncryptions; import com.mongodb.client.vault.RewrapManyDataKeyOptions; import com.mongodb.MongoClientSettings; import com.mongodb.ConnectionString; import java.util.HashMap; import java.util.Map; public class RotateMasterKey { public static void main(String[] args) { String connectionString = "mongodb://localhost:27017"; String keyVaultDb = "encryption"; String keyVaultColl = "__keyvault"; // 生成新的96字节本地masterKey byte[] newlyCreatedMasterKey = new byte[96]; new java.security.SecureRandom().nextBytes(newlyCreatedMasterKey); // 初始化KMS提供者:local直接绑定密钥字节数组 Map<String, Object> kmsProviders = new HashMap<>(); kmsProviders.put("local", newlyCreatedMasterKey); // 配置ClientEncryption ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder() .keyVaultMongoClientSettings(MongoClientSettings.builder() .applyConnectionString(new ConnectionString(connectionString)) .build()) .keyVaultNamespace(keyVaultDb + "." + keyVaultColl) .kmsProviders(kmsProviders) .build(); try (MongoClient mongoClient = MongoClients.create(connectionString); ClientEncryption clientEncryption = ClientEncryptions.create(clientEncryptionSettings)) { // 批量重加密所有DEK,无需循环单个处理 RewrapManyDataKeyOptions options = new RewrapManyDataKeyOptions() .provider("local") .masterKey(new BsonBinary(newlyCreatedMasterKey)); // 直接传入二进制密钥,无需包装文档 clientEncryption.rewrapManyDataKey(Filters.empty(), options); System.out.println("所有数据密钥已完成重加密"); } } }
Node.js版本
const crypto = require("crypto"); const fs = require("fs"); const { MongoClient } = require("mongodb"); const uri = "mongodb://localhost:27017"; const keyVaultNamespace = "encryption.__keyvault"; // 生成新的96字节本地masterKey const newMasterKey = crypto.randomBytes(96); fs.writeFileSync('new-local-keyfile.bin', newMasterKey); async function rotateMasterKey() { // 配置自动加密选项:local KMS直接传入Buffer类型的密钥 const autoEncryptionOpts = { keyVaultNamespace: keyVaultNamespace, kmsProviders: { local: { key: newMasterKey // 直接传Buffer,无需BinData包装 } } }; const client = new MongoClient(uri, autoEncryptionOpts); try { await client.connect(); const keyVault = client.getKeyVault(); // 批量重加密所有DEK const result = await keyVault.rewrapManyDataKey({}, { provider: 'local', masterKey: newMasterKey // 直接传入Buffer,无需keyMaterial字段 }); console.log(`成功重加密 ${result.modifiedCount} 个数据密钥`); } finally { await client.close(); } } rotateMasterKey().catch(err => console.error("重加密失败:", err));
额外注意事项
- 密钥规格:本地masterKey必须是96字节的随机字节数组,符合MongoDB客户端字段加密的要求。
- 批量处理:无需循环单个DEK调用
rewrapManyDataKey,传入Filters.empty()(Java)或{}(Node.js)即可批量处理所有密钥,提升效率。 - 密钥存储:新生成的masterKey需要妥善存储,后续应用解密必须使用该密钥。
内容的提问来源于stack exchange,提问作者user3549302
相关产品推荐
相关产品推荐

