up. 双向数据加密

This commit is contained in:
u2nyakim 2025-08-29 16:09:30 +08:00
parent c8a073a46b
commit 99e5d3848e
5 changed files with 102 additions and 58 deletions

View File

@ -136,10 +136,17 @@ abstract class BaseController
'list' => $data->items()
];
}
return json(array(
$result = array(
"code" => $code,
"data" => $data,
"message" => $message,
));
);
$resultEncrypted = [
'encryptedData' => \app\Request::encryptCryptoJSAES($result)
];
return json($resultEncrypted);
}
}

View File

@ -5,6 +5,7 @@ namespace app;
// 应用请求对象类
use app\http\HttpAuth;
use app\http\HttpClient;
use think\exception\ValidateException;
use think\helper\Macroable;
/**
@ -56,4 +57,91 @@ class Request extends \think\Request
return get_agent_os($this->server('HTTP_USER_AGENT'));
}
const string DATA_SECRET_KEY = "mySecretKey123!";
/**
* 加密数据
*/
static function encryptCryptoJSAES($data): string
{
// 如果是数组或对象,转换为 JSON 字符串
if (is_array($data) || is_object($data)) {
$data = json_encode($data);
}
// 生成随机 salt (8字节)
$salt = openssl_random_pseudo_bytes(8);
// 通过 EVP_BytesToKey 派生密钥和 IV
$keyIv = self::evpBytesToKey($salt);
$key = $keyIv['key'];
$iv = $keyIv['iv'];
// 添加 PKCS#7 填充
$blockSize = 16; // AES 块大小
$padding = $blockSize - (strlen($data) % $blockSize);
$data .= str_repeat(chr($padding), $padding);
// AES-256-CBC 加密
$encrypted = openssl_encrypt(
$data,
'aes-256-cbc',
$key,
OPENSSL_RAW_DATA,
$iv
);
// 组合 Salted__ + salt + 加密数据
$result = "Salted__" . $salt . $encrypted;
// Base64 编码
return base64_encode($result);
}
/**
* 解密 CryptoJS 加密的数据
*/
static function decryptCryptoJSData(string $encryptedData): string
{
// Base64解码
$data = base64_decode($encryptedData);
// 检查Salted__头
if (!str_starts_with($data, "Salted__")) {
throw new ValidateException('Invalid format: missing "Salted__" header');
}
// 提取Salt (8字节)
$salt = substr($data, 8, 8);
$ct = substr($data, 16);
// 通过EVP_BytesToKey派生密钥和IV
$keyIv = self::evpBytesToKey($salt);
$key = $keyIv['key'];
$iv = $keyIv['iv'];
// AES-256-CBC解密
return openssl_decrypt(
$ct,
'aes-256-cbc',
$key,
OPENSSL_RAW_DATA,
$iv
);
}
static private function evpBytesToKey($salt): array
{
$password = self::DATA_SECRET_KEY;
$bytes = '';
$last = '';
// 生成48字节32字节key + 16字节IV
while(strlen($bytes) < 48) {
$last = md5($last . $password . $salt, true);
$bytes .= $last;
}
return [
'key' => substr($bytes, 0, 32),
'iv' => substr($bytes, 32, 16)
];
}
}

View File

@ -8,7 +8,7 @@ use think\{exception\ValidateException, file\UploadedFile, middleware, Request,
class ContextMiddleware extends middleware
{
const string DATA_SECRET_KEY = "mySecretKey123!";
public function handle(Request $request, Closure $next): Response
{
@ -24,7 +24,7 @@ class ContextMiddleware extends middleware
$encryptedData = $request->param('encryptedData','');
if($encryptedData) {
try{
$jsonInput = $this->decryptCryptoJSData($encryptedData);
$jsonInput = \app\Request::decryptCryptoJSData($encryptedData);
}catch (\Throwable){
$jsonInput = null;
}
@ -162,55 +162,4 @@ class ContextMiddleware extends middleware
}
}
/**
* 解密 CryptoJS 加密的数据
*
* @param string $encryptedData Base64 编码的加密数据
* @return array 解密后的数据
* @throws ValidateException 解密失败时抛出异常
*/
private function decryptCryptoJSData(string $encryptedData): string
{
// Base64解码
$data = base64_decode($encryptedData);
// 检查Salted__头
if (!str_starts_with($data, "Salted__")) {
throw new ValidateException('Invalid format: missing "Salted__" header');
}
// 提取Salt (8字节)
$salt = substr($data, 8, 8);
$ct = substr($data, 16);
// 通过EVP_BytesToKey派生密钥和IV
$keyIv = $this->evpBytesToKey($salt);
$key = $keyIv['key'];
$iv = $keyIv['iv'];
// AES-256-CBC解密
return openssl_decrypt(
$ct,
'aes-256-cbc',
$key,
OPENSSL_RAW_DATA,
$iv
);
}
private function evpBytesToKey($salt): array
{
$password = self::DATA_SECRET_KEY;
$bytes = '';
$last = '';
// 生成48字节32字节key + 16字节IV
while(strlen($bytes) < 48) {
$last = md5($last . $password . $salt, true);
$bytes .= $last;
}
return [
'key' => substr($bytes, 0, 32),
'iv' => substr($bytes, 32, 16)
];
}
}

View File

@ -9,7 +9,7 @@ export interface ApiResult<T> {
/** 返回数据 */
data?: T;
/** 后台做了数据加密->前端需要做对称解密 */
encrypted?: string;
encryptedData?: string;
}
/**

View File

@ -111,8 +111,8 @@ const service = axios.create({
*/
service.interceptors.response.use(
(res: AxiosResponse<ApiResult<unknown>>) => {
if (res.data?.encrypted) {
res.data = decryptData(res.data.encrypted);
if (res.data?.encryptedData) {
res.data = decryptData(res.data.encryptedData);
}
const errorMessage = responseInterceptor(res);