在 Go 语言中实现 FPE(Format-Preserving Encryption,格式保留加密)
通常会遵循 NIST 发布的 FF1 或 FF3-1 算法。这些算法允许你在加密数据后,密文仍然保持与原文相同的格式和长度,这对于处理敏感数据(如信用卡号、社会安全号等)在不改变现有系统数据结构的情况下进行加密非常有用。
示例
以下是一个使用 Go 语言 FPE 开源库的示例,以 capitalone/fpe
库为例,它实现了 NIST 批准的 FF1 和 FF3 算法:
package main
import (
"encoding/hex"
"fmt"
"github.com/capitalone/fpe/ff1" // 或者使用 "github.com/capitalone/fpe/ff3"
)
func main() {
// 密钥和 Tweak 应该作为字节数组。
// 在实际应用中,请确保你的密钥是安全生成的,并妥善管理。
// Tweak 类似于 IV,通常不需要保密,但每次加密相同明文时应使用不同的 Tweak。
key, err := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") // 128-bit key (16 bytes)
if err != nil {
panic(err)
}
tweak, err := hex.DecodeString("D8E7920AFA330A73") // 64-bit tweak (8 bytes)
if err != nil {
panic(err)
}
// 创建一个新的 FF1 加密器对象
// 参数:
// 1. radix (基数): 定义了明文和密文的字符集。例如,10 表示数字 0-9。
// 2. maxTweakLen: Tweak 的最大长度。
// 3. key: 加密密钥。
// 4. tweak: 加密 Tweak。
ff1Cipher, err := ff1.NewCipher(10, 8, key, tweak) // 对于数字字符串,radix 为 10
if err != nil {
panic(err)
}
original := "1234567890123456" // 示例:16位数字字符串 (例如信用卡号)
// 加密
ciphertext, err := ff1Cipher.Encrypt(original)
if err != nil {
panic(err)
}
// 解密
plaintext, err := ff1Cipher.Decrypt(ciphertext)
if err != nil {
panic(err)
}
fmt.Println("Original:", original)
fmt.Println("Ciphertext:", ciphertext)
fmt.Println("Plaintext:", plaintext)
// 示例:使用不同的 Tweak 加密相同明文
// 在 FPE 中,Tweak 用于确保相同明文在不同上下文或每次加密时产生不同的密文。
// 假设我们有另一个 Tweak
anotherTweak, err := hex.DecodeString("1234567890ABCDEF")
if err != nil {
panic(err)
}
ciphertext2, err := ff1Cipher.EncryptWithTweak(original, anotherTweak)
if err != nil {
panic(err)
}
fmt.Println("\nOriginal (with another tweak):", original)
fmt.Println("Ciphertext (with another tweak):", ciphertext2)
plaintext2, err := ff1Cipher.DecryptWithTweak(ciphertext2, anotherTweak)
if err != nil {
panic(err)
}
fmt.Println("Plaintext (with another tweak):", plaintext2)
// 如果需要处理包含字母的字符串,可以调整 radix
// 例如,radix 36 包含 0-9 和 a-z
ff1AlphaNumericCipher, err := ff1.NewCipher(36, 8, key, tweak)
if err != nil {
panic(err)
}
alphaNumericOriginal := "abc123xyz"
alphaNumericCiphertext, err := ff1AlphaNumericCipher.Encrypt(alphaNumericOriginal)
if err != nil {
panic(err)
}
alphaNumericPlaintext, err := ff1AlphaNumericCipher.Decrypt(alphaNumericCiphertext)
if err != nil {
panic(err)
}
fmt.Println("\nOriginal (alphanumeric):", alphaNumericOriginal)
fmt.Println("Ciphertext (alphanumeric):", alphaNumericCiphertext)
fmt.Println("Plaintext (alphanumeric):", alphaNumericPlaintext)
}
如何运行这个示例:
- 安装 Go 环境: 确保你的机器上安装了 Go 语言环境。
- 创建项目:
mkdir go-fpe-example
cd go-fpe-example
go mod init go-fpe-example
- 安装 FPE 库:
go get github.com/capitalone/fpe
- 保存代码: 将上述 Go 代码保存为
main.go
文件。 - 运行:
代码说明:
github.com/capitalone/fpe/ff1
: 这是 Go 语言中一个流行的 FPE 实现,支持 NIST FF1 算法。该库也提供了 FF3-1 算法 (github.com/capitalone/fpe/ff3
),但在某些情况下 FF3-1 已被 NIST 建议弃用,因此通常更推荐使用 FF1。- 密钥 (
key
) 和 Tweak (tweak
):- 密钥是加密操作的核心,必须保密。示例中使用了 128 位(16 字节)的密钥。
- Tweak(或称作上下文)是 FPE 中的一个重要参数,它允许你对相同的明文产生不同的密文。例如,在加密信用卡号时,可以把用户的 ID 或者交易 ID 作为 Tweak,这样即使是相同的卡号,在不同的交易中也会生成不同的密文。Tweak 通常不需要保密,但每次加密时需要使用。
NewCipher(radix, maxTweakLen, key, tweak)
:radix
(基数): 这是 FPE 的关键概念,它定义了你加密的字符集。10
用于仅包含数字的字符串(如信用卡号、电话号码)。36
用于包含数字和小写字母的字符串(0-9, a-z)。62
用于包含数字、大写字母和小写字母的字符串(0-9, a-z, A-Z)。- 如果你需要加密包含特殊字符的字符串,可能需要自定义一个更大的 radix 或在加密前进行字符映射。
maxTweakLen
: Tweak 的最大允许长度。
Encrypt(original string)
和 Decrypt(ciphertext string)
: 用于对字符串进行加密和解密。EncryptWithTweak(X string, tweak []byte)
和 DecryptWithTweak(X string, tweak []byte)
: 这些方法允许你在每次加密/解密时指定 Tweak,这对于重复使用同一个 ff1.Cipher
实例但使用不同上下文加密数据非常有用。
重要提示:
- 密钥管理: 在生产环境中,密钥的生成、存储和管理至关重要。切勿将密钥硬编码在代码中。应使用安全的密钥管理系统(KMS)。
- Tweak 的选择: Tweak 应尽可能具有唯一性,以提高 FPE 的安全性。对于敏感数据,不建议使用固定或可预测的 Tweak。
- 性能: FPE 算法通常比传统的块加密算法(如 AES-GCM)计算量更大,因此在对大量数据进行加密时需要考虑其性能影响。
- NIST 标准: FPE 算法(FF1 和 FF3-1)是 NIST 批准的标准,但在使用时仍然需要了解其限制和最佳实践。
通过这个示例,你应该能够理解 Go 语言中 FPE 的基本用法,并开始将其应用于你的项目中。