mirror of
https://gitee.com/liuxioabin/fengketrade.git
synced 2026-04-17 21:03:17 +08:00
242 lines
7.9 KiB
PHP
242 lines
7.9 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro\library\ccblife;
|
||
|
||
/**
|
||
* 建行生活RSA加密解密类
|
||
* 支持1024位RSA密钥,117字节分段加密,128字节分段解密
|
||
*/
|
||
class CcbRSA
|
||
{
|
||
/**
|
||
* RSA公钥加密
|
||
* 对于长数据使用117字节分段加密(1024位RSA密钥)
|
||
*
|
||
* @param string $data 待加密数据
|
||
* @param string $publicKey BASE64编码的公钥字符串
|
||
* @return string 加密后的BASE64字符串
|
||
* @throws \Exception
|
||
*/
|
||
public static function encrypt($data, $publicKey)
|
||
{
|
||
// 格式化公钥
|
||
$publicKey = self::formatPublicKey($publicKey);
|
||
|
||
// 加载公钥资源
|
||
$pubKey = openssl_pkey_get_public($publicKey);
|
||
if (!$pubKey) {
|
||
throw new \Exception('公钥格式错误: ' . openssl_error_string());
|
||
}
|
||
|
||
// 获取密钥详情
|
||
$keyDetails = openssl_pkey_get_details($pubKey);
|
||
$keySize = $keyDetails['bits'] / 8; // 密钥字节数
|
||
$maxEncryptBlock = $keySize - 11; // RSA_PKCS1_PADDING 模式下最大加密块大小
|
||
|
||
// 将数据分段加密
|
||
$dataBytes = str_split($data, $maxEncryptBlock);
|
||
$encrypted = '';
|
||
|
||
foreach ($dataBytes as $block) {
|
||
$encryptedBlock = '';
|
||
$success = openssl_public_encrypt($block, $encryptedBlock, $pubKey, OPENSSL_PKCS1_PADDING);
|
||
if (!$success) {
|
||
throw new \Exception('RSA加密失败: ' . openssl_error_string());
|
||
}
|
||
$encrypted .= $encryptedBlock;
|
||
}
|
||
|
||
openssl_free_key($pubKey);
|
||
|
||
// 返回BASE64编码的结果
|
||
return base64_encode($encrypted);
|
||
}
|
||
|
||
/**
|
||
* RSA私钥解密
|
||
* 对于长数据使用128字节分段解密(1024位RSA密钥)
|
||
*
|
||
* @param string $encryptedData BASE64编码的加密数据
|
||
* @param string $privateKey BASE64编码的私钥字符串
|
||
* @return string 解密后的原始数据
|
||
* @throws \Exception
|
||
*/
|
||
public static function decrypt($encryptedData, $privateKey)
|
||
{
|
||
// 格式化私钥
|
||
$privateKey = self::formatPrivateKey($privateKey);
|
||
|
||
// 加载私钥资源
|
||
$priKey = openssl_pkey_get_private($privateKey);
|
||
if (!$priKey) {
|
||
throw new \Exception('私钥格式错误: ' . openssl_error_string());
|
||
}
|
||
|
||
// 获取密钥详情
|
||
$keyDetails = openssl_pkey_get_details($priKey);
|
||
$keySize = $keyDetails['bits'] / 8; // 密钥字节数(128字节)
|
||
|
||
// BASE64解码
|
||
$encryptedData = base64_decode($encryptedData);
|
||
|
||
// 将数据分段解密
|
||
$dataBytes = str_split($encryptedData, $keySize);
|
||
$decrypted = '';
|
||
|
||
foreach ($dataBytes as $block) {
|
||
$decryptedBlock = '';
|
||
$success = openssl_private_decrypt($block, $decryptedBlock, $priKey, OPENSSL_PKCS1_PADDING);
|
||
if (!$success) {
|
||
throw new \Exception('RSA解密失败: ' . openssl_error_string());
|
||
}
|
||
$decrypted .= $decryptedBlock;
|
||
}
|
||
|
||
openssl_free_key($priKey);
|
||
|
||
return $decrypted;
|
||
}
|
||
|
||
/**
|
||
* 使用建行公钥加密数据(API请求)
|
||
*
|
||
* @param string $data 待加密数据
|
||
* @param string $ccbPublicKey 建行提供的公钥
|
||
* @return string 加密后的BASE64字符串
|
||
* @throws \Exception
|
||
*/
|
||
public static function encryptForCcb($data, $ccbPublicKey)
|
||
{
|
||
return self::encrypt($data, $ccbPublicKey);
|
||
}
|
||
|
||
/**
|
||
* 使用商户私钥解密数据(API响应)
|
||
*
|
||
* @param string $encryptedData 加密的数据
|
||
* @param string $merchantPrivateKey 商户私钥
|
||
* @return string 解密后的数据
|
||
* @throws \Exception
|
||
*/
|
||
public static function decryptFromCcb($encryptedData, $merchantPrivateKey)
|
||
{
|
||
return self::decrypt($encryptedData, $merchantPrivateKey);
|
||
}
|
||
|
||
/**
|
||
* 格式化公钥字符串
|
||
* 将BASE64字符串格式化为PEM格式
|
||
*
|
||
* @param string $publicKey BASE64格式的公钥
|
||
* @return string PEM格式的公钥
|
||
*/
|
||
private static function formatPublicKey($publicKey)
|
||
{
|
||
// 移除可能存在的空格和换行
|
||
$publicKey = preg_replace('/\s+/', '', $publicKey);
|
||
|
||
// 如果已经是PEM格式,直接返回
|
||
if (strpos($publicKey, '-----BEGIN PUBLIC KEY-----') !== false) {
|
||
return $publicKey;
|
||
}
|
||
|
||
// ✅ 修复: chunk_split()会在末尾添加换行符,需要用rtrim()去除
|
||
// 否则会导致PEM格式中密钥内容和尾部之间有多余空行,OpenSSL解析失败
|
||
$pem = "-----BEGIN PUBLIC KEY-----\n";
|
||
$pem .= rtrim(chunk_split($publicKey, 64, "\n"), "\n") . "\n";
|
||
$pem .= "-----END PUBLIC KEY-----\n";
|
||
|
||
return $pem;
|
||
}
|
||
|
||
/**
|
||
* 格式化私钥字符串
|
||
* 将BASE64字符串格式化为PEM格式
|
||
* 自动识别PKCS#1和PKCS#8格式
|
||
*
|
||
* @param string $privateKey BASE64格式的私钥
|
||
* @return string PEM格式的私钥
|
||
*/
|
||
private static function formatPrivateKey($privateKey)
|
||
{
|
||
// 移除可能存在的空格和换行
|
||
$privateKey = preg_replace('/\s+/', '', $privateKey);
|
||
|
||
// 如果已经是PEM格式,直接返回
|
||
if (strpos($privateKey, '-----BEGIN RSA PRIVATE KEY-----') !== false ||
|
||
strpos($privateKey, '-----BEGIN PRIVATE KEY-----') !== false) {
|
||
return $privateKey;
|
||
}
|
||
|
||
// ✅ 自动识别密钥格式
|
||
// PKCS#8格式特征:以 MIICdwIBADANBgkqhkiG9w0BAQEFAASC 开头(包含ASN.1结构标识)
|
||
// PKCS#1格式特征:以 MIICXAIBAAKBgQC 或类似开头(直接是RSA私钥参数)
|
||
|
||
// 解码BASE64看前几个字节
|
||
$decoded = base64_decode($privateKey);
|
||
$isPkcs8 = false;
|
||
|
||
if ($decoded !== false && strlen($decoded) > 20) {
|
||
// PKCS#8格式的特征:包含OID标识 (0x06 0x09 0x2a 0x86 0x48 0x86 0xf7 0x0d 0x01 0x01 0x01)
|
||
// 简单判断:检查是否包含 "0609" (DER编码的OID标识)
|
||
$hex = bin2hex(substr($decoded, 0, 30));
|
||
if (strpos($hex, '06092a864886f70d010101') !== false) {
|
||
$isPkcs8 = true;
|
||
}
|
||
}
|
||
|
||
// 格式化为PEM
|
||
$keyContent = rtrim(chunk_split($privateKey, 64, "\n"), "\n");
|
||
|
||
if ($isPkcs8) {
|
||
// PKCS#8格式 (建行使用的格式)
|
||
$pem = "-----BEGIN PRIVATE KEY-----\n";
|
||
$pem .= $keyContent . "\n";
|
||
$pem .= "-----END PRIVATE KEY-----\n";
|
||
} else {
|
||
// PKCS#1格式 (传统格式)
|
||
$pem = "-----BEGIN RSA PRIVATE KEY-----\n";
|
||
$pem .= $keyContent . "\n";
|
||
$pem .= "-----END RSA PRIVATE KEY-----\n";
|
||
}
|
||
|
||
return $pem;
|
||
}
|
||
|
||
/**
|
||
* 生成RSA密钥对(用于测试)
|
||
*
|
||
* @param int $bits 密钥长度,默认1024
|
||
* @return array 包含public_key和private_key的数组
|
||
* @throws \Exception
|
||
*/
|
||
public static function generateKeyPair($bits = 1024)
|
||
{
|
||
$config = [
|
||
'private_key_bits' => $bits,
|
||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||
];
|
||
|
||
// 生成密钥对
|
||
$resource = openssl_pkey_new($config);
|
||
if (!$resource) {
|
||
throw new \Exception('生成密钥对失败: ' . openssl_error_string());
|
||
}
|
||
|
||
// 导出私钥
|
||
openssl_pkey_export($resource, $privateKey);
|
||
|
||
// 获取公钥
|
||
$details = openssl_pkey_get_details($resource);
|
||
$publicKey = $details['key'];
|
||
|
||
// 转换为BASE64格式(去除PEM头尾)
|
||
$privateKey = str_replace(['-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----', "\n"], '', $privateKey);
|
||
$publicKey = str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----', "\n"], '', $publicKey);
|
||
|
||
return [
|
||
'public_key' => $publicKey,
|
||
'private_key' => $privateKey
|
||
];
|
||
}
|
||
} |