修改解码代码

This commit is contained in:
Billy 2025-10-20 09:58:32 +08:00
parent 3f452ca979
commit 7df1bbdf19
2 changed files with 134 additions and 32 deletions

View File

@ -137,7 +137,7 @@ class Ccblife extends Common
* @param string $ccbUserId 建行用户ID * @param string $ccbUserId 建行用户ID
* @param string $mobile 手机号 * @param string $mobile 手机号
* @param string $openId OpenID * @param string $openId OpenID
* @param array $params 其他参数 * @param array $params 其他参数(包含 Usr_Name 等建行返回的数据)
* @return array 用户信息 * @return array 用户信息
*/ */
private function processUserLogin($ccbUserId, $mobile, $openId, $params) private function processUserLogin($ccbUserId, $mobile, $openId, $params)
@ -152,41 +152,67 @@ class Ccblife extends Common
$isNew = false; $isNew = false;
Db::name('user')->where('id', $user['id'])->update([ Db::name('user')->where('id', $user['id'])->update([
'prevtime' => $user['logintime'] ?? null, // 记录上次登录时间
'logintime' => time(), 'logintime' => time(),
'loginip' => $this->request->ip(), 'loginip' => $this->request->ip(),
'updatetime' => time() 'updatetime' => time()
]); ]);
// 重新获取更新后的用户信息
$user = Db::name('user')->where('id', $user['id'])->find();
} else { } else {
// 用户不存在,先尝试通过手机号查找 // 用户不存在,先尝试通过手机号查找
if ($mobile) { if (!empty($mobile)) {
$user = Db::name('user')->where('mobile', $mobile)->find(); $user = Db::name('user')->where('mobile', $mobile)->find();
} }
if ($user) { if ($user) {
// 手机号已存在,更新建行用户ID // 手机号已存在,绑定建行用户ID
$isNew = false; $isNew = false;
Db::name('user')->where('id', $user['id'])->update([ Db::name('user')->where('id', $user['id'])->update([
'ccb_user_id' => $ccbUserId, 'ccb_user_id' => $ccbUserId,
'prevtime' => $user['logintime'] ?? null,
'logintime' => time(), 'logintime' => time(),
'loginip' => $this->request->ip(), 'loginip' => $this->request->ip(),
'updatetime' => time() 'updatetime' => time()
]); ]);
// 重新获取更新后的用户信息
$user = Db::name('user')->where('id', $user['id'])->find();
} else { } else {
// 创建新用户 // 创建新用户
$isNew = true; $isNew = true;
// 从建行参数中获取用户名Usr_Name 或 nickname
$userName = $params['Usr_Name'] ?? $params['nickname'] ?? '';
if (empty($userName)) {
$userName = '建行用户' . substr($ccbUserId, -4);
}
// 生成盐值
$salt = \fast\Random::alnum();
$userData = [ $userData = [
'ccb_user_id' => $ccbUserId, 'ccb_user_id' => $ccbUserId,
'username' => 'ccb_' . substr(md5($ccbUserId), 0, 8), 'username' => 'ccb_' . substr(md5($ccbUserId), 0, 10), // 唯一用户名
'nickname' => $params['nickname'] ?? '建行用户' . substr($ccbUserId, -4), 'nickname' => $userName,
'mobile' => $mobile, 'mobile' => $mobile ?: '',
'avatar' => $params['avatar'] ?? '/assets/img/avatar.png', 'avatar' => $params['avatar'] ?? '/assets/img/avatar.png',
'status' => 'normal', 'status' => 'normal',
'salt' => \fast\Random::alnum(), 'salt' => $salt,
'password' => '', // 建行用户无需密码 'password' => md5(md5(\fast\Random::alnum(32)) . $salt), // 随机密码
'group_id' => 0, // 默认用户组
'level' => 0, // 默认等级
'gender' => 0, // 未知性别
'money' => 0.00,
'commission' => 0.00,
'score' => 0,
'successions' => 1,
'maxsuccessions' => 1,
'loginfailure' => 0,
'joinip' => $this->request->ip(), 'joinip' => $this->request->ip(),
'jointime' => time(), 'jointime' => time(),
'logintime' => time(), 'logintime' => time(),
@ -195,27 +221,45 @@ class Ccblife extends Common
'updatetime' => time() 'updatetime' => time()
]; ];
// 设置随机密码 Log::info('创建建行新用户', [
$userData['password'] = md5(md5(\fast\Random::alnum(32)) . $userData['salt']); 'ccb_user_id' => $ccbUserId,
'mobile' => $mobile,
'nickname' => $userName
]);
$userId = Db::name('user')->insertGetId($userData); $userId = Db::name('user')->insertGetId($userData);
if (!$userId) {
throw new \Exception('创建用户失败');
}
$user = Db::name('user')->where('id', $userId)->find(); $user = Db::name('user')->where('id', $userId)->find();
} }
} }
Db::commit(); Db::commit();
Log::info('建行用户登录处理成功', [
'user_id' => $user['id'],
'ccb_user_id' => $ccbUserId,
'is_new' => $isNew
]);
return [ return [
'user_id' => $user['id'], 'user_id' => $user['id'],
'nickname' => $user['nickname'], 'nickname' => $user['nickname'],
'avatar' => $user['avatar'], 'avatar' => $user['avatar'],
'mobile' => $this->maskMobile($user['mobile']), 'mobile' => $user['mobile'],
'is_new' => $isNew, 'is_new' => $isNew,
'ccb_user_id' => $ccbUserId 'ccb_user_id' => $ccbUserId
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
Db::rollback(); Db::rollback();
Log::error('建行用户登录处理失败', [
'error' => $e->getMessage(),
'ccb_user_id' => $ccbUserId,
'trace' => $e->getTraceAsString()
]);
throw $e; throw $e;
} }
} }
@ -283,19 +327,4 @@ class Ccblife extends Common
$this->error('解密失败: ' . $e->getMessage()); $this->error('解密失败: ' . $e->getMessage());
} }
} }
/**
* 手机号脱敏
*
* @param string $mobile 手机号
* @return string 脱敏后的手机号
*/
private function maskMobile($mobile)
{
if (empty($mobile) || strlen($mobile) !== 11) {
return '';
}
return substr($mobile, 0, 3) . '****' . substr($mobile, -4);
}
} }

View File

@ -12,7 +12,14 @@ class CcbUrlDecrypt
{ {
/** /**
* 解密建行URL参数ccbParamSJ * 解密建行URL参数ccbParamSJ
* 流程URLDecode -> RSA解密使用服务方私钥内部会进行BASE64解码 *
* 建行加密流程参考Java demo
* 1. 原文 RSA公钥加密 BASE64编码第一次
* 2. 再把结果转UTF-8字节 BASE64编码第二次
*
* 解密流程:
* 1. URLDecode BASE64解码第一次 BASE64解码第二次
* 2. RSA私钥解密 原文
* *
* @param string $ccbParamSJ 加密的参数字符串可能已经URLDecode * @param string $ccbParamSJ 加密的参数字符串可能已经URLDecode
* @param string $privateKey 服务方私钥BASE64格式或PEM格式 * @param string $privateKey 服务方私钥BASE64格式或PEM格式
@ -22,7 +29,7 @@ class CcbUrlDecrypt
{ {
try { try {
// 调试日志 // 调试日志
trace('开始解密建行参数(RSA方式', 'info'); trace('开始解密建行参数(双重BASE64 + RSA', 'info');
trace('ccbParamSJ 长度: ' . strlen($ccbParamSJ), 'info'); trace('ccbParamSJ 长度: ' . strlen($ccbParamSJ), 'info');
// 验证输入 // 验证输入
@ -34,12 +41,26 @@ class CcbUrlDecrypt
throw new \Exception('privateKey 为空'); throw new \Exception('privateKey 为空');
} }
// URLDecode如果还没有解码 // 步骤1: URLDecode如果还没有解码
$urlDecoded = urldecode($ccbParamSJ); $urlDecoded = urldecode($ccbParamSJ);
trace('URLDecode后长度: ' . strlen($urlDecoded), 'info'); trace('URLDecode后长度: ' . strlen($urlDecoded), 'info');
// RSA解密CcbRSA::decrypt内部会自动进行BASE64解码 // 步骤2: 第一次 BASE64 解码
$decrypted = CcbRSA::decrypt($urlDecoded, $privateKey); $firstDecode = base64_decode($urlDecoded);
if ($firstDecode === false || empty($firstDecode)) {
throw new \Exception('第一次 BASE64 解码失败');
}
trace('第一次BASE64解码成功长度: ' . strlen($firstDecode), 'info');
// 步骤3: 第二次 BASE64 解码建行特殊之处双重BASE64编码
$secondDecode = base64_decode($firstDecode);
if ($secondDecode === false || empty($secondDecode)) {
throw new \Exception('第二次 BASE64 解码失败');
}
trace('第二次BASE64解码成功长度: ' . strlen($secondDecode), 'info');
// 步骤4: RSA 私钥解密(直接处理二进制数据,不再用 CcbRSA::decrypt
$decrypted = self::rsaPrivateDecrypt($secondDecode, $privateKey);
if ($decrypted === false || empty($decrypted)) { if ($decrypted === false || empty($decrypted)) {
throw new \Exception('RSA解密失败'); throw new \Exception('RSA解密失败');
} }
@ -47,7 +68,7 @@ class CcbUrlDecrypt
trace('RSA解密成功长度: ' . strlen($decrypted), 'info'); trace('RSA解密成功长度: ' . strlen($decrypted), 'info');
trace('解密内容: ' . $decrypted, 'info'); trace('解密内容: ' . $decrypted, 'info');
// 解析参数字符串为数组 // 步骤5: 解析参数字符串为数组
parse_str($decrypted, $params); parse_str($decrypted, $params);
if (empty($params)) { if (empty($params)) {
@ -66,6 +87,58 @@ class CcbUrlDecrypt
} }
} }
/**
* RSA 私钥分块解密(不做 BASE64 解码)
*
* @param string $encryptedData 加密的二进制数据
* @param string $privateKey 私钥BASE64或PEM格式
* @return string|false 解密后的数据
*/
private static function rsaPrivateDecrypt($encryptedData, $privateKey)
{
try {
// 格式化私钥为 PEM 格式
$pemPrivateKey = $privateKey;
if (strpos($pemPrivateKey, '-----BEGIN') === false) {
$pemPrivateKey = preg_replace('/\s+/', '', $pemPrivateKey);
$pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
chunk_split($pemPrivateKey, 64, "\n") .
"-----END RSA PRIVATE KEY-----\n";
}
// 加载私钥资源
$priKey = openssl_pkey_get_private($pemPrivateKey);
if (!$priKey) {
throw new \Exception('私钥格式错误: ' . openssl_error_string());
}
// 获取密钥大小
$keyDetails = openssl_pkey_get_details($priKey);
$keySize = $keyDetails['bits'] / 8; // 1024位 = 128字节
// 分块解密
$blocks = str_split($encryptedData, $keySize);
$decrypted = '';
foreach ($blocks 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;
} catch (\Exception $e) {
trace('RSA私钥解密错误: ' . $e->getMessage(), 'error');
return false;
}
}
/** /**
* DES解密 * DES解密
* 使用ECB模式PKCS5Padding填充 * 使用ECB模式PKCS5Padding填充