konfigyr/konfigyr-crypto
GitHub: konfigyr/konfigyr-crypto
Konfigyr Crypto是一个Spring Boot加密库,用于数据加密和密钥管理。
Stars: 1 | Forks: 1
# Konfigyr Crypto 配置

[](https://codecov.io/gh/konfigyr/konfigyr-crypto)
[](https://gitter.im/konfigyr/konfigyr-crypt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://central.sonatype.com/search?q=g%3Acom.konfigyr)

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, 主密钥, 代码覆盖率, 加密库, 加密操作, 双因素加密, 域名枚举, 安全实践, 密钥加密密钥, 开源框架, 持久化存储, 持续集成, 数据保护, 数据加密密钥, 行业标准