You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Sm2EncryptCommand extends Command
{
/**
* The name and signature of the console command.
*
* 用法php artisan sm2:encrypt "明文" "公钥hex(可含04前缀)" --mode=c1c3c2
*/
protected $signature = 'sm2:encrypt';
/**
* The console command description.
*/
protected $description = '使用 SM2 公钥加密硬编码明文与公钥返回hex密文';
public function handle(): int
{
// 硬编码待加密明文与公钥hex含04前缀
$message = '{"title": "","district": "","xmlxS":"足球"}';
$publicKey = 'MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEkecuYCCoPprFvmgZoXTtEXcpYyvJi9rgizj+FMqVIL0OSQ24plc4F8ONugktvjPRbwvRly12ieRK9SGiG+9sjA==';
$mode = 1; // C1C3C2
try {
// 若提供的是 SPKI Base64 公钥转换为未压缩点HEX(04+x+y)
$pubHex = $this->normalizePublicKeyToHex($publicKey);
// RtSm2 缺省返回hex且加密返回 c1c3c2
$sm2 = new \Rtgm\sm\RtSm2('hex', true);
$cipherHex = $sm2->doEncrypt($message, $pubHex, $mode);
$this->line($cipherHex);
return self::SUCCESS;
} catch (\Throwable $e) {
$this->error('SM2 加密失败:' . $e->getMessage());
return self::FAILURE;
}
}
private function normalizePublicKeyToHex(string $key): string
{
$trimmed = trim($key);
// 如果已经是 128/130 长度的HEX直接返回
if (preg_match('/^(04)?[0-9a-fA-F]{128}$/', $trimmed)) {
return strtolower($trimmed);
}
// 可能是 Base64(SPKI SubjectPublicKeyInfo)
$bin = base64_decode($trimmed, true);
if ($bin === false) {
throw new \InvalidArgumentException('公钥格式不支持既不是HEX也不是Base64');
}
// 优先使用 OpenSSL 解析 SPKI获取未压缩公钥点
if (function_exists('openssl_pkey_get_public')) {
$pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($trimmed, 64, "\n") . "-----END PUBLIC KEY-----\n";
$res = @openssl_pkey_get_public($pem);
if ($res !== false) {
$details = openssl_pkey_get_details($res);
if ($details && isset($details['ec']) && isset($details['ec']['public_key'])) {
$pubBin = $details['ec']['public_key']; // binary starting with 0x04
if ($pubBin !== '' && ord($pubBin[0]) === 0x04) {
return strtolower(bin2hex($pubBin));
}
}
// 某些 PHP 版本可能在 key 索引中提供公钥
if ($details && isset($details['key'])) {
// 再次从 details['key']PEM中提取 BIT STRING
$pem2 = $details['key'];
$clean = str_replace(["-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----", "\r", "\n", " "], '', $pem2);
$bin2 = base64_decode($clean, true);
if ($bin2 !== false) {
$found = $this->extractUncompressedPointFromSpki($bin2);
if ($found !== null) {
return $found;
}
}
}
}
}
// 退而求其次:直接从 SPKI 二进制中提取 BIT STRING不下钻根 SEQUENCE直接扫描 0x03 标签)
$found = $this->extractUncompressedPointFromSpki($bin);
if ($found !== null) {
return $found;
}
throw new \RuntimeException('未找到公钥BIT STRING');
}
private function extractUncompressedPointFromSpki(string $spkiBin): ?string
{
$len = strlen($spkiBin);
for ($pos = 0; $pos < $len; $pos++) {
if (ord($spkiBin[$pos]) !== 0x03) {
continue;
}
$p = $pos + 1;
if ($p >= $len)
break;
$lenByte = ord($spkiBin[$p++]);
if (($lenByte & 0x80) === 0) {
$l = $lenByte;
} else {
$num = $lenByte & 0x7F;
if ($num === 0 || $p + $num > $len) {
continue;
}
$l = 0;
for ($i = 0; $i < $num; $i++) {
$l = ($l << 8) | ord($spkiBin[$p++]);
}
}
if ($p + $l > $len) {
continue;
}
if ($l < 2) {
continue;
}
$unusedBits = ord($spkiBin[$p]);
if ($unusedBits !== 0) {
continue;
}
$bitString = substr($spkiBin, $p + 1, $l - 1);
if ($bitString !== '' && ord($bitString[0]) === 0x04) {
return strtolower(bin2hex($bitString));
}
}
return null;
}
}