那个不填充的函数"

admin 2026-10-17 23:47:04 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

那个不填充的函数

原创

利刃信安
利刃信安

利刃信安

2026年8月31日 17:30
北京

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

那些年我们踩过的坑 · 其一

那个不填充的函数

GM/T 0018 里那句话只有十个字。我花了整整一年,才让它变得有意义。


一 · 那句话

GM/T 0018-2023 里,SDF\_Encrypt 的功能说明只有一句话,短得像没写完。

“此函数不对数据进行填充处理。” 十个字。我没多想,把它当一条备注抄进代码。后来的几个月,我以为我读懂了它;再后来的某个下午,用户在电话那头,报出一条 35 字节的地址。从那一刻起,我才开始读这句话里剩下的大部分东西。

我那时做的是 SM4-CBC。分组 16 字节,两个分组恰好 32 字节,而我的测试数据,长度正好都在 32。我写:outlen = data\_len。编译通过。测试通过。上线。一切正常。我甚至把这段代码写得相当干净:

ULONG outlen = data\_len; SDF\_Encrypt(hSession, hKey, SGD\_SM4\_CBC,             IV, data, (ULONG)data\_len,             enc, &outlen);

我把“输出等于输入”当成了这条接口的全部。大多数人也是。可那句话其实传达了两条信息:第一,它不填充;第二,正因为不填充,输出才等于输入。我只读懂了第二条,把第一条留在了下一页。

二 · “不填充”

字面很好懂:明文必须是分组的整数倍,也就是 16 的倍数。但它的设计含义经常被忽略——纯分组运算引擎,不是加密工具箱。它只做一件事:把 SM4 套上 CBC、ECB、CTR,在密码卡上执行。你的明文要不要补齐、用什么方案补,它一概不知,也一概不管。

这恰恰是它和常见密码库的分野。OpenSSL 默认 PKCS#7 填充,Java 默认 PKCS#5(与 PKCS#7 等价),Python 的 cryptography 默认也是 PKCS#7。我们用惯自动填充,于是在脑子里养成一个关于“加密”的心智模型:丢一段任意长度的明文进去,拿一段密文出来,别的都不用我管。

SDF 不这样。它像一扇有洁癖的门:你给我 16 字节一组,我还你 16 字节一组。脏东西,留在门外面。

我那 35 字节的地址,就是门外的脏东西。35 除 16,余 3。差 13 个字节凑满一整块。谜底的形状几乎是自己掉出来的。

三 · 我自己填

答案不在接口里,在接口外面:填充归我,加密归它。35 + 13 = 48,十六的三倍,正好一次还清。值得被写成一行规矩的是,填充值不是固定不变的——它等于补位的个数。补 13 个,每一字节就写 13;哪怕明文已经是 16 的整数倍,按 PKCS#7 也得补一整块 16,不能补零个。

我在加密之前,先替它把这道算术做完:

补充值 = 补位个数;即使整除(如 32)也补一整块 16

0–1516–3132–3435–47 填充13

35 + 13 = 48,恰为 16 的整数倍

// 第一步:调用者自己先填充(PKCS#7) ULONG pad = 16 - (data\_len % 16);   // 35 → 13 ULONG plen = data\_len + pad;        // 35 → 48 BYTE \p = (BYTE \)malloc(plen); memcpy(p, data, data\_len); memset(p + data\_len, (BYTE)pad, pad); // 每字节 = 补的个数 // 第二步:此时长度已是 16 的整数倍 ULONG enc\_len = plen; LONG ret = SDF\_Encrypt(hSession, hKey, SGD\_SM4\_CBC,             IV, p, plen, enc\_data, &enc\_len); free(p); // ret == 0,enc\_len == 48

四 · 收尾

密文回来了,事情只算做完一半。另外一半在对端:读密文最后一个字节,记下它等于 n,从头往前去掉末尾的 n 字节,还原的才是那 35 字节的地址。填充是约定,加密的双方都要遵守同一条。

所以,每逢我又要碰这接口,我会先问自己三个问题。

加密之前,我传进去的 uiDataLength 是 16 的整数倍吗?
加密之后,我核对过返回值是不是 0 吗?
解密之后,我有没有按当初的方案,去掉那层填充?

只要有一个答不上来,我大概率正在踩同一个坑。

五 · 那道门

GM/T 0018 那句话只有十个字,但它不是免责声明,是一道契约,一条边界。加密归我,填充归你。它在文档里划下这条线,剩下的,谁也没替你兜。后来我每次调用 SDF\_Encrypt,都先把 16 除净,才敢把它递给那扇门。

规范从来没有亏待我。亏待我的是我自己的假设——我默认它和 OpenSSL 一样体贴,默认我永远不会收到第 33 字节。直到那通电话。那通电话之后,我在每一个分组边界上,都比从前多停了一拍。

我没有再犯过。


    那个不填充的函数" 网络安全文章

    那个不填充的函数"

    本文通过作者亲身经历,揭示国密标准GM/T0018中SDF\\_Encrypt函数不做数据填充的特性。该函数是纯分组运算引擎,要求明文长度必须为16字节整数倍,填充责任在调用者。作者详细演示了PKCS#7填充实现方法,强调填充值等于补位个数,即使数据长度恰好为16的整数倍也需补一整块16字节,解密后需按约定去除填充。文章提醒开发者勿默认其行为与OpenSSL等库一致,具有很强实践指导价值。
    流量红利退潮,快手想用AI和老友们破局" 网络安全文章

    流量红利退潮,快手想用AI和老友们破局"

    快手在流量红利趋缓背景下,转向存量用户价值经营,通过多元圈层、短直一体、创作者扶持计划及AI工具提升内容与交易效率。二季度营收355亿元同比增1.4%,直播收入下降13.5%,可灵AI收入超8.5亿元增200%。平台强调长期创作者沉淀与私域变现,但面临利润承压与转化效率挑战。
    评论:0   参与:  0