与银行对接 sm4 国密算法

8 小时 21 分钟前
 cbasil
最近跟银行对接 sm4 国密算法,搞了好久才对接好,我大概讲一下开发中遇到的坑。
php 相关国密算法的教程很少,找了好久才找到 github 上的包[https://github.com/lizhichao/sm]( https://github.com/lizhichao/sm)
配置好后,一运行就报错秘钥长度为 16 位。跟对方沟通后才知道他们给的是 16 进制的 32 位的 key,在 php 中需要用 hex2bin 转成 16 位。
如果对方给的加密模式是 sm4-cbc,还需要配置 iv 。默认 iv 用 hex2bin('00000000000000000000000000000000')生成。不然解密后的字符串前后会有乱码。填充方法一般常用 pkcs5 和 pkcs7 。这二种填充方式概念上没有什么区别,只是 pkcs5 在 blockSize 上固定为 8 bytes,即数据始终会被切割成 8 个字节的数据块,然后计算需要填充的长度.
加密后的字符编码也有 hex 和 base64 区分。

如果 openssl 版本大于 1.1.1 ,就可以用 openssl_decrypt($data, "sm4", $key, $options=OPENSSL_RAW_DATA,$iv)来解密。
2153 次点击
所在节点    PHP
18 条回复
momo7411
8 小时 10 分钟前
iv 固定?
bagel
8 小时 5 分钟前
IV 写死不用看了,屎山加屎而已,至于对不对,根本无人在意。
joyhub2140
7 小时 38 分钟前
iv 写死,哈哈,那每次加密的密文是不是都一样?
hervey0424
7 小时 21 分钟前
直接用他们的开发语言弄个中间层比研究这玩意强多了
cbasil
6 小时 43 分钟前
@bagel 银行加密的 IV 就是默认填 0 生成的,你如果用随机数生成 iv ,解密肯定有问题。至于全零 IV 生成,用 str_repeat("\0", 16)更简洁更明确。
PendingOni
6 小时 13 分钟前
xshanow
6 小时 2 分钟前
@cbasil SM 系列一般要用硬件产品的才合规,我们专门做这类产品的
InkStone
6 小时 0 分钟前
@xshanow 有软认证。不过只能到二级
majula
5 小时 58 分钟前
@momo7411 @bagel @joyhub2140

固定 iv 是可行的,只要你确保第一个 block 的明文不重复(比如用一个自增 id ),且确保其无法被攻击者自由选择即可。这时,第一个 block 的密文等效于一个用 CSPRNG 生成的 iv

NIST SP 800-38A 官方支持了这种用法(见 Appendix C )

有的时候甚至不得不这样做,尤其是在没有可靠的 random source 可用的时候(比如一些嵌入式场景)
ca2oh4
5 小时 46 分钟前
php 对接属实有点困难,国密那一套一开始好像是国内的区块链研究机构搞的.记得 有发布官方的 sdk 来着(golang 版本)
YUCOAT
5 小时 43 分钟前
我以前搞国密的时候,用到了 GmSSL ,你可以参考一下
dode
5 小时 25 分钟前
用 Java 包做,bouncycastle
ntedshen
5 小时 6 分钟前
话说我这里有个阿里的 javasdk 用的个 RSA/ECB/OAEPPadding ,似乎完全找不到其他语言的方案。。。
找 ai 要了几个 nodejs 的包但是实际上加密加不出来。。。
现在专门跑了个 tomcat 当接口用着的。。。
cbasil
4 小时 24 分钟前
@ca2oh4 我当时也考虑用 golang 写一个脚本,然后 php 通过 http 调用。不过后面解决了就不用了。这是当时写的 golang 案例

```golang
package main

import (
"bytes"
"crypto/cipher"
"encoding/hex"
"fmt"

"github.com/tjfoc/gmsm/sm4"
)

// PKCS5Padding 使用 PKCS5 填充
func PKCS5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}

// PKCS5UnPadding 去除 PKCS5 填充
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}

// SM4 CBC 模式加密
func sm4CBCEncrypt(key, plaintext, iv []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}

plaintext = PKCS5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
return ciphertext, nil
}

// SM4 CBC 模式解密
func sm4CBCDecrypt(key, ciphertext, iv []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}

plaintext := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(plaintext, ciphertext)
plaintext = PKCS5UnPadding(plaintext)
return plaintext, nil
}

func main() {
key, _ := hex.DecodeString("key") // 16 字节的十六进制密钥
iv, _ := hex.DecodeString("iv") // 16 字节的 IV
plaintext := []byte("This is a secret message.")

// 加密
ciphertext, err := sm4CBCEncrypt(key, plaintext, iv)
if err != nil {
fmt.Println("Encryption error:", err)
return
}
fmt.Printf("Ciphertext (hex): %s\n", hex.EncodeToString(ciphertext))
// 解密
decrypted, err := sm4CBCDecrypt(key, ciphertext, iv)
if err != nil {
fmt.Println("Decryption error:", err)
return
}
fmt.Printf("Decrypted text: %s\n", decrypted)
}
```
AoEiuV020JP
4 小时 14 分钟前
这块都一样,国密和国际算法都是这些坑,
我公司几年前做接口加固时我写文档教其他各端实现就踩了这种坑, 家家有本难念的经,尤其 js 连“字节数组”的概念都没有,整数类型也没有字节数的概念,我都很难给他们解释,
最终文档里是单开一页用来举例子,就是涉及到的每种加密算法封装后的输入输出给个具体例子参考,涉及字节数组就强调该字节数组 base64 编码或者 16 进制编码后是某某某,

但后面其他同事设计别的加密时还是会做出比如 长度 16 的字节数组,先 base64 编码成长度 24 的字符串再截取 16 字符转成 新的 16 字节作为密钥使用,这种意义不明麻烦还降低安全性的操作,但一开始定好了这一套操作后面别人实现就都得做成一样的,
GiggleSmile
3 小时 42 分钟前
@AoEiuV020JP 说得很对。
cbasil
3 小时 23 分钟前
@ntedshen RSA 也是一个大坑,之前对接的一个项目,接口用到私钥签名、公钥验签加上公钥加密、私钥解密。双方交换公钥。折腾了好久,发现 rsa 加密要分段加密。RSA 密钥长度 1024bit ,加密的时候 117 个字符加密一次,然后把所有的密文拼接成一个密文;解密的时候需要 128 个字符解密一下,然后拼接成数据。具体可以看看这篇文章 https://www.cnblogs.com/meetuj/p/14954533.html
不同语言的加解密处理确实太麻烦了,尤其是对方一句话,我们用的是默认的加密方式。你们自己实现就好了。代码也不给,给一串加密前和加密后的参数。你自己慢慢去试。成功了就是精诚所至金石为开。
brando
2 小时 12 分钟前
这种我一律要 SDK 的,没这标准咋搞,靠理解还是猜来猜去?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1085268

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX