konfigyr/konfigyr-crypto

GitHub: konfigyr/konfigyr-crypto

Konfigyr Crypto是一个Spring Boot加密库,用于数据加密和密钥管理。

Stars: 1 | Forks: 1

# Konfigyr Crypto 配置 ![CI 构建状态](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/d435ec3237023040.svg) [![codecov](https://codecov.io/gh/konfigyr/konfigyr-crypto/graph/badge.svg?token=K76STH7L4L)](https://codecov.io/gh/konfigyr/konfigyr-crypto) [![加入 Gitter 聊天](https://badges.gitter.im/konfigyr/konfigyr-crypto.svg)](https://gitter.im/konfigyr/konfigyr-crypt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![最新版本](https://img.shields.io/maven-central/v/com.konfigyr/konfigyr-crypto-api.svg?style=flat)](https://central.sonatype.com/search?q=g%3Acom.konfigyr) ![Java 21+](https://img.shields.io/badge/java-21+-lightgray.svg) Konfigyr Crypto 库定义了 Spring 应用程序如何执行加密操作、生成加密材料以及管理其生命周期。它试图定义一个 API,以最佳实践描述如何保护您的数据并保护保护您数据的加密密钥。 Konfigyr Crypto 不实现也不提供任何直接的加密实现,其目标是提供一个 API,说明如何将这些库集成到应用程序中。我们建议使用经过良好建立的加密库来执行加密操作,例如 [Google Tink](https://github.com/tink-crypto/tink-java) 或 [BouncyCastle](https://www.bouncycastle.org/documentation.html). 库强制执行一种双层方法,这是一种推荐的行业标准,用于加密数据。在双层方法中,有两种类型的加密密钥。第一种是您用于加密数据的密钥,通常称为 *数据加密密钥 (DEK)*。第二种密钥仅用于加密 DEK,称为主密钥或密钥加密密钥 (KEK),它生成 *加密数据加密密钥 (eDEK)*,然后可以安全地存储在持久存储中,如数据库或文件系统。 在可能的情况下,密钥加密密钥应存储在与加密数据加密密钥不同的位置。例如,如果 DEK 存储在数据库中,则 KEK 应存储在文件系统中。这意味着如果攻击者只能访问其中之一(例如通过目录遍历或 SQL 注入),则他们无法访问这两个密钥和数据。 建议您的密钥加密密钥由外部密钥管理系统管理,其中 DEK 的包装和解包在 KMS 服务器上发生。这样,KEK 的私钥材料就不会为您的应用程序所知,使您的系统对攻击者更具弹性。 ## 入门 使用此库的最简单方法是导入 BOM,然后仅声明您需要的模块——而不指定版本。 **Gradle (Kotlin DSL)** ``` dependencies { implementation(platform("com.konfigyr:konfigyr-crypto-dependencies:1.0.0-RC6")) // pick the modules you need — versions are managed by the BOM implementation("com.konfigyr:konfigyr-crypto-api") implementation("com.konfigyr:konfigyr-crypto-tink") // Google Tink implementation implementation("com.konfigyr:konfigyr-crypto-jose") // Nimbus JOSE JWT implementation implementation("com.konfigyr:konfigyr-crypto-jdbc") // JDBC KeysetRepository } ``` **Maven** ``` com.konfigyr konfigyr-crypto-dependencies 1.0.0-RC6 pom import com.konfigyr konfigyr-crypto-api ``` 检查 [Maven Central](https://central.sonatype.com/search?q=g%3Acom.konfigyr) 以获取最新版本。 ### 可用模块 | 艺术品 | 描述 | |---|---| | `konfigyr-crypto-api` | 核心API — 接口、自动配置和 `KeysetStore` | | `konfigyr-crypto-tink` | [Google Tink](https://github.com/tink-crypto/tink-java) `KeysetFactory` 和 `KeyEncryptionKey` 实现 | | `konfigyr-crypto-jose` | [Nimbus JOSE JWT](https://connect2id.com/products/nimbus-jose-jwt) `KeysetFactory` 实现 | | `konfigyr-crypto-jdbc` | JDBC支持的 `KeysetRepository` | | `konfigyr-crypto-test` | 测试支持库 — AssertJ 断言和自定义 `KeysetFactory` 实现的基本测试类;在 `testImplementation` 范围内使用 | | `konfigyr-crypto-dependencies` | BOM — 导入此以在一个地方管理所有模块版本 | ## 关键概念 此库的目标不是在加密方面重新发明轮子,而是定义一个 Java API,说明客户端应用程序如何加密数据以及管理用于加密它的密钥。 让我们将库分解为一些最常用的类型和服务: * `Keyset` - 表示数据加密密钥 (DEK) * `EncryptedKeyset` - 表示加密数据加密密钥 (eDEK) * `KeyEncryptionKey` - 如其名所示 * `KeysetFactory` - 生成用于加密数据的密钥集 * `KeysetStore` - 用于生成、读取和操作密钥集或 DEK * `KeysetRepository` - 用于读取和存储加密 DEK ### Keyset 和 Keyset 工厂 `Keyset` 是使用此库时的焦点。它们表示一组密钥,这些密钥执行由其 `Algorithm` 定义的特定加密操作。 以下是一个使用 `Keyset` 的 Spring `BytesEncryptor` 接口实现示例: ``` public class KeysetBytesEncryptor { private final Keyset keyset; @Override public byte[] encrypt(byte[] byteArray) { return keyset.encrypt(new ByteArray(byteArray)).array(); } @Override public byte[] decrypt(byte[] encryptedByteArray) { return keyset.decrypt(new ByteArray(encryptedByteArray)).array(); } } ``` `Keyset` 的实现以及加密操作是如何执行的,是 `KeysetFactory` 的职责。此接口在 Konfigyr Crypto API 和实际生成密钥材料和定义其使用的加密库之间架起了一座桥梁。 工厂应能够: * 根据它们定义和支持的 `Algorithm` 生成新的密钥集 * 在存储之前包装或加密密钥集 * 在使用之前解包或解密加密密钥集 Konfigyr Crypto 提供以下 `KeysetFactory` 实现,您可以使用: * [Google Tink](konfigyr-crypto-tink) * [Nimbus JOSE JWT](konfigyr-crypto-jose) ### 算法 `Algorithm` 是一个不可变值对象,它声明了加密算法的身份和能力: * `name()` — 一个稳定、唯一的标识符,**持久化**在 `EncryptedKeyset` 旁边。它必须在创建密钥材料后永远不变。 * `purpose()` — `KeysetPurpose` (`SIGNING` 或 `ENCRYPTION`),它确定密钥集支持哪些操作。 * `type()` — 底层密钥材料的 `KeyType` (`EC`、`RSA` 或 `OCTET`)。 内置的 `TinkAlgorithm` 和 `JoseAlgorithm` 常量遵循以库家族(`tink:` 和 `jose:` 分别)为前缀的命名约定。为任何自定义算法使用类似的稳定前缀以避免名称冲突。 #### 算法注册表 `AlgorithmRegistry` 是所有已知于应用程序的算法的密封目录。它有两个用途: 1. **解析** — 将存储在 `EncryptedKeyset` 中的算法名称转换回用于解密的具体 `Algorithm` 实例。 2. **算法混淆预防** — 只有在启动时注册的算法才能解析。引用未知名称的 `EncryptedKeyset` 将快速失败,而不是尝试使用意外的算法。 在 Spring 上下文初始化所有单例之后,注册表被密封。在此之后尝试注册算法将抛出 `IllegalStateException`。 #### 算法注册器 通过 `AlgorithmRegistrar` 实例将算法贡献给注册表。每个内置模块在自动配置期间注册其算法: ``` @Bean AlgorithmRegistrar joseAlgorithmRegistrar() { return registry -> JoseAlgorithm.DEFAULT_ALGORITHMS.forEach(registry::register); } ``` 声明您自己的 `AlgorithmRegistrar` 实例以添加自定义算法以及内置算法。 ### 密钥加密密钥和提供者 `KeyEncryptionKey` 由 `KeyEncryptionKeyProvider` 提供,需要至少一个提供者以及至少一个 KEK 才能使用此库生成 `Keyset`。 以下是如何定义一个 `KeyEncryptionKeyProvider` 作为 Spring Bean 的示例,该 Bean 使用基于 Tink 的随机生成 `KeyEncryptionKey`: ``` class KeyEncryptionKeyProviderConfiguration { @Bean KeyEncryptionKeyProvider myKeyEncryptionKeyProvider() { return KeyEncryptionKeyProvider.of("my-kek-provider", List.of( TinkKeyEncryptionKey.builder("my-kek-provider").generate("my-kek") )); } } ``` 当使用 `konfigyr-crypto-tink` 时,建议使用具有信封加密的 `KmsClient` 作为 `KeyEncryptionKey`。Tink 附带 Google 和 AWS KMS 客户端实现,您也可以轻松创建自己的 `KmsClient` 实现。请参阅 [Google Tink 文档](https://developers.google.com/tink) 了解它们的使用或实现方法。 以下是如何使用 AWS KMS 声明 `KeyEncryptionKey` 的示例: ``` class KeyEncryptionKeyProviderConfiguration { @Bean KeyEncryptionKeyProvider myKeyEncryptionKeyProvider() { return KeyEncryptionKeyProvider.of("my-kek-provider", List.of( TinkKeyEncryptionKey.builder("my-kek-provider").generate("my-kek") )); } @Bean KeyEncryptionKeyProvider kmsKeyEncryptionKeyProvider() { return KeyEncryptionKeyProvider.of("kms-provider", List.of( TinkKeyEncryptionKey.builder("kms-provider").kms( "aws-kms://arn:aws:kms:us-west-2:account-id:key/key-id", // KEK ID is the same as the key ARN "AES256_GCM" // algorithm used to create the DEK for the Keyset ) )); } } ``` ### Keyset 存储 存储是应用程序开发者用于与他们的数据加密密钥或 DEK 交互的 Spring Bean。它将实际的加密和存储实现桥接在一个地方。 当您检索 `Keyset` 时,存储将检索 `EncryptedKeyset`,找到用于包装它的 `KeyEncryptionKey`,并使用负责的 `KeysetFactory` 解包和构建它。 以下是如何使用 `Keyset` 创建基于 `BytesEncryptor` 实现的示例: ``` class KeysetBytesEncryptorFactory { private final KeysetStore store; public KeysetBytesEncryptor create(String keysetName) { return new KeysetBytesEncryptor(store.read(keysetName)); } } ``` 当您希望生成或更新 `Keyset` 时,将应用相反的过程,它会使用负责的 `KeyEncryptionKey` 包装密钥,并使用定义的 `KeysetRepository` 实现存储 `KeyEncryptionKey`。 以下是如何创建、旋转或删除新的 Tink 密钥集的示例: ``` class TinkExample { private final KeysetStore store; public Keyset create() { return store.create("my-kek-provider", "my-kek", KeysetDefinition.of( "my-dek", // give a name to your DEK TinkAlgorithm.AES256_GCM // define the Tink algorithm to the DEK )); } public Keyset createWithKek() { final KeyEncryptionKey kek = store.kek("my-kek-provider", "my-kek"); return store.create(kek, KeysetDefinition.of( "my-dek", // give a name to your DEK TinkAlgorithm.AES256_GCM // define the Tink algorithm to the DEK )); } public void rotate() { store.rotate("my-dek"); } public void remove() { store.remove("my-dek"); } } ``` ### 密钥生命周期管理 每个密钥集中的 `EncryptedKey` 都携带一个 `KeyStatus`,它描述了其在生命周期状态机中的位置: | 状态 | 描述 | |---|---| | `ENABLED` | 活跃的;参与加密操作 | | `DISABLED` | 管理员停用的;不允许任何加密操作 | | `COMPROMISED` | 密钥材料被怀疑或确认已暴露;永久阻止 | | `PENDING_DESTRUCTION` | 已安排擦除;目前处于其宽限期 | | `DESTROYED` | 密钥材料已永久擦除;行保留以供审计 | `KeysetStore` 提供方法来驱动每个转换: - `disable(keysetName, keyId)` — `ENABLED` → `DISABLED` - `enable(keysetName, keyId)` — `DISABLED` → `ENABLED` - `compromise(keysetName, keyId)` — 紧急转换;永久阻止密钥的所有加密操作 - `scheduleDestruction(keysetName, keyId)` — `DISABLED` 或 `COMPROMISED` → `PENDING_DESTRUCTION`,使用密钥集配置的宽限期(未设置宽限期时立即销毁) - `scheduleDestruction(keysetName, keyId, Instant)` — 与上相同,具有显式的销毁时间 - `cancelDestruction(keysetName, keyId)` — `PENDING_DESTRUCTION` → `DISABLED` - `destroy(keysetName, keyId)` — `PENDING_DESTRUCTION` → `DESTROYED`;擦除密钥材料但保留行以供审计 ``` // disable the old primary key after rotating to a new one store.disable("my-dek", oldKey.getId()); // schedule it for destruction using the keyset's configured grace period store.scheduleDestruction("my-dek", oldKey.getId()); ``` ### Keyset 存储库 Keyset 存储库是一个简单的接口,其目标是实现如何存储、检索或删除 `EncryptedKeyset`。 每个 `EncryptedKeyset` 都携带一个由存储库管理的版本计数器。`write()` 和 `updateKeyStatus()` 都会检查此计数器,并在检测到并发修改时抛出 `CryptoException.KeysetConcurrentModificationException`。始终缓存并使用 `write()` 返回的 `EncryptedKeyset`——而不是输入——以确保正确的版本被带入下一次写入。 Konfigyr Crypto 提供以下 `KeysetRepository` 实现,您可以使用: * [JDBC](konfigyr-crypto-jdbc) ### 定期维护:旋转和销毁 `KeysetRepository` 提供了两个查询方法,旨在用于定期维护任务。 `findPendingRotation()` 返回部分密钥集(仅元数据,空密钥列表),其主键的到期时间已过。为每个结果调用 `store.rotate(name)`: ``` for (EncryptedKeyset keyset : repository.findPendingRotation()) { store.rotate(keyset.getName()); } ``` `findPendingDestruction()` 返回部分密钥集(元数据和仅符合销毁条件的待销毁密钥),其中 `destructionScheduledAt` 已在过去。为每个密钥调用 `store.destroy(name, keyId)`: ``` for (EncryptedKeyset keyset : repository.findPendingDestruction()) { for (EncryptedKey key : keyset) { store.destroy(keyset.getName(), key.getId()); } } ``` 这两个方法默认返回空列表;可以发出高效查询的存储库(例如 `JdbcKeysetRepository`)会覆盖它们。 当应用程序上下文中存在 `KeysetStore` 和 `KeysetRepository` Bean 时,`KeysetTaskAutoConfiguration` 会自动注册这两个任务并启用 Spring 调度。默认情况下,每个任务以固定速率触发器每 **1 小时** 运行一次。 任务配置在 `konfigyr.crypto.tasks` 前缀下。每个任务名称是映射中的键(`keyset-rotation` 或 `keyset-destruction`)并支持三个属性: | 属性 | 类型 | 默认值 | 描述 | |---|---|---|---| | `enabled` | `boolean` | `true` | 将其设置为 `false` 以完全禁用任务 | | `interval` | `Duration` | `PT1H` | 执行之间的固定速率周期 | | `cron` | `String` | — | Cron 表达式;当设置时,优先于 `interval` | 当为同一任务同时配置 `cron` 和 `interval` 时,`cron` 优先,并在启动时记录警告。 ``` # 每晚02:00运行轮换 konfigyr.crypto.tasks.keyset-rotation.cron=0 0 2 * * * # 每30分钟运行销毁 konfigyr.crypto.tasks.keyset-destruction.interval=PT30M # 完全禁用轮换调度(例如,外部处理) konfigyr.crypto.tasks.keyset-rotation.enabled=false ``` ## 实现自定义加密提供者 要集成新的加密库或添加自定义算法,您需要以下三件事: 1. 一个声明算法身份的 `Algorithm` 实现。 2. 一个执行实际加密操作的 `Keyset` 实现。 3. 一个从定义和加密数据创建 `Keyset` 实例的 `KeysetFactory` 实现。 将它们作为 Spring Bean 连接起来,并通过 `AlgorithmRegistrar` 注册您的算法。 ### 第 1 步:定义您的算法 ``` public final class MyAlgorithm implements Algorithm { public static final MyAlgorithm MY_SIGNING = new MyAlgorithm( "my-lib:EC_SIGNING", KeysetPurpose.SIGNING, KeyType.EC ); private final String name; private final KeysetPurpose purpose; private final KeyType type; public MyAlgorithm(String name, KeysetPurpose purpose, KeyType type) { this.name = name; this.purpose = purpose; this.type = type; } @Override public String name() { return name; } @Override public KeysetPurpose purpose() { return purpose; } @Override public KeyType type() { return type; } } ``` `name` 在 `EncryptedKeyset` 行中持久化,并在加载时用于查找算法。选择一个独特的稳定前缀,仅限于您的库(例如 `my-lib:`),并且一旦创建密钥材料,就永远不要重命名算法。 ### 第 2 步:实现 KeysetFactory ``` public class MyKeysetFactory implements KeysetFactory { public static final String NAME = "my-lib"; private final AlgorithmRegistry registry; public MyKeysetFactory(AlgorithmRegistry registry) { this.registry = registry; } @Override public boolean supports(KeysetDefinition definition) { // the definition carries the Algorithm object directly return definition.getAlgorithm() instanceof MyAlgorithm; } @Override public boolean supports(EncryptedKeyset encryptedKeyset) { // match by the factory name stored in the encrypted keyset return NAME.equals(encryptedKeyset.getFactory()); } @Override public Keyset create(KeyEncryptionKey kek, KeysetDefinition definition) { MyAlgorithm algorithm = (MyAlgorithm) definition.getAlgorithm(); // generate key material using your library, return a Keyset implementation } @Override public EncryptedKeyset create(Keyset keyset) throws IOException { final List encryptedKeys = new ArrayList<>(); for (Key key : keyset) { final ByteArray serialized = // serialize this key to bytes using your library final ByteArray wrapped = keyset.getKeyEncryptionKey().wrap(serialized); encryptedKeys.add(EncryptedKey.from(key, WrappedKeyMaterial.of(wrapped))); } return EncryptedKeyset.from(keyset, encryptedKeys); } @Override public Keyset create(KeyEncryptionKey kek, EncryptedKeyset encryptedKeyset) throws IOException { for (EncryptedKey key : encryptedKeyset) { final MyAlgorithm algorithm = (MyAlgorithm) registry.resolve(key.getAlgorithm()); // unwrap key.getData() using kek, then deserialize into your Keyset } // return a Keyset implementation } } ``` `supports(EncryptedKeyset)` 通过存储在密钥集上的工厂名称识别所有权。`supports(KeysetDefinition)` 可以使用 `instanceof`,因为定义已经直接持有 `Algorithm` 对象。 ### 第 3 步:注册并作为 Spring Bean 连接 ``` @Configuration class MyLibAutoConfiguration { @Bean AlgorithmRegistrar myAlgorithmRegistrar() { return registry -> registry.register(MyAlgorithm.MY_SIGNING); } @Bean MyKeysetFactory myKeysetFactory(AlgorithmRegistry registry) { return new MyKeysetFactory(registry); } } ``` `KeysetStore` 自动配置会自动拾取所有 `KeysetFactory` Bean。一旦声明了 Bean,`store.create(kek, KeysetDefinition.of("my-key", MyAlgorithm.MY_SIGNING))` 将委托给您的工厂,无需任何进一步的连接。 ## 从源代码构建 Konfigyr Crypto 使用基于 Gradle 的构建系统。在下面的说明中,`./gradlew` 从源树根目录调用,作为跨平台、自包含的构建引导机制。 ### 先决条件 Git 和 JDK 21。 ### 检出源代码 ``` git clone git@github.com:konfigyr/konfigyr-crypto.git ``` ### 发布到您的本地 Maven 仓库 ``` ./gradlew publishToMavenLocal ``` ### 编译和测试 ``` ./gradlew build ``` 使用 `./gradlew tasks` 发现更多命令。 ## 获取支持 尝试联系我们的 [Gitter 聊天](https://gitter.im/konfigyr/konfigyr-crypt)。也提供商业支持。 ## 贡献 [拉取请求](https://help.github.com/articles/creating-a-pull-request) 欢迎接受;请参阅 [贡献者](CONTRIBUTING.md) 指南以获取详细信息。 ## 许可证 Konfigyr Crypto 库是开源软件,根据 [Apache 2.0 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布。
标签:API设计, BouncyCastle, Google Tink, Java 21+, JS文件枚举, Maven 仓库, ProjectDiscovery, Spring Boot, 主密钥, 代码覆盖率, 加密库, 加密操作, 双因素加密, 域名枚举, 安全实践, 密钥加密密钥, 开源框架, 持久化存储, 持续集成, 数据保护, 数据加密密钥, 行业标准