与银行对接 sm4 国密算法 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cbasil
V2EX    PHP

与银行对接 sm4 国密算法

  •  
  •   cbasil 346 天前 5054 次点击
    这是一个创建于 346 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近跟银行对接 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)来解密。
    20 条回复    2024-11-01 14:27:19 +08:00
    momo7411
        1
    momo7411  
       346 天前 via Android
    iv 固定?
    bagel
        2
    bagel  
       346 天前   5
    IV 写死不用看了,屎山加屎而已,至于对不对,根本无人在意。
    nxforce
        3
    nxforce  
       346 天前
    iv 写死,哈哈,那每次加密的密文是不是都一样?
    hervey0424
        4
    hervey0424  
       346 天前
    直接用他们的开发语言弄个中间层比研究这玩意强多了
    cbasil
        5
    cbasil  
    OP
       346 天前
    @bagel 银行加密的 IV 就是默认填 0 生成的,你如果用随机数生成 iv ,解密肯定有问题。至于全零 IV 生成,用 str_repeat("\0", 16)更简洁更明确。
    xshanow
        7
    xshanow  
       346 天前
    @cbasil SM 系列一般要用硬件产品的才合规,我们专门做这类产品的
    InkStone
        8
    InkStone  
       346 天前
    @xshanow 有软认证。不过只能到二级
    48y1951r9G8k7Zou
        9
    48y1951r9G8k7Zou  
       346 天前
    @momo7411 @bagel @joyhub2140

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

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

    有的时候甚至不得不这样做,尤其是在没有可靠的 random source 可用的时候(比如一些嵌入式场景)
    ca2oh4
        10
    ca2oh4  
       346 天前
    php 对接属实有点困难,国密那一套一开始好像是国内的区块链研究机构搞的.记得 有发布官方的 sdk 来着(golang 版本)
    YUCOAT
        11
    YUCOAT  
       346 天前
    我以前搞国密的时候,用到了 GmSSL ,你可以参考一下
    dode
        12
    dode  
       346 天前
    用 Java 包做,bouncycastle
    ntedshen
        13
    ntedshen  
       346 天前
    话说我这里有个阿里的 javasdk 用的个 RSA/ECB/OAEPPadding ,似乎完全找不到其他语言的方案。。。
    找 ai 要了几个 nodejs 的包但是实际上加密加不出来。。。
    现在专门跑了个 tomcat 当接口用着的。。。
    cbasil
        14
    cbasil  
    OP
       346 天前
    @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
        15
    AoEiuV020JP  
       346 天前
    这块都一样,国密和国际算法都是这些坑,
    我公司几年前做接口加固时我写文档教其他各端实现就踩了这种坑, 家家有本难念的经,尤其 js 连“字节数组”的概念都没有,整数类型也没有字节数的概念,我都很难给他们解释,
    最终文档里是单开一页用来举例子,就是涉及到的每种加密算法封装后的输入输出给个具体例子参考,涉及字节数组就强调该字节数组 base64 编码或者 16 进制编码后是某某某,

    但后面其他同事设计别的加密时还是会做出比如 长度 16 的字节数组,先 base64 编码成长度 24 的字符串再截取 16 字符转成 新的 16 字节作为密钥使用,这种意义不明麻烦还降低安全性的操作,但一开始定好了这一套操作后面别人实现就都得做成一样的,
    GiggleSmile
        16
    GiggleSmile  
       346 天前
    @AoEiuV020JP 说得很对。
    cbasil
        17
    cbasil  
    OP
       346 天前   1
    @ntedshen RSA 也是一个大坑,之前对接的一个项目,接口用到私钥签名、公钥验签加上公钥加密、私钥解密。双方交换公钥。折腾了好久,发现 rsa 加密要分段加密。RSA 密钥长度 1024bit ,加密的时候 117 个字符加密一次,然后把所有的密文拼接成一个密文;解密的时候需要 128 个字符解密一下,然后拼接成数据。具体可以看看这篇文章 https://www.cnblogs.com/meetuj/p/14954533.html
    不同语言的加解密处理确实太麻烦了,尤其是对方一句话,我们用的是默认的加密方式。你们自己实现就好了。代码也不给,给一串加密前和加密后的参数。你自己慢慢去试。成功了就是精诚所至金石为开。
    brando
        18
    brando  
       346 天前
    这种我一律要 SDK 的,没这标准咋搞,靠理解还是猜来猜去?
    momo7411
        19
    momo7411  
       345 天前
    @majula 你这里提到用一个自增 id 的密文来等效充当 iv ,那么自增 id 的初始值是不是也得随机生成?
    fengpan567
        20
    fengpan567  
       345 天前
    想起来之前电网做安全测试,有个国密算法功能测试不通过,他们说我们解密出来的都是错误明文,还不给我们看正确的解密明文。最后搞了一周才搞清楚,那群傻只解密到 16 进制,没有完全解密到明文
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2805 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 13:47 PVG 21:47 LAX 06:47 JFK 09:47
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86