lpilp / phpsm2sm3sm4

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

PEM格式的sm2证书如何转换成HEX #26

Closed honghuzhi closed 2 years ago

honghuzhi commented 2 years ago

有的银行提供的pem格式证书,如何转换成HEX格式呢?sm2加解密好像也没法直接读取pem格式吧

lpilp commented 2 years ago

pem的base64_decode下, 用 asn1的编码方式解析出来就行了, 我这代码没有做这个用pem文件加解密的,只有hex的, 需要的话,你可以查看签名的那个,有从pem取出明文公私钥的方法

honghuzhi commented 2 years ago

base64格式的一串公钥和 hex格式的一串公钥,怎么才能相互转化呢?试了下签名里面的方法,也是出错

HEX格式公钥,是ans1格式的二进制转成 16进制的吗?bin2hex 这个函数转换出来的长度好像也不对

honghuzhi commented 2 years ago
        $base64String = '*****';
        $binaryData = base64_decode($base64String);
        $asnObject = ASNObject::fromBinary($binaryData);
        printObject($asnObject);

    function printObject(ASNObject $object, $depth = 0){
        $treeSymbol = '';
        $depthString = str_repeat('─', $depth);
        if ($depth > 0) {
            $treeSymbol = '├';
        }
        $name = Identifier::getShortName($object->getType());
        echo "{$treeSymbol}{$depthString}{$name} : ";
        echo $object->__toString().PHP_EOL;
        $content = $object->getContent();
        if (is_array($content)) {
            foreach ($object as $child) {
                printObject($child, $depth + 1);
            }
        }
    }

base64格式的sm2公钥转换后的hex公钥为130个字符,但私钥转换完却有242个字符,这是咋回事啊

honghuzhi commented 2 years ago

gmp_init(): Unable to convert variable to GMP - string is not an integer // $encryptData = $c1.$c3.$c2 $adapter = $this->adapter; $generator = $this->generator; $this->cipher = new \Rtgm\smecc\SM2\Cipher(); $c1X = substr($encryptData,0,64); $c1Y = substr($encryptData,strlen($c1X),64); $c1Length = strlen($c1X) + strlen($c1Y); $c3 = substr($encryptData,$c1Length,64); $c2 = substr($encryptData,$c1Length+strlen($c3)); $p1 = new Point( $adapter, $generator->getCurve(), gmp_init($c1X, 16 ), gmp_init( $c1Y, 16 ) ); $this->cipher->initDecipher($p1,$privateKey);

    $arrMsg = Hex2ByteBuf::HexStringToByteArray2($c2);
    $arrMsg = $this->cipher->decryptBlock($arrMsg); 
    $document = hex2bin(Hex2ByteBuf::ByteArrayToHexString($arrMsg));

    $c3_ = strtolower(Hex2ByteBuf::ByteArrayToHexString($this->cipher->Dofinal()));
    if($c3 == $c3_){ //hash签名相同,
        return $document;
lpilp commented 2 years ago

私钥里是包含了公钥信息的,私钥里有 oid, 私钥,公钥, 公钥里只有 :oid, 公钥 ,
我的rtsm2文件 281行有从pem文件里解析出公钥的代码,同理也可以从文件里读出私钥的明文,这个只要操作一次就行,然后记下来 $sigSerializer = new DerSignatureSerializer(); $keyData = file_get_contents( $publickeyFile ); $derSerializer = new DerPublicKeySerializer( $adapter ); $pemSerializer = new PemPublicKeySerializer( $derSerializer ); $key = $pemSerializer->parse( $keyData ); $pubKeyX = $this->decHex( $key->getPoint()->getX() ); $pubKeyY = $this->decHex( $key->getPoint()->getY() );

$publickey = $pubKeyX.$pubKeyY;

honghuzhi commented 2 years ago

$sigSerializer = new DerSignatureSerializer(); $keyData = file_get_contents( $publickeyFile ); $derSerializer = new DerPublicKeySerializer( $adapter ); $pemSerializer = new PemPublicKeySerializer( $derSerializer ); $key = $pemSerializer->parse( $keyData ); $pubKeyX = $this->decHex( $key->getPoint()->getX() ); $pubKeyY = $this->decHex( $key->getPoint()->getY() );

$publickey = $pubKeyX.$pubKeyY;

了解了,最好能封装个方法,方便外部直接调用导出

codingyu commented 6 months ago

示例临时生成的公私钥

protected $publicKey = '3059301306072a8648ce3d020106082a811ccf5501822d03420004ab8e37df98b9dedf35771d01501596144aa06a80c7e90fe13a3933b373da9d0f3275403a294f1bf97d95705a7783cdb648c615410ed6088bafbe0e5a5f33af58';
protected $privateKey = '308193020100301306072a8648ce3d020106082a811ccf5501822d04793077020101042047d278e4fcc62df9d1af450adf9338b684f95467c3eb1f8d2d04d4915d4bc9f5a00a06082a811ccf5501822da14403420004ab8e37df98b9dedf35771d01501596144aa06a80c7e90fe13a3933b373da9d0f3275403a294f1bf97d95705a7783cdb648c615410ed6088bafbe0e5a5f33af58';

    // https://the-x.cn/encodings/Asn1.aspx ASN.1解码工具 分析出来是以下这样
protected $publicKeyDecode = '04ab8e37df98b9dedf35771d01501596144aa06a80c7e90fe13a3933b373da9d0f3275403a294f1bf97d95705a7783cdb648c615410ed6088bafbe0e5a5f33af58';
protected $privateKeyDecode = '47D278E4FCC62DF9D1AF450ADF9338B684F95467C3EB1F8D2D04D4915D4BC9F5';

@honghuzhi @lpilp 您好,求助,最后如何解决的?我的私钥使用 MyAsn1 decode() 结果是 242位:

array:3 [
  0 => "00"
  1 => array:2 [
    0 => "ecPublicKey"
    1 => "sm2"
  ]
  2 => "3077020101042047D278E4FCC62DF9D1AF450ADF9338B684F95467C3EB1F8D2D04D4915D4BC9F5A00A06082A811CCF5501822DA14403420004AB8E37DF98B9DEDF35771D01501596144AA06A80C7E90FE13A3933B373DA9D0F3275403A294F1BF97D95705A7783CDB648C615410ED6088BAFBE0E5A5F33AF58"
]

而且我将私钥转为pem格式,使用 doSignOutKey 签名报错 Invalid data,使用公钥verifySignOutKey验签没问题

最后如何用代码实从原私钥获取到本扩展所能使用的公钥和私钥?

lpilp commented 6 months ago

这个 myasn1 是简化版本的,不能递归全解出来,正常情况是公钥解一次, 私钥要解两次, 这是项目后面加的我写的简要功能, 不完善

array:3 [
  0 => "00"
  1 => array:2 [
    0 => "ecPublicKey"
    1 => "sm2"
  ]
  2 => "3077020101042047D278E4FCC62DF9D1AF450ADF9338B684F95467C3EB1F8D2D04D4915D4BC9F5A00A06082A811CCF5501822DA14403420004AB8E37DF98B9DEDF35771D01501596144AA06A80C7E90FE13A3933B373DA9D0F3275403A294F1BF97D95705A7783CDB648C615410ED6088BAFBE0E5A5F33AF58"
]

将那个 array[2], 再解一次就出来私钥了,或者就直接调用

Array
(
    [0] => 01
    [1] => 47D278E4FCC62DF9D1AF450ADF9338B684F95467C3EB1F8D2D04D4915D4BC9F5
    [2] => Array
        (
        )

    [3] => Array
        (
        )

)
codingyu commented 6 months ago

@lpilp 好的,感谢您的回复