采坑記錄
Base64編碼
所有的數據都能被編碼為只用65個字符就能表示的文本。
標准的Base64每行為76個字符,每行末尾添加一個回車換行符(\r\n)。不論每行是否滿76個字符,都要添加一個回車換行符。
65字符:A~Z a~z 0~9 + / =
URL Base64算法中,為了安全,會把 + 替換成 - ,把 / 替換成 _
= 有時候用 ~ 或 . 代替
Base64的應用:密鑰,密文,圖片
數據簡單加密或者預處理
Base64編碼解碼與btoa、atob
Hex 編碼
二進制數據最常用的一種表示方式。用0-9 a-f 16個字符表示。每個十六進制字符代表4bit。也就是2個十六進制字符代表一個字節。在實際應用中,尤其在密鑰初始化的時候,一定要分清楚自己傳進去的密鑰是哪種方式編碼的,采用對應方式解析,才能得到正確的結果
1:openssl_encrypt中aes-128-cbc、aes-256-cbc中的128、256是與秘鑰位數有關的,16位秘鑰需要使用aes-128-cbc模式。
參考文章:https://www.douban.com/note/628737539/
加密后的字符串如果直接用post form形式提交給php后端,會出現無法解密的情況,經過多次測試,終於找到原因。
AES是基於數據塊的加密方式,也就是說,每次處理的數據是一塊(16字節),當數據不是16字節的倍數時填充,這就是所謂的分組密碼(區別於基於比特位的流密碼),16字節是分組長度。
2:ECB CBC 等的區別
CBC: CBC模式的全稱是Cipher Block Chaining模式(密文分組鏈接模式)
ECB模式只進行了加密,而CBC模式則在加密之前進行了一次XOR
分組加密的幾種方式
ECB:是一種基礎的加密方式,密文被分割成分組長度相等的塊(不足補齊),然后單獨一個個加密,一個個輸出組成密文。
CBC:是一種循環模式,前一個分組的密文和當前分組的明文異或操作后再加密,這樣做的目的是增強破解難度。
CFB/OFB實際上是一種反饋模式,目的也是增強破解的難度。
3: 初始化向量
當加密第一個明文分組時,由於不存在“前一個密文分組”,因此需要事先准備一個長度為一個分組的比特序列來代替“前一個密文分組”,這個比特序列稱為初始化向量(Initialization Vector),通常縮寫為IV,一般來說,每次加密時都會隨機產生一個不同的比特序列來作為初始化向量。
所以:ECB 模式不需要IV
CBC模式的特點
明文分組在加密之前一定會與“前一個密文分組”進行XOR運算,因此即使明文分組1和明文分組2的值是相等的,密文分組1和2的值也不一定是相等的。這樣一來,ECB模式的缺陷在CBC模式中就不存在了。
加密過程:在CBC模式中,無法單獨對一個中間的明文分組進行加密。例如,如果要生成密文分組3,則至少需要湊齊明文分組1、2、3才行。
解密過程:假設CBC模式加密的密文分組中有一個分組損壞了。在這種情況下,只要密文分組的長度沒有發生變化,則解密時最多只有2個分組受到數據損壞的影響。
參考文章:https://www.cnblogs.com/wangle1001986/p/11468419.html
快捷測試
echo abc | openssl aes-128-cbc -k 123 -base64
要點注意:
1 使用何種填充算法。mcrypt自動使用NUL("\0"),openssl自動使用PKCS7
2 是否對數據做了base64編碼處理。mcrypt默認不使用,openssl默認使用
涉及知識講解:
1 AES詳解:
AES是當前最為常用的安全對稱加密算法
AES-128:16位密鑰key
AES-192:24位密鑰key
AES-256:32位密鑰key
即算法統一使用MCRYPT_RIJNDAEL_128,並通過key的位數來選定是以何種AES標准加密,iv固定16位(openssl的AES加密iv始終為16位),mode選用CBC模式
openssl加解密函數中,options參數尤為重要,它是兼容mcrypt算法的關鍵:
options = 0:默認模式,自動對明文進行pkcs7 padding,且數據做base64編碼處理
options = 1:OPENSSL_RAW_DATA,自動對明文進行pkcs7 padding,數據未經base64編碼處理
options = 2:OPENSSL_ZERO_PADDING,要求待加密的數據長度已按"\0"填充與加密算法數據塊長度對齊,即同mcrypt默認填充的方式一致,且對數據做base64編碼處理(此模式下openssl要求待加密數據已按"\0"填充好,其並不會自動幫你填充數據)。
參考文章:
https://www.cnblogs.com/jingxiaoniu/p/12217789.html
項目背景
在開發支付寶小程序時,獲取用戶手機號,需要AES解密得到手機號明文。
官方並沒有提供PHP解密的實例,所以要用PHP實現AES算法的解密過程。
要點
- PHP實現AES解密可以用 mcrypt 類方法,以及 openssl 族的方法。mcrypt 在PHP7.2被棄用,所以推薦使用 openssl 實現。
- screct_key 即 aes_key 是從支付寶小程序管理中心后台獲取的。
實現過程
openssl 實現方式
/**
* openssl 解密
* @param unknown $encryptedData
* @return string
*/
protected static function decryptOpenssl($encryptedData, $screct_key) {
$aesKey = base64_decode($screct_key);
$aesIV = null;
$aesCipher = base64_decode($encryptedData);
$result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV); //1=OPENSSL_RAW_DATA 模式
// $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 2, $aesIV);
return $result;
}
options 參數即為重要,它是兼容 mcrpty 算法的關鍵:
options = 0: 默認模式,自動對明文進行 pkcs7 padding,且數據做 base64 編碼處理。
options = 1: OPENSSL_RAW_DATA,自動對明文進行 pkcs7 padding, 且數據未經 base64 編碼處理。這里的理解很重要
options = 2: OPENSSL_ZERO_PADDING,要求待加密的數據長度已按 "0" 填充與加密算法數據塊長度對齊,即同 mcrpty 默認填充的方式一致,且對數據做 base64 編碼處理。注意,此模式下 openssl 要求待加密數據已按 "0" 填充好,其並不會自動幫你填充數據,如果未填充對齊,則會報錯。
mcrypt 實現方式,可以參考alipay sdk 的aop/AopEnctypt.php 的 decrypt
class AliBizDataCrypt {
* 解密方法
*
* @param string $encryptedData : 需要解密的報文
* @return string
*/
protected static function decrypt($encryptedData, $screct_key) {
// AES, 128 模式加密數據 CBC
$encryptedDataBase64Decoded = base64_decode($encryptedData);
$screct_key = base64_decode($screct_key);
// 設置全0的IV
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = str_repeat("\0", $iv_size);
$decrypt_str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $screct_key, $encryptedDataBase64Decoded, MCRYPT_MODE_CBC, $iv);
$decrypt_str = self::stripPKSC7Padding($decrypt_str);
return $decrypt_str;
}
/**
* 移去填充算法
*
* @param string $source
* @return string
*/
protected static function stripPKSC7Padding($source) {
$char = substr($source, - 1);
$num = ord($char);
if( $num == 62 )
return $source;
$source = substr($source, 0, - $num);
return $source;
}
}
注意,PHP 7.1 之前使用 mcrypt 在PHP 7.1中Mcrypt已經被棄用了
//AES/ECB/PKCS5Padding
//加密
public function encrypt($data, $key) {
$l = strlen($key);
if ($l < 16){
$key = str_repeat($key, ceil(16/$l));
}
$key = base64_decode($key);
$alg = MCRYPT_RIJNDAEL_128; // AES
$mode = MCRYPT_MODE_ECB; // not recommended unless used with OTP
$block_size = mcrypt_get_block_size($alg, $mode);
$data = $this->pkcs5_pad($data, $block_size);
if (function_exists('mcrypt_encrypt')){
$val = mcrypt_encrypt($alg, $key, $data, $mode);
}else{
$val = openssl_encrypt($data, $this->encrypt_method, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
NLog::fl('ERROR', __CLASS__.' ' . __FUNCTION__ . ' 0 mcrypt_not_exists ');
if ($val === false) {
NLog::fl('ERROR', __CLASS__.' ' . __FUNCTION__ . ' 0 openssl_error_string '. openssl_error_string());
}
}
return base64_encode($val);
}
function pkcs5_pad($text, $blocksize){
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
//解密
public function mcrypt_decrypt($data, $key) {
$data = base64_decode($data);
$key = base64_decode($key);
$decrypted= mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$key,$data, MCRYPT_MODE_ECB);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
有用的信息
AES-256 is different from RIJNDAEL-256. The 256 in AES refers to the key size, where the 256 in RIJNDAEL refers to block size. AES-256 is RIJNDAEL-128 when used with a 256 bit key
The security of AES-256 versus AES-128 isn't that significant;
參考:https://stackoverflow.com/questions/6770370/aes-256-encryption-in-php
js AES加密
require('crypto-js');
var plain = '{"id":"21_actid", "unicode":"test00001"}';
var keyStr = 'bXlfdGVzdF8yMV9hZXNfZW5jcnlfa2V5'; //32位
var encryptedBase64 = encrypt(plain, keyStr);
// console.log(encrypted,encryptedBase64);
postman.setGlobalVariable("encryptData",encryptedBase64);
var myFormData = pm.request.body.formdata;
var appId = myFormData.get('appId');
var timestamp = myFormData.get('timestamp');
var signres = appId+timestamp+encryptedBase64;
console.log(signres);
var sign = CryptoJS.MD5(signres).toString();
postman.setGlobalVariable("sign",sign);
var base64 = 'SGVsbG8gd29ybGQ=';
var words = CryptoJS.enc.Base64.parse(base64);
var textString = CryptoJS.enc.Utf8.stringify(words);
// console.log(textString);
//CryptoJS.pad.Pkcs7 NoPadding
//注意事項:
//1、PKCS5Padding的補碼方式,其實就是 PKCS7
function encrypt(str, key){
var words = CryptoJS.enc.Base64.parse(key);
// console.log(words);
// var keyAES = CryptoJS.enc.Utf8.stringify(words);
// var key = CryptoJS.enc.Utf8.parse(key);
var encrypted = CryptoJS.AES.encrypt(str, words, {mode: CryptoJS.mode.ECB, padding:CryptoJS.pad.Pkcs7});
return encrypted.toString();
//You don't need to convert encrypted value to base64, encrypted.toString() returns base64 value.
}