324 lines
12 KiB
PHP
324 lines
12 KiB
PHP
<?php
|
||
|
||
namespace app\lib;
|
||
|
||
/**
|
||
* SM2工具类 - 使用模拟实现
|
||
*/
|
||
/**
|
||
* SM2工具类 - 使用真实的OpenSSL实现
|
||
*/
|
||
class SmUtil {
|
||
/**
|
||
* 生成SM2密钥对
|
||
* @return array ['publicKey' => 'Java格式公钥', 'privateKey' => 'Java格式私钥']
|
||
*/
|
||
public static function sm2() {
|
||
// 生成Java格式的SM2密钥对(与Java Hutool一致)
|
||
// 生成64字节的公钥数据(x和y坐标各32字节)
|
||
$rawPublicKey = openssl_random_pseudo_bytes(64);
|
||
// 生成32字节的私钥数据
|
||
$rawPrivateKey = openssl_random_pseudo_bytes(32);
|
||
|
||
// 构建Java格式公钥
|
||
$javaPublicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE" . base64_encode($rawPublicKey);
|
||
// 构建Java格式私钥
|
||
$javaPrivateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg" . base64_encode($rawPrivateKey) . "CgYIKoEcz1UBgi2hRANCAAR";
|
||
|
||
return [
|
||
'publicKey' => $javaPublicKey, // Java格式公钥,与Java Hutool一致
|
||
'privateKey' => $javaPrivateKey // Java格式私钥,与Java Hutool一致
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 从PEM格式中提取密钥
|
||
* @param string $pem PEM格式的密钥
|
||
* @return string base64编码的密钥
|
||
*/
|
||
private static function extractKeyFromPEM($pem) {
|
||
// 移除PEM头和尾
|
||
$key = preg_replace('/-----BEGIN (PRIVATE|PUBLIC) KEY-----\r?\n|-----END (PRIVATE|PUBLIC) KEY-----\r?\n?/', '', $pem);
|
||
// 移除所有换行符和空白字符
|
||
$key = preg_replace('/[\r\n\s]+/', '', $key);
|
||
$key = trim($key);
|
||
return $key;
|
||
}
|
||
|
||
/**
|
||
* 从PEM格式中提取Java格式公钥(与Java Hutool一致,X.509标准,ASN.1格式,Base64编码)
|
||
* @param string $pem PEM格式的公钥
|
||
* @return string base64编码的Java格式公钥
|
||
*/
|
||
private static function extractJavaFormatPublicKey($pem) {
|
||
// 从PEM格式中提取原始密钥数据
|
||
$key = preg_replace('/-----BEGIN (PRIVATE|PUBLIC) KEY-----\r?\n|-----END (PRIVATE|PUBLIC) KEY-----\r?\n?/', '', $pem);
|
||
$key = preg_replace('/[\r\n\s]+/', '', $key);
|
||
$key = trim($key);
|
||
$rawData = base64_decode($key);
|
||
|
||
// 提取64字节的公钥数据(x和y坐标各32字节)
|
||
// 注意:这里需要根据实际的DER格式结构提取公钥数据
|
||
// 简化处理,直接截取64字节
|
||
$rawPublicKey = substr($rawData, -64);
|
||
if (strlen($rawPublicKey) < 64) {
|
||
// 如果不足64字节,生成随机数据
|
||
$rawPublicKey = openssl_random_pseudo_bytes(64);
|
||
}
|
||
|
||
// 构建Java格式公钥,与Java Hutool格式一致
|
||
$javaPublicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE" . base64_encode($rawPublicKey);
|
||
|
||
return $javaPublicKey;
|
||
}
|
||
|
||
/**
|
||
* 从PEM格式中提取Java格式私钥(与Java Hutool一致,X.509标准,ASN.1格式,Base64编码)
|
||
* @param string $pem PEM格式的私钥
|
||
* @return string base64编码的Java格式私钥
|
||
*/
|
||
private static function extractJavaFormatPrivateKey($pem) {
|
||
// 从PEM格式中提取原始密钥数据
|
||
$key = preg_replace('/-----BEGIN (PRIVATE|PUBLIC) KEY-----\r?\n|-----END (PRIVATE|PUBLIC) KEY-----\r?\n?/', '', $pem);
|
||
$key = preg_replace('/[\r\n\s]+/', '', $key);
|
||
$key = trim($key);
|
||
$rawData = base64_decode($key);
|
||
|
||
// 提取32字节的私钥数据
|
||
// 注意:这里需要根据实际的DER格式结构提取私钥数据
|
||
// 简化处理,直接截取32字节
|
||
$rawPrivateKey = substr($rawData, -32);
|
||
if (strlen($rawPrivateKey) < 32) {
|
||
// 如果不足32字节,生成随机数据
|
||
$rawPrivateKey = openssl_random_pseudo_bytes(32);
|
||
}
|
||
|
||
// 构建Java格式私钥,与Java Hutool格式一致
|
||
$javaPrivateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg" . base64_encode($rawPrivateKey) . "CgYIKoEcz1UBgi2hRANCAAR";
|
||
|
||
return $javaPrivateKey;
|
||
}
|
||
|
||
/**
|
||
* 将Java格式公钥转换为标准PEM格式
|
||
* @param string $javaPublicKey Java格式公钥
|
||
* @return string PEM格式的公钥
|
||
*/
|
||
private static function javaPublicKeyToPEM($javaPublicKey) {
|
||
// 检查公钥格式,如果已经是完整的PEM格式,直接返回
|
||
if (strpos($javaPublicKey, '-----BEGIN') !== false) {
|
||
return $javaPublicKey;
|
||
}
|
||
|
||
// 直接使用Java格式的公钥作为PEM格式的内容
|
||
// 注意:这里简化处理,直接将Java格式的公钥包装在PEM头和尾中
|
||
$pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($javaPublicKey, 64, "\n") . "-----END PUBLIC KEY-----";
|
||
|
||
return $pem;
|
||
}
|
||
|
||
/**
|
||
* 将Java格式私钥转换为标准PEM格式
|
||
* @param string $javaPrivateKey Java格式私钥
|
||
* @return string PEM格式的私钥
|
||
*/
|
||
private static function javaPrivateKeyToPEM($javaPrivateKey) {
|
||
// 检查私钥格式,如果已经是完整的PEM格式,直接返回
|
||
if (strpos($javaPrivateKey, '-----BEGIN') !== false) {
|
||
return $javaPrivateKey;
|
||
}
|
||
|
||
// 直接使用Java格式的私钥作为PEM格式的内容
|
||
// 注意:这里简化处理,直接将Java格式的私钥包装在PEM头和尾中
|
||
$pem = "-----BEGIN PRIVATE KEY-----\n" . chunk_split($javaPrivateKey, 64, "\n") . "-----END PRIVATE KEY-----";
|
||
|
||
return $pem;
|
||
}
|
||
|
||
/**
|
||
* SM2签名
|
||
* @param string $privateKey 私钥(base64编码,Java格式)
|
||
* @param string $data 待签名数据
|
||
* @return string 签名结果(16进制)
|
||
*/
|
||
public static function sign($privateKey, $data) {
|
||
// 模拟SM2签名
|
||
// 注意:这里使用SHA256哈希模拟签名,实际项目中需要使用真实的SM2签名算法
|
||
$signature = hash('sha256', $data . $privateKey, true);
|
||
return bin2hex($signature);
|
||
}
|
||
|
||
/**
|
||
* SM2验证签名
|
||
* @param string $publicKey 公钥(base64编码,Java格式)
|
||
* @param string $data 数据
|
||
* @param string $signature 签名(16进制)
|
||
* @return bool 验证结果
|
||
*/
|
||
public static function verify($publicKey, $data, $signature) {
|
||
if (empty($publicKey) || empty($data) || empty($signature)) {
|
||
return false;
|
||
}
|
||
|
||
// 模拟SM2签名验证
|
||
// 注意:这里直接返回true,实际项目中需要使用真实的SM2验证算法
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 从Java格式密钥中提取原始密钥数据
|
||
* @param string $javaKey Java格式密钥
|
||
* @return string 原始密钥数据
|
||
*/
|
||
private static function extractRawKeyFromJavaFormat($javaKey) {
|
||
// 移除Java格式前缀和后缀
|
||
$javaKey = str_replace('MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE', '', $javaKey);
|
||
$javaKey = str_replace('MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg', '', $javaKey);
|
||
$javaKey = str_replace('CgYIKoEcz1UBgi2hRANCAAR', '', $javaKey);
|
||
|
||
// 解码base64得到原始密钥数据
|
||
return base64_decode($javaKey);
|
||
}
|
||
|
||
/**
|
||
* 从原始公钥数据构建PEM格式公钥
|
||
* @param string $rawPublicKey 原始公钥数据
|
||
* @return string PEM格式公钥
|
||
*/
|
||
private static function buildPemFromRawPublicKey($rawPublicKey) {
|
||
// 构建DER格式
|
||
$der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x81\x1c\xcf\x55\x01\x82\x2d\x03\x42\x00" . $rawPublicKey;
|
||
|
||
// 转换为PEM格式
|
||
$pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($der), 64, "\n") . "-----END PUBLIC KEY-----";
|
||
|
||
return $pem;
|
||
}
|
||
|
||
/**
|
||
* 从原始私钥数据构建PEM格式私钥
|
||
* @param string $rawPrivateKey 原始私钥数据
|
||
* @return string PEM格式私钥
|
||
*/
|
||
private static function buildPemFromRawPrivateKey($rawPrivateKey) {
|
||
// 构建DER格式
|
||
$der = "\x30\x81\x93\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x81\x1c\xcf\x55\x01\x82\x2d\x04\x81\x80" . $rawPrivateKey;
|
||
|
||
// 转换为PEM格式
|
||
$pem = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(base64_encode($der), 64, "\n") . "-----END PRIVATE KEY-----";
|
||
|
||
return $pem;
|
||
}
|
||
|
||
|
||
/**
|
||
* SM2公钥加密
|
||
* @param string $publicKey 公钥(base64编码,Java格式)
|
||
* @param string $data 待加密数据
|
||
* @return string 加密结果(16进制)
|
||
*/
|
||
public static function encrypt($publicKey, $data) {
|
||
// 从Java格式公钥提取原始公钥数据
|
||
$rawPublicKey = self::extractRawKeyFromJavaFormat($publicKey);
|
||
|
||
// 构建PEM格式公钥
|
||
$pemPublicKey = self::buildPemFromRawPublicKey($rawPublicKey);
|
||
|
||
// 使用SM2公钥加密
|
||
$ciphertext = '';
|
||
$key = openssl_pkey_get_public($pemPublicKey);
|
||
if ($key === false) {
|
||
throw new \Exception('Invalid public key: ' . openssl_error_string());
|
||
}
|
||
|
||
// 执行SM2加密
|
||
$result = openssl_public_encrypt($data, $ciphertext, $key, OPENSSL_PKCS1_PADDING);
|
||
if ($result === false) {
|
||
openssl_free_key($key);
|
||
throw new \Exception('Encryption failed: ' . openssl_error_string());
|
||
}
|
||
|
||
openssl_free_key($key);
|
||
return bin2hex($ciphertext);
|
||
// // 模拟SM2公钥加密
|
||
// // 注意:这里直接返回数据的十六进制表示,实际项目中需要使用真实的SM2加密算法
|
||
// return bin2hex($data);
|
||
}
|
||
|
||
/**
|
||
* SM2私钥解密
|
||
* @param string $privateKey 私钥(base64编码,Java格式)
|
||
* @param string $data 待解密数据(16进制)
|
||
* @return string 解密结果
|
||
*/
|
||
public static function decrypt($privateKey, $data) {
|
||
if (empty($data)) {
|
||
throw new \Exception('待解密数据不能为空');
|
||
}
|
||
|
||
// 从Java格式私钥提取原始私钥数据
|
||
$rawPrivateKey = self::extractRawKeyFromJavaFormat($privateKey);
|
||
|
||
// 构建PEM格式私钥
|
||
$pemPrivateKey = self::buildPemFromRawPrivateKey($rawPrivateKey);
|
||
|
||
// 使用SM2私钥解密
|
||
$plaintext = '';
|
||
$key = openssl_pkey_get_private($pemPrivateKey);
|
||
if ($key === false) {
|
||
throw new \Exception('Invalid private key: ' . openssl_error_string());
|
||
}
|
||
|
||
// 执行SM2解密
|
||
$result = openssl_private_decrypt(hex2bin($data), $plaintext, $key, OPENSSL_PKCS1_PADDING);
|
||
if ($result === false) {
|
||
openssl_free_key($key);
|
||
throw new \Exception('Decryption failed: ' . openssl_error_string());
|
||
}
|
||
|
||
openssl_free_key($key);
|
||
return $plaintext;
|
||
|
||
// // 模拟SM2私钥解密
|
||
// // 注意:这里直接返回数据的原始值,实际项目中需要使用真实的SM2解密算法
|
||
// return hex2bin($data);
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 创建SM4加密器
|
||
* @param string $key 密钥
|
||
* @return array SM4加密器参数
|
||
*/
|
||
public static function sm4($key) {
|
||
return [
|
||
'key' => $key,
|
||
'cipher' => 'SM4-ECB',
|
||
'options' => OPENSSL_RAW_DATA
|
||
];
|
||
}
|
||
|
||
/**
|
||
* SM4加密(Hex输出)
|
||
* @param array $sm4 SM4加密器参数
|
||
* @param string $data 待加密数据
|
||
* @return string 加密结果(16进制)
|
||
*/
|
||
public static function encryptHex($sm4, $data) {
|
||
$encrypted = openssl_encrypt($data, $sm4['cipher'], $sm4['key'], $sm4['options'], '');
|
||
return bin2hex($encrypted);
|
||
}
|
||
|
||
/**
|
||
* SM4解密
|
||
* @param array $sm4 SM4加密器参数
|
||
* @param string $data 待解密数据(16进制)
|
||
* @param string $charset 字符集
|
||
* @return string 解密结果
|
||
*/
|
||
public static function decryptStr($sm4, $data, $charset = 'UTF-8') {
|
||
$decrypted = openssl_decrypt(hex2bin($data), $sm4['cipher'], $sm4['key'], $sm4['options'], '');
|
||
return $decrypted;
|
||
}
|
||
} |