go如何对gmssl命令生成的密文做加解密和验签?

1. 运行环境

开发环境: Kubuntu22.04
GO: go version go1.22.1 linux/amd64

2. 问题描述?

由php调用gmssl命令生成的密文(密钥也是用gmssl命令生成的), 想用go进行解密, 用大佬的包github.com/deatil/go-cryptobin 可以解密但是验签一直都不正确,
在包的test文件里面也没有找到示例, 希望大佬们帮忙看下应该调用那些方法去做加解密和验签.
非常感谢!

代码部分

package  sm_algorithm

import  (
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/deatil/go-cryptobin/cryptobin/sm2"
sm2p256 "github.com/deatil/go-cryptobin/gm/sm2"
)

func  UseGmsslDecode()  {

    selfPri := `-----BEGIN EC PARAMETERS-----
BggqgRzPVQGCLQ==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILVZdvMydZGiSaiwYU0u9sASEi2i3WwYE38MZjRvpGgroAoGCCqBHM9V
AYItoUQDQgAEe6Nc0BJgsyrcKmbpYDox7iX3adD165XA0NnNmDkk/XmJ5xK/Lfnm
MSTaI4vA+UGpGw5kqhKAbFzHJyKjgFz2sQ==
-----END EC PRIVATE KEY-----
`
    selfpub := `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEe6Nc0BJgsyrcKmbpYDox7iX3adD1
65XA0NnNmDkk/XmJ5xK/LfnmMSTaI4vA+UGpGw5kqhKAbFzHJyKjgFz2sQ==
-----END PUBLIC KEY-----
`
    stdPri :=  `-----BEGIN EC PARAMETERS-----
BggqgRzPVQGCLQ==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDA4QHiJv2gju/CVF8PzisNDfR4z8AH9nopYGATtXj0poAoGCCqBHM9V
AYItoUQDQgAEd5tZ/XlQgV9AbJbU5JuzZimcK/LCOX+xNwdI1XHHkIGl3W0VBmGR
BK3VxkBSvp8tsGkZsxEmA7ngXyECzrDiuA==
-----END EC PRIVATE KEY-----
`
    stdPub :=  `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEd5tZ/XlQgV9AbJbU5JuzZimcK/LC
OX+xNwdI1XHHkIGl3W0VBmGRBK3VxkBSvp8tsGkZsxEmA7ngXyECzrDiuA==
-----END PUBLIC KEY-----
`
    // priBlock, priErr := x509.ParseECPrivateKey([]byte(qnPri))
    // fmt.Println(priBlock, " -- ", priErr)
    priBlock, prirest := pem.Decode([]byte(qnPri))
    fmt.Println(priBlock.Type,  " -- ", prirest)
    priBlock1, prirest1 := pem.Decode(prirest)
    fmt.Println(priBlock1.Type, priBlock1.Bytes,  " -- ", prirest1)
    pri, priErr := sm2p256.ParseSM2PrivateKey(priBlock1.Bytes)

    fmt.Println(pri, priErr)
    fmt.Println("------------------------------------- 666")
    kdBlock, _ := pem.Decode([]byte(kdPub))
    fmt.Println(kdBlock.Type)
    pub, pubErr := sm2p256.ParsePublicKey(kdBlock.Bytes)
    fmt.Println(pub, pubErr)

    // 加密前是一个json字符串 {"data":"qwe"}
    encrypted :=  `MH8CIQC5vLQm7+4JYg5MD39ViKgeuHnAN3BZpzD36pHYOada9QIgLiKsD1GLVRW5bW7sanplYCi+
    +e6wuarVffKZDnTTWCkEIMcgRSAXgDLhEJDtmed4LCPdRitNjd3ywVpfi12b5rchBBaObgYaK/8s
    h6D5seFQFqp7B246d9lr`
    sign :=  `MEYCIQDr7Vgvt0pfMddIa94dxvc3IkpEvkMhm07A0NEKASHcWwIhANPaZzmsV5m4I3TKkWecCcV1
    jGAJaWDORkhDVOkYH2Rt`
    fmt.Println(encrypted,  " --- ", sign)

    encryptedAfter, _ := base64.StdEncoding.DecodeString(encrypted)
    signAfter, _ := base64.StdEncoding.DecodeString(sign)
    // fmt.Println(string(signAfter))

    result, resultErr := pri.DecryptASN1(encryptedAfter, sm2p256.EncrypterOpts{
        Mode: sm2p256.C1C3C2,
    })
    // 解密得到的是 ["{\"data\":\"qwe\"}"] 和加密前的不符, 感觉有点小问题
    fmt.Println(string(result),  " -- ", resultErr)
    resultBool := pri.Verify(encryptedAfter, signAfter,  nil)
    // resultBool := pub.Verify([]byte(encrypted), signAfter, nil)
    fmt.Println("--- sign verify: ", resultBool)
    var jsonData interface{}
    json.Unmarshal(result,  &jsonData)
    // fmt.Printf("%T \n", jsonData)
    fmt.Println(jsonData)
    // ecData := sm2.FromBase64String(encrypted).FromPKCS8PrivateKey(pri.).SetMode("C1C2C3").Decrypt().ToString()
}

为了防止代码复制格式问题, 我把公私钥再贴一下

"selfPri": "-----BEGIN EC PARAMETERS-----
BggqgRzPVQGCLQ==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILVZdvMydZGiSaiwYU0u9sASEi2i3WwYE38MZjRvpGgroAoGCCqBHM9V
AYItoUQDQgAEe6Nc0BJgsyrcKmbpYDox7iX3adD165XA0NnNmDkk/XmJ5xK/Lfnm
MSTaI4vA+UGpGw5kqhKAbFzHJyKjgFz2sQ==
-----END EC PRIVATE KEY-----"

"selfPub": "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEe6Nc0BJgsyrcKmbpYDox7iX3adD1
65XA0NnNmDkk/XmJ5xK/LfnmMSTaI4vA+UGpGw5kqhKAbFzHJyKjgFz2sQ==
-----END PUBLIC KEY-----"


"stdPri": "-----BEGIN EC PARAMETERS-----
BggqgRzPVQGCLQ==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDA4QHiJv2gju/CVF8PzisNDfR4z8AH9nopYGATtXj0poAoGCCqBHM9V
AYItoUQDQgAEd5tZ/XlQgV9AbJbU5JuzZimcK/LCOX+xNwdI1XHHkIGl3W0VBmGR
BK3VxkBSvp8tsGkZsxEmA7ngXyECzrDiuA==
-----END EC PRIVATE KEY-----"

"stdPub": "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEd5tZ/XlQgV9AbJbU5JuzZimcK/LC
OX+xNwdI1XHHkIGl3W0VBmGRBK3VxkBSvp8tsGkZsxEmA7ngXyECzrDiuA==
-----END PUBLIC KEY-----"

这里是php做解密和验签的操作

最佳答案

@deatil 我刚才在又试了一下, 把验签方法换了一下又正确了
代码这样写的

    encrypted := `MH8CIQC5vLQm7+4JYg5MD39ViKgeuHnAN3BZpzD36pHYOada9QIgLiKsD1GLVRW5bW7sanplYCi+
+e6wuarVffKZDnTTWCkEIMcgRSAXgDLhEJDtmed4LCPdRitNjd3ywVpfi12b5rchBBaObgYaK/8s
h6D5seFQFqp7B246d9lr`
    sign := `MEYCIQDr7Vgvt0pfMddIa94dxvc3IkpEvkMhm07A0NEKASHcWwIhANPaZzmsV5m4I3TKkWecCcV1
jGAJaWDORkhDVOkYH2Rt`
    signAfter, _ := base64.StdEncoding.DecodeString(sign)
    uid := base64.StdEncoding.EncodeToString([]byte("qinnong"))
    signOpt := sm2p256.SignerOpts{Uid: []byte(uid)}
    resultBool := pub.Verify([]byte(encrypted), signAfter, signOpt)

想在问下大佬在解密之后两边的中括号应该如何处理?有什么更好的方式作处理?

2周前 评论
deatil 2周前
Harvoc (作者) (楼主) 2周前
deatil 2周前
讨论数量: 8

不太清楚是php的sm2的情况,不过看到初始化的时候有设置 qinnong,怀疑跟uid设置有关,需要确认 php 用的 uid 是什么,比如这种: github.com/deatil/go-cryptobin/blo...

解密能够解出来那就是解密没有问题,至于为啥会出现json字符传包裹了中括号,得看加密的时候做了哪些处理逻辑

2周前 评论

@deatil 大佬这里各种姿势都试了一下还是不得行,这是我的代码

    encrypted := `MH8CIQC5vLQm7+4JYg5MD39ViKgeuHnAN3BZpzD36pHYOada9QIgLiKsD1GLVRW5bW7sanplYCi+
+e6wuarVffKZDnTTWCkEIMcgRSAXgDLhEJDtmed4LCPdRitNjd3ywVpfi12b5rchBBaObgYaK/8s
h6D5seFQFqp7B246d9lr`
    sign := `MEYCIQDr7Vgvt0pfMddIa94dxvc3IkpEvkMhm07A0NEKASHcWwIhANPaZzmsV5m4I3TKkWecCcV1
jGAJaWDORkhDVOkYH2Rt`
    signAfter, _ := base64.StdEncoding.DecodeString(sign)
    uid := base64.StdEncoding.EncodeToString([]byte("qinnong"))
    signOpt := sm2p256.SignerOpts{Uid: []byte(uid)}
    fmt.Println("uid base64_encode: ", uid, []byte(uid), signOpt)
    resultBool := pub.VerifyBytes([]byte(encrypted), signAfter, signOpt)
    fmt.Println("--- sign verify: ", resultBool)

这里是php的逻辑加签、验签


    public function sign(string $content): string
    {
        $command = vsprintf('echo "%s" | base64 -d | %s sm2utl -sign -inkey %s -id %s | base64', [
            base64_encode($content),
            self::$gmsslBin,
            $this->privateKey,
            base64_encode($this->id),
        ]);
        if ($result = self::process($command, $error)) {
            return $result;
        }
        throw new Exception('签名失败: ' . $error);
    }

    public function verify(string $content, string $sign): bool
    {
        $date = date('Ymd');
        $sigFile = '/tmp/'.$date.'/'. uniqid(microtime(true),true).mt_rand(1,1000000);
        $command = vsprintf('mkdir -p /tmp/"%s" && echo "%s" | base64 -d | base64 -d > %s && echo "%s" | base64 -d | %s sm2utl -verify -pubin -inkey %s -sigfile %s -id %s',
                [
                    $date,
                    base64_encode($sign),
                    $sigFile,
                    base64_encode($content),
                    self::$gmsslBin,
                    $this->publicKey,
                    $sigFile,
                    base64_encode($this->id),
                ]);
        $result = self::process($command, $error);
        return 'Signature Verification Successful' === trim($result);
    }

麻烦大佬有空帮忙瞅瞅

2周前 评论
deatil 2周前

@deatil

公钥用的是这个

"stdPub": "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEd5tZ/XlQgV9AbJbU5JuzZimcK/LC
OX+xNwdI1XHHkIGl3W0VBmGRBK3VxkBSvp8tsGkZsxEmA7ngXyECzrDiuA==
-----END PUBLIC KEY-----"

这个php加解密验签类有点长

<?php

namespace std\auth;

use Exception;
use Symfony\Component\Process\Process;

/**
 * Class SM2
 *
 * @package std\auth
 */
class SM2
{
    public static $gmsslBin = 'gmssl';

    /**
     * @var string
     */
    public $id;

    /**
     * @var string 私钥文件地址
     */
    protected $privateKey;

    /**
     * @var string 公钥文件地址
     */
    protected $publicKey;

    /**
     * SM2 constructor.
     *
     * @param string $id
     * @param string $privateKey
     * @param string $publicKey
     *
     * @throws Exception
     */
    public function __construct(string $id, string $privateKey, string $publicKey)
    {
        if (!file_exists($privateKey)) {
            throw new Exception('私钥文件不存在');
        }
        if (!file_exists($publicKey)) {
            throw new Exception('公钥文件不存在');
        }

        $this->id = $id;

        $this->privateKey = $privateKey;
        $this->publicKey  = $publicKey;
    }

    /**
     * @param string $command 命令行
     * @param string $error
     *
     * @return string
     */
    protected static function process($command, &$error)
    {
        $process = Process::fromShellCommandline($command);
        $process->run();

        $error = rtrim($process->getErrorOutput());

        return rtrim($process->getOutput());
    }

    /**
     * 生成公钥
     *
     * @param string $privateKeyPath 私钥地址
     * @param string $publicKeyPath  公钥地址
     *
     * @return bool
     */
    public static function genPublicKey($privateKeyPath, $publicKeyPath)
    {
        $command = vsprintf('%s ec -in %s -pubout -out %s', [
            self::$gmsslBin,
            $privateKeyPath,
            $publicKeyPath,
        ]);

        self::process($command, $error);

        return file_exists($publicKeyPath);
    }

    /**
     * 生成私钥
     *
     * @param string $privateKeyPath 私钥地址
     *
     * @return bool
     */
    public static function genPrivateKey($privateKeyPath)
    {
        $command = vsprintf('%s ecparam -genkey -name SM2 -out %s', [
            self::$gmsslBin,
            $privateKeyPath,
        ]);

        self::process($command, $error);

        return file_exists($privateKeyPath);
    }

    /**
     * @param string $content
     *
     * @return string
     * @throws Exception
     */
    public function sign(string $content): string
    {
        $command = vsprintf('echo "%s" | base64 -d | %s sm2utl -sign -inkey %s -id %s | base64', [
            base64_encode($content),
            self::$gmsslBin,
            $this->privateKey,
            base64_encode($this->id),
        ]);

        if ($result = self::process($command, $error)) {
            return $result;
        }

        throw new Exception('签名失败: ' . $error);
    }

    /**
     * @param string $content
     * @param string $sign
     *
     * @return bool
     */
    public function verify(string $content, string $sign): bool
    {
        $date = date('Ymd');
        $sigFile = '/tmp/'.$date.'/'. uniqid(microtime(true),true).mt_rand(1,1000000);

        $command =
            vsprintf('mkdir -p /tmp/"%s" && echo "%s" | base64 -d | base64 -d > %s && echo "%s" | base64 -d | %s sm2utl -verify -pubin -inkey %s -sigfile %s -id %s',
                [
                    $date,
                    base64_encode($sign),
                    $sigFile,
                    base64_encode($content),
                    self::$gmsslBin,
                    $this->publicKey,
                    $sigFile,
                    base64_encode($this->id),
                ]);

        $result = self::process($command, $error);

        return 'Signature Verification Successful' === trim($result);
    }

    /**
     * @param string $content 字符长度最多为65535,超过部分会被丢弃
     *
     * @return string
     * @throws Exception
     */
    public function encrypt(string $content): string
    {
        $content = json_encode([$content], JSON_UNESCAPED_UNICODE); // 解决前后部分字符丢失问题
        $command = vsprintf('echo "%s" | base64 -d | %s sm2utl -encrypt -pubin -inkey %s | base64', [
            base64_encode($content),
            self::$gmsslBin,
            $this->publicKey,
        ]);

        if ($result = self::process($command, $error)) {
            return $result;
        }

        throw new Exception('加密失败: ' . $error);
    }

    /**
     * @param string $content
     *
     * @return string
     * @throws Exception
     */
    public function decrypt(string $content): string
    {
        $command = vsprintf('echo "%s" | base64 -d | base64 -d | %s sm2utl -decrypt -inkey %s', [
            base64_encode($content),
            self::$gmsslBin,
            $this->privateKey,
        ]);
        if ($result = self::process($command, $error)) {
            return current(json_decode($result, true));
        }

        throw new Exception('解密失败: ' . $error);
    }
}

2周前 评论

@deatil 我刚才在又试了一下, 把验签方法换了一下又正确了
代码这样写的

    encrypted := `MH8CIQC5vLQm7+4JYg5MD39ViKgeuHnAN3BZpzD36pHYOada9QIgLiKsD1GLVRW5bW7sanplYCi+
+e6wuarVffKZDnTTWCkEIMcgRSAXgDLhEJDtmed4LCPdRitNjd3ywVpfi12b5rchBBaObgYaK/8s
h6D5seFQFqp7B246d9lr`
    sign := `MEYCIQDr7Vgvt0pfMddIa94dxvc3IkpEvkMhm07A0NEKASHcWwIhANPaZzmsV5m4I3TKkWecCcV1
jGAJaWDORkhDVOkYH2Rt`
    signAfter, _ := base64.StdEncoding.DecodeString(sign)
    uid := base64.StdEncoding.EncodeToString([]byte("qinnong"))
    signOpt := sm2p256.SignerOpts{Uid: []byte(uid)}
    resultBool := pub.Verify([]byte(encrypted), signAfter, signOpt)

想在问下大佬在解密之后两边的中括号应该如何处理?有什么更好的方式作处理?

2周前 评论
deatil 2周前
Harvoc (作者) (楼主) 2周前
deatil 2周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!