goforj/crypt
GitHub: goforj/crypt
一个受 Laravel 启发的 Go 语言应用层加密库,提供经过验证的 AES-CBC 加密和透明密钥轮换能力。
Stars: 2 | Forks: 0
为 Go 提供经验证的 AES-128/256 CBC 加密,支持向后兼容的读取和密钥轮换。
## 安装 ``` go get github.com/goforj/crypt ``` # 功能 **crypt** 为 Go 服务提供经验证的 AES-CBC 加密,并通过 **APP_PREVIOUS_KEYS** 实现平滑的密钥轮换。`Encrypt` 使用单一的当前传输格式,而 `Decrypt` 则保持与早期版本写入的密文向后兼容。 - AES-128 / AES-256 CBC 加密 - 验证加密(AES-CBC + HMAC) - 单一规范的 CBC 写入格式 - 向后兼容读取历史 CBC 封包 - 通过 `APP_PREVIOUS_KEYS` 实现透明密钥轮换 - 零依赖(仅使用标准库) - 支持实例化和全局使用风格 - 针对无效密钥、格式错误的有效负载和身份验证失败,提供稳定的错误标识 ## 为什么选择 crypt? **crypt** 的存在是为了很好地解决一个问题:使用安全的默认设置和轻松的密钥轮换来加密小型应用有效负载。 它不是一个通用的密码学库。 它是一个专注的、应用层的实用工具,旨在做到单调且可预测。 ## 快速开始 ### 实例化(推荐) ``` package main import ( "fmt" "github.com/goforj/crypt" ) func main() { appKey := "base64:..." // 16-byte (AES-128) or 32-byte (AES-256) key after decoding. key, err := crypt.ReadAppKey(appKey) if err != nil { panic(err) } c, err := crypt.New(key) if err != nil { panic(err) } ciphertext, err := c.Encrypt("secret") if err != nil { panic(err) } plaintext, err := c.Decrypt(ciphertext) if err != nil { panic(err) } fmt.Println(plaintext) // "secret" } ``` `c.Decrypt` 也能读取以 crypt 的历史格式写入的密文。 ### 全局(基于环境变量的便捷方式) ``` package main import ( "fmt" "os" "github.com/goforj/crypt" ) func main() { _ = os.Setenv("APP_KEY", "base64:...") ciphertext, _ := crypt.Encrypt("secret") plaintext, _ := crypt.Decrypt(ciphertext) fmt.Println(plaintext) // "secret" } ``` ## 密钥格式与轮换 `crypt` 使用带有 base64 前缀的应用密钥语法,并支持在轮换期间进行平滑解密。 * **`APP_KEY`** 必须以 `base64:` 为前缀,并解码为 **16 字节(AES-128)** 或 **32 字节(AES-256)**。 * **`APP_PREVIOUS_KEYS`** 是可选的,可以包含以逗号分隔的、格式相同的旧密钥列表。 * 在解密期间,首先尝试当前密钥,然后再尝试任何以前的密钥。 * 加密**始终**使用当前的 `APP_KEY`;以前的密钥永远不会用于加密。 ### 示例 ``` export APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY=" export APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w=" ``` ## 传输格式兼容性 | 操作 | crypt 原始格式 | Laravel 12 CBC `encryptString` | |---|---:|---:| | `Encrypt` / `(*Cipher).Encrypt` 写入 | 否 | 是 | | `Decrypt` / `(*Cipher).Decrypt` 读取 | 是 | 是 | `Encrypt` 写入兼容的 CBC 封包:外层 base64 的 JSON 包含 `iv`、`value`、小写十六进制的 `mac` 以及明确的空 `tag`。它对 base64 的 IV 和密文进行签名,与 Laravel 的 [`Encrypter`](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Encryption/Encrypter.php) 匹配。 16 字节的密钥选择 AES-128-CBC,32 字节的密钥选择 AES-256-CBC。 兼容性有意仅限于 Laravel 的字符串 API。`crypt` 不会 生成或使用 Laravel 通用 `encrypt` 方法生成的 PHP 序列化值,也不支持 Laravel 的 GCM 密码。有关上游模型,请参阅 Laravel 的 [加密文档](https://laravel.com/docs/12.x/encryption)。 ## 传输格式迁移 此版本将 `Encrypt` 和 `(*Cipher).Encrypt` 从 crypt 的原始封包 更改为上述的十六进制 MAC 封包。它们的 Go 签名保持不变,并且 `Decrypt` 保持 与现有密文的向后兼容性。 仅理解原始 MAC 编码的早期 crypt 版本和外部使用者 无法读取新写入的数据。混合版本的部署必须要么在切换写入器之前发布 双格式读取器,要么在协调升级期间暂停写入。 除非仅接受十六进制 MAC 封包的使用者必须读取现有密文,否则现有密文不需要重新加密。在依赖的 密文过期或迁移之前,请在 `APP_PREVIOUS_KEYS` 中保留所需的旧密钥。 `EncryptedPayload` 保留了其原始的三字段 Go 结构以保证源代码兼容性, 但密文封包应被视为不透明的。 ## 安全与错误处理 - 每次写入都会生成一个新的加密 IV。在 CBC 解密或填充检查之前,会先对有效负载进行验证。 - `ErrInvalidKey`、`ErrInvalidPayload` 和 `ErrAuthentication` 支持 `errors.Is`;错误文本绝不包含密钥、明文或密文值。 - 该库有意不施加任意的有效负载大小限制。加密会在内存中缓冲有效负载,因此调用者应根据其信任边界应用适当的限制。 - 将 `APP_KEY` 和 `APP_PREVIOUS_KEYS` 视为机密。不要记录它们、将它们嵌入源代码中,或者在现有的密文仍依赖于某个先前密钥时删除该密钥。 ## 修改 `.env` 文件 这两个辅助函数都会保留注释、不相关的格式、引号样式、CRLF/LF 换行符 以及现有文件的权限模式。新文件以 `0600` 模式创建。 审核旧文件,并在部署要求允许时将其限制为 `0600`; 该库不会静默更改现有的显式模式。替换后的文件 在准备机密字节时保持为 `0600`,然后在同步重命名之前立即获得保留的最终 模式。 写入操作使用同步的同目录临时文件和原子重命名。最终组件的 符号链接会被拒绝,并且同路径的修改会在单个进程内串行化。 不相关的进程必须单独协调其读-改-写操作。 原子替换会创建一个由写入进程拥有的 inode,因此请在应当拥有 生成文件的账户下运行这些辅助函数。如果重命名已提交,但目录 同步报告了持久性错误,辅助函数将同时返回已安装的密钥和该错误。 可以使用 `go test -bench=. -benchmem` 对加密和两种读取格式进行基准测试。 ## 可运行示例 已记录工作流程的可运行示例位于 [`./examples`](./examples) 下。 示例直接从函数文档注释生成,相同的代码片段支持 README 和 GoDoc 示例。 自动化测试会构建每个示例,因此文档会随着 API 的发展保持有效。 ## API 索引 全局 = 包级函数(基于环境变量的便捷方式)。 实例化 = 带有注入密钥的 `*crypt.Cipher` 方法。 | 分组 | 命名空间 | 函数 | |------:|-----------|-----------| | **加密** | 全局 | [Decrypt](#decrypt) · [Encrypt](#encrypt) | | **加密** | 实例化 | [Cipher.Decrypt](#cipher-decrypt) · [Cipher.Encrypt](#cipher-encrypt) | | **密钥管理** | 全局 | [GenerateAppKey](#generateappkey) · [GenerateKeyToEnv](#generatekeytoenv) · [GetAppKey](#getappkey) · [GetPreviousAppKeys](#getpreviousappkeys) · [New](#new) · [NewFromEnv](#newfromenv) · [ReadAppKey](#readappkey) · [RotateKeyInEnv](#rotatekeyinenv) | ## 加密 ### 全局 #### Decrypt Decrypt 使用 APP_KEY 和 APP_PREVIOUS_KEYS 解密支持的任一有效负载格式。 _示例:使用当前密钥解密_ ``` appKey, _ := crypt.GenerateAppKey() _ = os.Setenv("APP_KEY", appKey) ciphertext, _ := crypt.Encrypt("secret") plaintext, _ := crypt.Decrypt(ciphertext) godump.Dump(plaintext) // #string "secret" ``` _示例:解密使用先前密钥加密的密文_ ``` oldAppKey, _ := crypt.GenerateAppKey() newAppKey, _ := crypt.GenerateAppKey() // Encrypt with the old key first. _ = os.Setenv("APP_KEY", oldAppKey) rotatedCiphertext, _ := crypt.Encrypt("rotated") // Rotate to a new current key, but keep the old key in APP_PREVIOUS_KEYS. _ = os.Setenv("APP_KEY", newAppKey) _ = os.Setenv("APP_PREVIOUS_KEYS", oldAppKey) plaintext, err := crypt.Decrypt(rotatedCiphertext) godump.Dump(plaintext, err) // #string "rotated" // #error标签:EVTX分析, Go, Retryablehttp, Ruby工具, SOC Prime, 加密库, 密码学, 开发工具, 手动系统调用, 日志审计