lpilp / phpsm2sm3sm4

php版本,支持国密SM2的签名算法,非对称加解密,SM3的hash, SM4的对称加解密
320 stars 74 forks source link

对接招行使用sm3withsm2加密,按照示例写的加签方法在招行验证失败。 #80

Closed kpsanmao closed 3 months ago

kpsanmao commented 3 months ago

作者好,我对加密算法不太熟悉,最近在做招行薪福通的对接,加签和验证的最后一步遇到了问题。能麻烦您指点一下吗? 项目测试的APPID:809131cf-af1d-4085-a3e9-926fd0e304a8 项目测试的authoritySecret:23895fd8787e94368c4d05407a61f351567fd7166cbbffba69ea2d72e62f9ba0 没有公钥信息。 按照招行的文档: 需要对以下字符串$signStr进行sm3withsm2加签: POST /ORG/orgqry/common/OPORGQRA?CSCAPPUID=809131cf-af1d-4085-a3e9-926fd0e304a8&CSCPRJCOD=XFV18267&CSCUSRNBR=V000T&CSCUSRUID=11839193&CSCREQTIM=1722304884880\nx-alb-digest: {"secretMsg":"b5e7e0541e84b08363524a8ae1e25edb"}\nx-alb-timestamp: 1722304884

招行的加签代码如下:

  /**  
     *  
     * @param authoritySecret 在应用详情->开发管理中查看  
     * @param signStr 需要加签的字符串  
     * @return 返回签名信息  
     * @throws Exception 签名异常  
     */  
    public static String sm3withsm2Signature(String authoritySecret, String signStr) throws Exception {

        byte[] key = Hex.decode(authoritySecret);  
        byte[] data = signStr.getBytes();

        // 获得一条签名曲线  
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");  
        // 构造domain函数  
        ECDomainParameters domainParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());

        // 国密要求,ID默认值为1234567812345678  
        ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(1, key), domainParameters);  
        ParametersWithID parameters = new ParametersWithID(privateKey, "1234567812345678".getBytes());  
        // 初始化签名实例  
        SM2Signer signer = new SM2Signer();  
        signer.init(true, parameters);  
        signer.update(data, 0, data.length);

        return Hex.toHexString(decodeDERSignature(signer.generateSignature()));  
    }  

    private static byte[] decodeDERSignature(byte[] signature) throws Exception {  
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));  
        ASN1Sequence primitive = (ASN1Sequence) stream.readObject();  
        Enumeration enumeration = primitive.getObjects();  
        BigInteger R = ((ASN1Integer) enumeration.nextElement()).getValue();  
        BigInteger S = ((ASN1Integer) enumeration.nextElement()).getValue();  
        byte[] bytes = new byte[64];  
        byte[] r = format(R.toByteArray());  
        byte[] s = format(S.toByteArray());  
        System.arraycopy(r, 0, bytes, 0, 32);  
        System.arraycopy(s, 0, bytes, 32, 32);  
        return bytes;  
    }

    private static byte[] format(byte[] value) {  
        if (value.length == 32) {  
            return value;  
        } else {  
            byte[] bytes = new byte[32];  
            if (value.length > 32) {  
                System.arraycopy(value, value.length - 32, bytes, 0, 32);  
            } else {  
                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);  
            }  
            return bytes;  
        }  
    }

招行并没有提供示例的加签结果。 我使用示例中的sm2加密代码如下:

$signStr = 'POST /ORG/orgqry/common/OPORGQRA?CSCAPPUID=809131cf-af1d-4085-a3e9-926fd0e304a8&CSCPRJCOD=XFV18267&CSCUSRNBR=V000T&CSCUSRUID=11839193&CSCREQTIM=1722304884880\nx-alb-digest: {"secretMsg":"b5e7e0541e84b08363524a8ae1e25edb"}\nx-alb-timestamp: 1722304884';
$authoritySecret='23895fd8787e94368c4d05407a61f351567fd7166cbbffba69ea2d72e62f9ba0';
$sm2 = new RtSm2('base64',true);
$apiSign = $sm2->doSign($signStr,$authoritySecret,'1234567812345678');
//$apiSign = $sm2->doSign($signStr,bin2hex(base64_decode($authoritySecret)),'1234567812345678');
//dd($apiSign);
dd(SmSignFormatRS::asn1_to_rs($apiSign));

得出的两种格式去 https://xft.cmbchina.com/open/#/simulate-tool?intent=openSimulateTool 网站最下面进行验签都提示签名失败.

lpilp commented 3 months ago

请查 readme最下边的特别注意, $sm2 = new RtSm2('base64',true); 第二参数请设为false 试下, 还有就是可能对要签名的参数的问题 “CSCAPPUID=809131cf-af1d-4085-a3e9-926fd0e304a8&CSCPRJCOD=XFV18267&CSCUSRNBR=V000T&CSCUSRUID=11839193&CSCREQTIM=1722304884880” 一般是签名这些吧,后面那些应该不用放进去,看他们给的 java 的sdk里签名的数据是什么

kpsanmao commented 3 months ago

感谢回复,设置false仍然提示验签失败,签名字符串是按照招行文档进行拼接的。

1.先生成签名字符串signStr,拼接规则为: "POST " + path + "\n" + "x-alb-digest: " + body字符串 + "\n" + "x-alb-timestamp: " + x-alb-timestamp对应的值。 比如: 请求接口URL为https://api.cmbchina.com/xft/apm/apm/EAIAGPAY body参数为"{"SYCOMOPAY":[{"SESTKN":"XXXX"}]}"。 则先生成的生成字符串signStr为为: POST /xft/apm/apm/EAIAGPAY?CSCAPPUID=XXX&CSCPRJCOD=XXX&CSCREQTIM=XXX&CSCUSRNBR=XXX&CSCUSRUID=XXXX\nx-alb-digest: {"SYCOMOPAY":[{"SESTKN":"XXXX"}]}\nx-alb-timestamp: 1657614229

他们java sdk没有源码,我看了c#的sdk是包含body的: c# sdk中生成签名字符串代码 string signStr = "POST " + signInf.Path + "\n" + "x-alb-digest: " + requestBody + "\n" + "x-alb-timestamp: " + signInf.Timestamp;

我的php代码生成签名字符串代码: $signStr = 'POST ' . $url . '?' . $this->flatten($config) . '\nx-alb-digest:' . $body . '\nx-alb-timestamp:' . $timestampS;

lpilp commented 3 months ago
string signStr = "POST " + signInf.Path + "\n" + "x-alb-digest: " + requestBody + "\n" + "x-alb-timestamp: " + signInf.Timestamp;

那你用我的 test例子里的公私钥,然后用C# 签名上面的的具体信息,再用我的代码签名下同样的数据,发给我看下, 就用这一组公私钥吧:

$publicKey = '04eb4b8bbe15e3ad94b85196adc2c6f694436b3c1336170fd1daac8b10d2b8824ada9687c138fb81590e0f66ab9678161732ac0d7866b169e76b74483285f2bc04';
$privateKey = '0bc1c1d2771b64ba1922d72f8a451cd09a82176f74d975d484ec62c862176b75';
$userId = '1234567812345678';
lpilp commented 3 months ago

刚发现一个问题,你的PHP代码中 $signStr = 'POST ' . $url . '?' . $this->flatten($config) . '\nx-alb-digest:' . $body . '\nx-alb-timestamp:' . $timestampS; 如 '\nx-alb-timestamp:' 得用双引号,

kpsanmao commented 3 months ago

我刚才尝试运行了一下 c# sdk,发现他们返回的签名是128位的,可以验签: c# sdk生成的要签名的字符串 POST /ORG/orgqry/common/OPORGQRA?CSCAPPUID=7366e866-ce19-40eb-8602-ae5de76d6475&CSCPRJCOD=XFV18267&CSCUSRUID=AUTO0001&CSCREQTIM=1722328133664&CSCUSRNBR=A0001 x-alb-digest: {"secretMsg":"2A574AD0457B6C19C489395DB0255654"} x-alb-timestamp: 1722328133 c# sdk对上面字符串sm2加密生成的签名,我试了几次,每次会变(会变应该是招行文档中说明的:对signStr进行SM2加签后的结果,由于计算中含有随机数,本参数仅提供加密后的校验),但是长度固定是128位的。 b888b69c69318dacaedc03b0f8c42a0ad894318f50dfaba1871de1689f15e1ec1d301ec532ae14196521979a07d52d5d2be95715f32afa556eab28ccb1446590

微信截图_20240730163942

kpsanmao commented 3 months ago
string signStr = "POST " + signInf.Path + "\n" + "x-alb-digest: " + requestBody + "\n" + "x-alb-timestamp: " + signInf.Timestamp;

那你用我的 test例子里的公私钥,然后用C# 签名上面的的具体信息,再用我的代码签名下同样的数据,发给我看下, 就用这一组公私钥吧:

$publicKey = '04eb4b8bbe15e3ad94b85196adc2c6f694436b3c1336170fd1daac8b10d2b8824ada9687c138fb81590e0f66ab9678161732ac0d7866b169e76b74483285f2bc04';
$privateKey = '0bc1c1d2771b64ba1922d72f8a451cd09a82176f74d975d484ec62c862176b75';
$userId = '1234567812345678';

---------明文密钥签名--------------------------- 304602210082290ebce34212b489ff78bf4de93bcdcc7b3a9fa0d64ad84bae1860483c3f4e022100f81715c24bdc25e946ace71fd4ddb00e66062b22b64b71fa93ce64b12329a638 ---------明文密钥验签--------------------------- bool(true)

$publicKey = '04eb4b8bbe15e3ad94b85196adc2c6f694436b3c1336170fd1daac8b10d2b8824ada9687c138fb81590e0f66ab9678161732ac0d7866b169e76b74483285f2bc04';
$privateKey = '0bc1c1d2771b64ba1922d72f8a451cd09a82176f74d975d484ec62c862176b75';
$userId = '1234567812345678';
$signStr = 'POST /ORG/orgqry/common/OPORGQRA?CSCAPPUID=7366e866-ce19-40eb-8602-ae5de76d6475&CSCPRJCOD=XFV18267&CSCUSRUID=AUTO0001&CSCREQTIM=1722328133664&CSCUSRNBR=A0001'
        ."\n".'x-alb-digest: {"secretMsg":"2A574AD0457B6C19C489395DB0255654"}'
        ."\n".'x-alb-timestamp: 1722328133';

$sm2 = new RtSm2();
echo "\n---------明文密钥签名---------------------------\n";
$sign = $sm2->doSign( $signStr, $privateKey, $userId);
print_r($sign);
echo "\n---------明文密钥验签---------------------------\n";
var_dump($sm2->verifySign( $signStr, $sign, $publicKey, $userId ));
exit;
lpilp commented 3 months ago

理论上不太可能每次都是一样长度的,如果每次都是 128的话,说明 C# 签名生成的是 r+s 格式的, PHP生成的标准的 asn1(r,s) 格式需要一样

kpsanmao commented 3 months ago

您好,实在不好意思麻烦您,我卡了两天了,改成r+s格式的仍然不好使,方便加一下您的联系方式帮我看看吗?我的QQ:369209726

kpsanmao commented 3 months ago

谢谢大佬开源的包,完美解决问题,再次感谢。

m1183909358 commented 3 months ago

我这边看了上述解决办法还是为解决,需要将字符串通过sm2withsm3算法进行加密,我看过java代码是使用sm2p256v1,需要怎么解决

kpsanmao commented 3 months ago

包是支持的,我的经验:一是检查要加签的字符串是否拼接正确,二是要确定签名的格式,扩展包默认返回的是asn1(r,s)的格式,而我对接的招行薪福通返回的是r+s的。格式的特征参考说明。

获取 Outlook for iOShttps://aka.ms/o0ukef


发件人: mengxianfengDream @.> 发送时间: Thursday, August 8, 2024 5:29:54 PM 收件人: lpilp/phpsm2sm3sm4 @.> 抄送: kpsanmao @.>; Author @.> 主题: Re: [lpilp/phpsm2sm3sm4] 对接招行使用sm3withsm2加密,按照示例写的加签方法在招行验证失败。 (Issue #80)

我这边看了上述解决办法还是为解决,需要将字符串通过sm2withsm3算法进行加密,我看过java代码是使用sm2p256v1,需要怎么解决

― Reply to this email directly, view it on GitHubhttps://github.com/lpilp/phpsm2sm3sm4/issues/80#issuecomment-2275370784, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEG7ZCFSOMMEJIVDDDAJVNTZQM3BFAVCNFSM6AAAAABLVOCAUSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENZVGM3TANZYGQ. You are receiving this because you authored the thread.Message ID: @.***>

lpilp commented 3 months ago

sm2p256v1 就是国密的sm2 椭圆,只是他们那么写而已, java里用的BC加密库的吧,它说的 sm2p256v1 就是正常的sm2 椭圆,国密椭圆就只有一个