文章总结: 本文通过作者亲身经历,揭示国密标准GM/T0018中SDF_Encrypt函数不做数据填充的特性。该函数是纯分组运算引擎,要求明文长度必须为16字节整数倍,填充责任在调用者。作者详细演示了PKCS#7填充实现方法,强调填充值等于补位个数,即使数据长度恰好为16的整数倍也需补一整块16字节,解密后需按约定去除填充。文章提醒开发者勿默认其行为与OpenSSL等库一致,具有很强实践指导价值。 综合评分: 88 文章分类: 实战经验,安全开发,数据安全
那个不填充的函数
原创
利刃信安 利刃信安
利刃信安
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 字节。直到那通电话。那通电话之后,我在每一个分组边界上,都比从前多停了一拍。
我没有再犯过。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:利刃信安 利刃信安 利刃信安《那个不填充的函数》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。


![[译苑雅集Vol.19]把界面交给AI,Salesforce反而更难被替代?](/images/random/titlepic/13.jpg)







评论