PHP 轉換 SM2 加密資料 ASN1 編碼格式為 C1C3C2 格式資料

deatil發表於2024-01-19

php 的 SM2 加密生成資料和解析資料格式基本為 C1C3C2 格式,JAVA 預設生成的加密資料為 ASN1 格式,PHP 解析可能會有問題,需要轉換下才能使用,所以就寫了一個轉換函式。
下面是轉換函式:

use phpseclib3\File\ASN1;
use phpseclib3\Math\BigInteger;

// sm2 加密資料 ASN1 格式轉換為 c1c3c2 格式
// 需要 composer 安裝 "phpseclib/phpseclib": "^3.0"
function sm2ASN1ToC1C3C2($data)
{
    $der = ASN1::decodeBER(base64_decode($data));

    $x = "";
    $y = "";
    if (isset($der[0]["content"][0]["content"])) {
        $x = $der[0]["content"][0]["content"]->toHex();
    }
    if (isset($der[0]["content"][1]["content"])) {
        $y = $der[0]["content"][1]["content"]->toHex();
    }

    $hash = bin2hex($der[0]["content"][2]["content"] ?? "");
    $ct   = bin2hex($der[0]["content"][3]["content"] ?? "");

    $x = str_pad($x, 64, "0", STR_PAD_LEFT);
    $y = str_pad($y, 64, "0", STR_PAD_LEFT);

    $endata = $x . $y . $hash . $ct;

    return $endata;
}

// sm2 加密資料 c1c3c2 格式轉換為 ASN1 格式
function sm2C1C3C2ToASN1($data)
{
    $data = hex2bin($data);

    $x    = substr($data, 0, 32);
    $y    = substr($data, 32, 32);
    $hash = substr($data, 64, 32);
    $ct   = substr($data, 96);

    $map = [
        'type' => ASN1::TYPE_SEQUENCE,
        'children' => [
            'x'    => ['type' => ASN1::TYPE_INTEGER],
            'y'    => ['type' => ASN1::TYPE_INTEGER],
            'hash' => ['type' => ASN1::TYPE_OCTET_STRING],
            'ct'   => ['type' => ASN1::TYPE_OCTET_STRING]
        ]
    ];
    $derData = [
        'x'    => new BigInteger($x, 256),
        'y'    => new BigInteger($y, 256),
        'hash' => $hash,
        'ct'   => $ct
    ];

    $der = ASN1::encodeDER($derData, $map);

    return base64_encode($der);
}

測試:

use Rtgm\sm\RtSm2;

// 測試資料
$prikey = "6b4bb2cfe6d4e3499f74cfd05b048b380230d6d7837ebbe1a865850054fbafdb";
$data = "MGwCIQDafQBon8ZrC5fRya4oC6yAgONN6PIWN/I4fk/8wwhGIAIgJgJ/vmW0UmEGmzTp4sgPvigyafQXSU5gsfwLJvE1WYwEIM8nvAb2K7xoK/Q/yi7z/7jzq5XwO3/TtDyvluEiZD0yBAP1Ed4=";

$endata = sm2ASN1ToC1C3C2($data);

// 使用包: "lpilp/guomi": "1.0.*",
$sm2 = new RtSm2();
$res = $sm2->doDecrypt($endata, $prikey);
var_dump($res);
// 輸出: 123

// 轉為 ASN1 格式
$asn1data = sm2C1C3C2ToASN1($endata);
var_dump($asn1data);

解析 ASN1 編碼格式和編碼 ASN1 編碼格式用到了第三方包 phpseclib/phpseclib, SM2 解密測試用到的包 lpilp/guomi
其他 PHP 包解密 SM2 加密資料時請注意測試。
go 的 sm2解密可以檢視 sm2 doc

本作品採用《CC 協議》,轉載必須註明作者和本文連結
我們走了很遠的路,終於發現,路已經不止一條

相關文章