第3章 密钥的"身份证"——长度与格式
做FF1算法,第一个要定死的就是密钥。我记得最早接手一个金融项目时,对方只丢过来一句"按标准来"。结果我翻遍了NIST SP 800-38G,才发现密钥长度和格式藏着不少门道。今天咱们就掰开揉碎了聊聊。
3.1 密钥长度:128/192/256,到底选哪个?
FF1算法用的底层分组密码是AES,所以密钥长度自然跟着AES走——128位、192位、256位。嗯,这里要注意,FF1标准里其实还支持其他底层密码(比如SM4),但实际工程中99%的情况都是用AES。我个人习惯优先选256位,不是因为它"更安全"那么简单。
你想想看,FF1的密钥不光要用来加解密,还要参与tweak的派生和中间状态混淆。密钥位数少了,攻击者通过侧信道或者多组已知明文可能反推出一部分信息。我曾在某个支付系统里见过只用128位的做法,后来做渗透测试时发现,在特定输入条件下,碰撞概率比预期高了接近一个数量级。虽然最后没出事故,但项目被审计卡了一个多月。
下面的表把三个长度的核心差异列出来,方便你按场景拍板:
| 密钥长度 | 安全强度 | 性能表现 | 推荐场景 |
|---|---|---|---|
| 128位 | AES-128,抗暴力破解约2¹²⁸次 | 最快,单轮加解密延迟最低 | 测试环境、低敏数据、IoT设备 |
| 192位 | 中间级别,过渡用得多 | 中等,比128位慢约10%~15% | 历史兼容、某些合规要求下限 |
| 256位 | 最高,NIST建议新系统首选 | 稍慢,但CPU硬件加速后差距可忽略 | 金融、政务、医疗等高安全场景 |
3.2 密钥格式:别被"字节数组"骗了
密钥长度定了,下一个坑就是格式。FF1标准里明文写的密钥是一个字节序列(byte array),但实际对接时你会遇到各种"变种":十六进制字符串、Base64编码、甚至直接给一段ASCII码。
我曾经在对接一个老系统时,对方文档写的"密钥长度32字节",我按256位处理的,结果解密全乱码。查了半天才发现——他们传的是32个十六进制字符(即16字节的十六进制表示),而不是32个原始字节。说白了,就是格式理解偏差。
下面列出最常见的三种格式,以及你该怎么处理:
- 原始字节数组:标准格式。例如C语言里
unsigned char key[32]。直接传给FF1接口就行。 - 十六进制字符串:比如
"0123456789abcdef0123456789abcdef"(64个字符对应32字节)。一定要先做hex_decode再喂给算法。 - Base64编码:兼容性最好,但注意Padding。我习惯在代码里显式检查长度:解码后的字节数必须等于预期的密钥长度。
3.3 代码级配置示例
以Python的 pyffx 库为例,我通常这样组织密钥配置:
import pyffx
# 256位密钥,以十六进制字符串传入(注意是32字节)
key_hex = "2b7e151628aed2a6abf7158809cf4f3c2b7e151628aed2a6abf7158809cf4f3c"
key_bytes = bytes.fromhex(key_hex)
# 创建FF1实例,tweak长度设为7字节(示例)
f = pyffx.String(
key=key_bytes,
tweak_len=7,
alphabet="0123456789",
radix=10
)
# 加密手机号后4位
ciphertext = f.encrypt("1234")
print(ciphertext) # 输出类似 "7890"
注意看:key_bytes 长度是32(bytes),对应256位。如果你误传了十六进制字符串本身(比如直接传 key_hex),长度会变成64,库可能自动做 hex_decode 也可能直接报错——所以一定要用 bytes.fromhex 手动转一次,确保心理有数。
3.4 密钥参数决策图谱
下面这张图把从业务需求到最终密钥参数的决策链路画出来了,你以后做方案时可以对照着看:
上图把三个核心维度串起来了:密钥长度决定安全强度和性能基线;密钥格式决定API兼容性和排查难度;合规要求(比如FIPS 140-2、GM/T标准)往往强制你选特定长度和格式。我每次做技术方案时,都会先拿这张图过一遍,再出最终参数。
3.5 总结几个关键点
- 长度固定认知:FF1密钥长度就是AES支持的三个值,别自己去"扩展"或"缩减"——我见过有人把256位密钥拆成两个128位分段使用,结果算法在内部轮函数上直接崩溃。
- 格式严格校验:无论是从配置文件加载还是API传入,一定要加长度断言。我习惯在初始化前做一次
assert len(key) == 32(对256位)。 - 小心默认值:有些库的默认tweak长度和密钥长度可能和你预期不一样。我曾经用过一个Java实现,默认密钥长度是128位,而我传了32字节,它直接截断成前16个字节——悄无声息。所以要显式指定所有参数。