up. 双向数据加密
This commit is contained in:
parent
c8a073a46b
commit
99e5d3848e
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ export interface ApiResult<T> {
|
||||
/** 返回数据 */
|
||||
data?: T;
|
||||
/** 后台做了数据加密->前端需要做对称解密 */
|
||||
encrypted?: string;
|
||||
encryptedData?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user