up. 双向数据加密
This commit is contained in:
parent
c8a073a46b
commit
99e5d3848e
@ -136,10 +136,17 @@ abstract class BaseController
|
|||||||
'list' => $data->items()
|
'list' => $data->items()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return json(array(
|
|
||||||
|
$result = array(
|
||||||
"code" => $code,
|
"code" => $code,
|
||||||
"data" => $data,
|
"data" => $data,
|
||||||
"message" => $message,
|
"message" => $message,
|
||||||
));
|
);
|
||||||
|
$resultEncrypted = [
|
||||||
|
'encryptedData' => \app\Request::encryptCryptoJSAES($result)
|
||||||
|
];
|
||||||
|
|
||||||
|
return json($resultEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace app;
|
|||||||
// 应用请求对象类
|
// 应用请求对象类
|
||||||
use app\http\HttpAuth;
|
use app\http\HttpAuth;
|
||||||
use app\http\HttpClient;
|
use app\http\HttpClient;
|
||||||
|
use think\exception\ValidateException;
|
||||||
use think\helper\Macroable;
|
use think\helper\Macroable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,4 +57,91 @@ class Request extends \think\Request
|
|||||||
return get_agent_os($this->server('HTTP_USER_AGENT'));
|
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
|
class ContextMiddleware extends middleware
|
||||||
{
|
{
|
||||||
const string DATA_SECRET_KEY = "mySecretKey123!";
|
|
||||||
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
@ -24,7 +24,7 @@ class ContextMiddleware extends middleware
|
|||||||
$encryptedData = $request->param('encryptedData','');
|
$encryptedData = $request->param('encryptedData','');
|
||||||
if($encryptedData) {
|
if($encryptedData) {
|
||||||
try{
|
try{
|
||||||
$jsonInput = $this->decryptCryptoJSData($encryptedData);
|
$jsonInput = \app\Request::decryptCryptoJSData($encryptedData);
|
||||||
}catch (\Throwable){
|
}catch (\Throwable){
|
||||||
$jsonInput = null;
|
$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;
|
data?: T;
|
||||||
/** 后台做了数据加密->前端需要做对称解密 */
|
/** 后台做了数据加密->前端需要做对称解密 */
|
||||||
encrypted?: string;
|
encryptedData?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -111,8 +111,8 @@ const service = axios.create({
|
|||||||
*/
|
*/
|
||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
(res: AxiosResponse<ApiResult<unknown>>) => {
|
(res: AxiosResponse<ApiResult<unknown>>) => {
|
||||||
if (res.data?.encrypted) {
|
if (res.data?.encryptedData) {
|
||||||
res.data = decryptData(res.data.encrypted);
|
res.data = decryptData(res.data.encryptedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorMessage = responseInterceptor(res);
|
const errorMessage = responseInterceptor(res);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user