在看一個17年左右的項目源碼,
碰到了一個這樣的加密類。
<?php
namespace app\common\lib;
/***
* aes 加密 解密類庫
* Class Aes
* @package app\common\lib
*/
class Aes {
private $key = null;
/***
* 密鑰
* Aes constructor.
*/
public function __construct() {
$this->key = '1234567887654321';
}
/**
* 加密
* @param String input 加密的字符串
* @param String key 解密的key
* @return HexString
*/
public function encrypt($input = '') {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = $this->pkcs5_pad($input, $size);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
/**
* 填充方式 pkcs5
* @param String text 原始字符串
* @param String blocksize 加密長度
* @return String
*/
private function pkcs5_pad($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
/**
* 解密
* @param String input 解密的字符串
* @param String key 解密的key
* @return String
*/
public function decrypt($sStr) {
$decrypted= mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$this->key,base64_decode($sStr), MCRYPT_MODE_ECB);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
}
可以看的出,
Aes 這個加密類使用的是mcrypt系列的加密函數。
同時也知道,
mcrypt系列的加密函數從7.1開始已經被廢棄,
所以需要使用openssl系列加密函數替代mcrypt系列的加密函數。
要想轉變爲openssl系列加密函數,
得注意 密鑰(key)、初始向量(iv)和加密模式($raw_output),
這樣就能轉換爲 openssl 加密。
- 關於密鑰
密鑰很容易就能從源代碼類中得到,對應的是 $key 屬性值。
- 關於初始向量
這個是源碼比較頑皮的部分,
哈哈哈哈。
開始被這句話混淆到了 $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
認爲 $iv 值是通過 mcrypt_create_iv
函數生成的,
所以轉換 openssl 代碼也需要通過 openssl 函數生成。
對應代碼:$ivlen = openssl_cipher_iv_length($cipher);$iv = openssl_random_pseudo_bytes($ivlen);
然而本地運行卻發現,
得到的結果根本不一致。
mcrypt 函數加密類多次運行會發現返回的加密後的字符串是一直保持不變的,
而 openssl 函數加密類如果改變 iv 值加密後的字符串是一直變化的。
只能再回頭看看源碼了,
通過 Aes 類不難發現 encrypt 這個方法 其實調用的就是 ECB 加密模式,
而Mcrypt 函數是有對應的 mcrypt_ecb 函數使用 ECB 模式加解密數據,
雖然已經廢棄,
但是不影響 test。
參考函數給出的代碼例子,
得到 encryptString 加密函數:
/// 參考資料:https://www.php.net/manual/zh/function.mcrypt-cbc.php
function encryptString($data) {
// $iv = '';
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = $this->pkcs5_pad($data, $size);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->key, $input, MCRYPT_MODE_ECB, $iv);
return base64_encode($enc);
}
上面的 $iv 的 值無論是生成的還是空的,
得到的加密後的字符串都是一致且保持不變的,
至此,可以斷定 iv 的實際值就是空字符串了。
- 關於加密模式
通過 MCRYPT_MODE_ECB
常量可以得知,
加密模式使用的是 ecb,
在 openssl 加密函數中對應的就是 aes-128-ecb。
現在已經確定了密鑰、向量和加密模式,
那麼對應的 openssl 加密類,
就可以轉換出來了:
<?php
namespace app\common\lib;
/****
* openssl方式加密代替mcrypt
* Class OpenAce
* @package app\common\lib
*/
class OpenAes {
private $cipher = 'aes-128-ecb';
private $key = '1234567887654321';
private $iv = '';
/***
* 加密
* @param $data 需要加密的字符串
* @return string
*/
public function opensslC($data){
return base64_encode(openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA , $this->iv));
}
/***
* 解密
* @param $str 加密後的字符串
* @return string
*/
public function opensslD($str){
return openssl_decrypt(base64_decode($str),$this->cipher,$this->key,OPENSSL_RAW_DATA,$this->iv);
}
}
測試時使用到的測試類,包含對 mcrypt、openssl 的測試。
<?php
namespace app\api\controller;
use app\common\lib\Aes;
use app\common\lib\OpenAes;
use think\Controller;
class Test extends Controller {
///測試 mcrypt 加密
public function encrypt(){
$data = [
'did' => '我在人民廣場吃炸雞',
'version' => 1,
'time' => '1564384941568',
];
// 1 按字段排序
ksort($data);
// 2拼接字符串數據 &
$string_data = http_build_query($data);
// 3通過aes來加密
$string = (new Aes())->encrypt($string_data);
echo "<pre>";
//vBhbc+ylzoSk7Wg1z/VvUvye4tM3XhQJAM9rD40GJLw1+MqLVQ+RGW99fKDBi2cU1LeWzprOo+Pf76DFqQQ/ZSihiw+niikwEu/fOv0FvkbUq3UGfQjZRjQRlfhXAerstFEinQjveRFNnN/ZHYQc8XC+0FQxBirUaFGFBjzEIp0=
var_dump($string);
///did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1
var_dump($string_data);
exit;
}
///測試 mcrypt 解密
public function decrypt(){
$str = 'vBhbc+ylzoSk7Wg1z/VvUvye4tM3XhQJAM9rD40GJLw1+MqLVQ+RGW99fKDBi2cU1LeWzprOo+Pf76DFqQQ/ZSihiw+niikwEu/fOv0FvkbUq3UGfQjZRjQRlfhXAerstFEinQjveRFNnN/ZHYQc8XC+0FQxBirUaFGFBjzEIp0=';
$decrypt_str = (new Aes())->decrypt($str);
echo "<pre>";
///did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1
var_dump($decrypt_str);
///bool(true)
var_dump($decrypt_str === 'did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1');
exit;
}
///測試 openssl 加密
function openC(){
$data = [
'did' => '我在人民廣場吃炸雞',
'version' => 1,
'time' => '1564384941568',
];
// 1 按字段排序
ksort($data);
// 2拼接字符串數據 &
$string_data = http_build_query($data);
// 3通過openssl來加密
$string = (new OpenAes())->opensslC($string_data);
echo "<pre>";
//opensslC:vBhbc+ylzoSk7Wg1z/VvUvye4tM3XhQJAM9rD40GJLw1+MqLVQ+RGW99fKDBi2cU1LeWzprOo+Pf76DFqQQ/ZSihiw+niikwEu/fOv0FvkbUq3UGfQjZRjQRlfhXAerstFEinQjveRFNnN/ZHYQc8XC+0FQxBirUaFGFBjzEIp0=
var_dump($string);
///opensslC:did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1
var_dump($string_data);
exit;
}
///測試 openssl 解密
function openD(){
$str = 'vBhbc+ylzoSk7Wg1z/VvUvye4tM3XhQJAM9rD40GJLw1+MqLVQ+RGW99fKDBi2cU1LeWzprOo+Pf76DFqQQ/ZSihiw+niikwEu/fOv0FvkbUq3UGfQjZRjQRlfhXAerstFEinQjveRFNnN/ZHYQc8XC+0FQxBirUaFGFBjzEIp0=';
$decrypt_str = (new OpenAes())->opensslD($str);
echo "<pre>";
///opensslC:did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1
var_dump($decrypt_str);
///opensslC:bool(true)
$original_str = 'did=%E6%88%91%E5%9C%A8%E4%BA%BA%E6%B0%91%E5%B9%BF%E5%9C%BA%E5%90%83%E7%82%B8%E9%B8%A1&time=1564384941568&version=1';
var_dump($decrypt_str === $original_str);
exit;
}
}
總結:
- 對於 mcrypt 轉換爲 openssl 多注意 密鑰(key)、初始向量(iv)和加密模式($raw_output)即可。