采坑记录
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.
}