万字总结PHP与JavaScript、PHP与PHP 实现开箱即用的AES、RSA和较为安全的自
实操(下方有理论)没有绝对安全的系统
[*]对于前后端通信安全的声明:对于前端,网页代码是暴漏给用户的,用户可以恣意摆布,可以反编译混淆过的代码,也可以调试、研究代码去攻破,因此不能保证百分百的安全,能做的仅仅是让攻击者增加攻击资本。
[*]明文硬编码毛病:如果是硬编码,把key,secret等明文暴漏,写死在代码中,这是很不安全的行为,代码检察即可攻破,(世界是个巨大的草台班子,这种行为并不少)。
[*]调用毛病:即使加密的逻辑代码被加密,秘钥被加密,可是其它模块的代码是明文的(此地无银三百两)(比方decrypt(response_data)),那么攻击者乃至不用调试加密代码,直接调用明文函数就可绕过。
[*]硬破解:开发者再怎么加密或混淆代码,但攻击者就像破案一样,团结Console控制台,一点一点耐心还原实验逻辑。
[*]通信获取:有些秘钥key,secret,是通过接口获取的,但是获取这些配置的过程,也不安全,可以被获取。
[*]对于前后端通信:能做的就是混淆全部代码(html加密混淆方式自行谷歌,有在线的和一些npm插件);不要硬编码;勤更换秘钥key,Secret,最好使用随机较长的。服务端在不影响业务的前提及时作废老数据验证策略。
服务端与客户端(AES对称加密)
[*]留意:
[*]AES有两个非常重要的参数:key和vi,若攻击者知道key与vi,等同于知道了钥匙。
[*]key:加密算法的焦点,用于确保只有持有相同密钥的人才能解密数据,保举随机生成。
[*]vi:一个随机或伪随机的值,用于在加密过程中初始化加密算法的状态,确保使用相同密钥加密的相同明文会产生差别的密文。
[*]PHP加密,JavaScript解密(PHP需要开启openssl扩展)
/**
* @function PHP使用AES 256加密字符串数据
* @param $str string 被加密的字符串
* @param $key string 256位自定义key,是加密算法的核心,用于确保只有持有相同密钥的人才能解密数据,推荐随机生成32个随机字符,确保不可预测
* @param $ivstring 128位初始化向量,是一个随机或伪随机的值,用于在加密过程中初始化加密算法的状态,确保使用相同密钥加密的相同明文会产生不同的密文
* @return string
*/
function aesEncrypt($str, $key, $iv) {
return base64_encode(openssl_encrypt($str, 'AES-256-CBC', $key, true, $iv));
}
$str = '{"code":0,"msg":"success","data":{"lists":{"k1":"v1","k2":"v2"}}}';
$key = 'abcdabcdabcdabcdabcdabcdabcdabcd';
$iv= 'abcdabcdabcdabcd';
echo aesEncrypt($str, $key, $iv);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script>
function decrypt(str, key, iv) {
return CryptoJS.AES.decrypt(str, CryptoJS.enc.Utf8.parse(key), {iv: CryptoJS.enc.Utf8.parse(iv), padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8);
}
console.log(decrypt('naQ3ZnyGetarkvugVeNaroUe9Hrk+KW3sKXSs42QJ6tPER1zcxvS8E2xsCJG0UaJ5WMFhFgLjD6z3wbqPLFgRFIl+BaGqINmdPbm9B+DXMs=', 'abcdabcdabcdabcdabcdabcdabcdabcd', 'abcdabcdabcdabcd'));
</script>
[*]JavaScript加密,PHP解密
/**
* @function PHP使用AES 256解密字符串数据
* @param $str string 被解密的字符串
* @param $key string 256位自定义key,是加密算法的核心,用于确保只有持有相同密钥的人才能解密数据,推荐随机生成32个随机字符,确保不可预测
* @param $ivstring 128位初始化向量,是一个随机或伪随机的值,用于在加密过程中初始化加密算法的状态,确保使用相同密钥加密的相同明文会产生不同的密文
* @return string
*/
function aesDecrypt($str, $key, $iv) {
return base64_decode(base64_encode(openssl_decrypt(base64_decode($str), 'AES-256-CBC', $key, true, $iv)));
}
$str = 'aZlC1imJbcUQblRR0QsMPGF+8Kbja/1fU+tdcTyAUVw=';
$key = 'abcdabcdabcdabcdabcdabcdabcdabcd';
$iv= 'abcdabcdabcdabcd';
echo aesDecrypt($str, $key, $iv);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script>
function encrypt(str, key, iv) {
return CryptoJS.AES.encrypt(str, CryptoJS.enc.Utf8.parse(key), {iv: CryptoJS.enc.Utf8.parse(iv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}).toString();
}
console.log(encrypt('{"name":"张三", "age": 20}', 'abcdabcdabcdabcdabcdabcdabcdabcd', 'abcdabcdabcdabcd'))
</script>
[*]前端针对key和vi的生成与通信题目不完美的办理方案,怎么选是当前业务的能容忍的安全底线,与开发便捷度的衡量。
[*]如果MVC架构:可在后端实时赋值给JavaScript,存入js内存,加载一次页面变一次秘钥。
[*]若求便捷:把代码写死后混淆,避免明文泄漏。大概每次通信都明文传输随机key和和随机vi,团结密文一起传送。
[*]若求安全:服务端写程序更换,或手动更换JavaScript脚本文件中的key和vi值,或根据用户的会话字符串(不要直接存会话中),前后端根据回话字符串使用相同的算法计算出新的key和vi值。
服务端与客户端(较为安全的自界说对称加密算法,避免AES算法的 key和vi在客户端存储题目)
[*]感受一下“Hello”的密文(安全品级绝对比不上主流加密算法)
$2a34912a90c3cf9bb1ec6aa64351f8ef56ae5726697dcefb07f3a73b09e789545f1295726714ed66d3b04e7cf696fc32
[*]PHP端加解密类
<?php
class Crypt {
//声明密文前缀
private static $prefix = '$';
/**
* @function 生成ASCII字符随机盐
* @return array
*/
public static function generateRandStr() {
//这里最小设置为1,最大设置为255,大小与密文长度成正比,随机长度用于加强混淆
$length = rand(1, 255);
$salt = '';
for ($i = 0; $i < $length; $i++) {
//按照ASCII,生成随机单字节字符
$salt .= chr(rand(0, 255));
}
return ['salt' => $salt, 'length' => $length];
}
/**
* @function 计算偏移量 加密用
* @param $v int 明文每个字节的ASCII编码值
* @param $s int 自定义算法取余后的值
* @return int
*/
private static function calcOffsetEn($v, $s) {
$r = 255 - $v;
if ($s > $r) {
return $s - $r - 1;
}
return $v + $s;
}
/**
* @function 计算偏移量 解密用
* @param $v int 密文每个字节的ASCII编码值
* @param $s int 自定义算法取余后的值
* @return int
*/
private static function calcOffsetDe($v, $s) {
if ($v >= $s) {
return $v - $s;
}
return 255 - ($s - $v) + 1;
}
/**
* @function 明文和盐混淆成密文
* @param $data string 要被加密的明文
* @param $salt array盐
* @return string 加密后的数据
*/
private static function encryptWithSalt($data, $salt) {
$result = '';
//获取明文有几个单字节的长度,此处不要用mb_strlen
$data_len = strlen($data);
for ($i = 0; $i < $data_len; $i ++) {
//逐个字节混淆 字符串中每个字节的ASCII值 与
$result .= chr(static::calcOffsetEn(ord($data[$i]), ord($salt['salt'][$i % $salt['length']])));
}
return $result;
}
/**
* @function 密文解密
* @param $data string 要被解密的密文
* @param $salt array 盐
* @return string
*/
private static function decryptWithSalt($data, $salt) {
$result = '';
$data_len = strlen($data);
for ($i = 0; $i < $data_len; $i++) {
// 去盐处理并转换为字符
$result .= chr(static::calcOffsetDe(ord($data[$i]), ord($salt['salt'][$i % $salt['length']])));
}
return $result;
}
/**
* @function 加密数据
* @param $data string 要被加密的字符串
* @return string
*/
public static function encrypt($data) {
if ($data == '') {
return '';
}
// 生成随机盐
$salt = static::generateRandStr();
// 将盐的长度、盐本身、密文
return static::$prefix . bin2hex(chr($salt['length']) . $salt['salt'] . static::encryptWithSalt(mb_convert_encoding($data, 'UTF-8', 'UTF-8'), $salt));
}
/**
* @function 解密数据
* @param $data string 要被加密的字符串
* @return string
*/
public static function decrypt($data) {
if ($data == '') {
return '';
}
// 检查是否是加密字符串,通过标签判断
if ($data != static::$prefix) {
return '';
}
// 去掉标签并从16进制字符串转换回二进制数据
$clear_data = hex2bin(substr($data, strlen(static::$prefix)));
// 获取盐的长度
$salt_len = ord($clear_data);
// 将解密数据转换为UTF-8编码的字符串
return mb_convert_encoding(static::decryptWithSalt(substr($clear_data, 1 + $salt_len), ['salt' => substr($clear_data, 1, $salt_len), 'length' => $salt_len]), 'UTF-8', 'UTF-8');
}
}
[*]JavaScript端加解密类
class Crypt {
// 声明密文前缀
static prefix = '$';
/**
* 生成ASCII字符随机盐
* @returns {Object} {salt, length}
*/
static generateRandStr() {
const length = Math.floor(Math.random() * 255) + 1;// 随机盐的长度在1到255之间
let salt = '';
for (let i = 0; i < length; i++) {
salt += String.fromCharCode(Math.floor(Math.random() * 256));// 生成一个随机的ASCII字符
}
return { salt, length };
}
/**
* 计算加密时的偏移量
* @param {number} v 明文字符的ASCII值
* @param {number} s 盐的ASCII值
* @returns {number}
*/
static calcOffsetEn(v, s) {
const r = 255 - v;
if (s > r) {
return s - r - 1;
}
return v + s;
}
/**
* 计算解密时的偏移量
* @param {number} v 密文字符的ASCII值
* @param {number} s 盐的ASCII值
* @returns {number}
*/
static calcOffsetDe(v, s) {
if (v >= s) {
return v - s;
}
return 255 - (s - v) + 1;
}
/**
* 明文与盐混淆加密
* @param {Uint8Array} data 明文的字节数据
* @param {Object} salt 盐
* @returns {Uint8Array} 加密后的密文
*/
static encryptWithSalt(data, salt) {
let result = [];
const data_len = data.length;
for (let i = 0; i < data_len; i++) {
const data_char_code = data;
const salt_char_code = salt.salt.charCodeAt(i % salt.length);
result.push(Crypt.calcOffsetEn(data_char_code, salt_char_code));
}
return new Uint8Array(result);
}
/**
* 密文解密
* @param {Uint8Array} data 密文的字节数据
* @param {Object} salt 盐
* @returns {Uint8Array} 解密后的明文
*/
static decryptWithSalt(data, salt) {
let result = [];
const data_len = data.length;
for (let i = 0; i < data_len; i++) {
const data_char_code = data;
const salt_char_code = salt.salt.charCodeAt(i % salt.length);
result.push(Crypt.calcOffsetDe(data_char_code, salt_char_code));
}
return new Uint8Array(result);
}
/**
* 将字符串转换为 UTF-8 字节数组
* @param {string} str
* @returns {Uint8Array}
*/
static stringToBytes(str) {
const encoder = new TextEncoder();
return encoder.encode(str);
}
/**
* 将 UTF-8 字节数组转换为字符串
* @param {Uint8Array} bytes
* @returns {string}
*/
static bytesToString(bytes) {
const decoder = new TextDecoder('utf-8');
return decoder.decode(bytes);
}
/**
* 加密数据
* @param {string} data 明文
* @returns {string} 加密后的字符串
*/
static encrypt(data) {
if (data === '') return '';
// 生成随机盐
const salt = Crypt.generateRandStr();
// 将数据转换为字节数组
const byte_data = Crypt.stringToBytes(data);
// 加密数据
const encrypted_data = Crypt.encryptWithSalt(byte_data, salt);
// 生成盐长度字符并将其与盐和密文一起编码为十六进制字符串
const salt_length = String.fromCharCode(salt.length);
// 转换为 UTF-8 字节数组并进行十六进制编码
const all_data = new Uint8Array();
return Crypt.prefix + Crypt.toHex(all_data);
}
/**
* 解密数据
* @param {string} data 加密后的字符串
* @returns {string} 解密后的明文
*/
static decrypt(data) {
if (data === '') return '';
// 检查加密格式
if (data !== Crypt.prefix) return '';
// 去掉前缀并解码16进制数据
const clear_data = Crypt.fromHex(data.slice(1));
// 获取盐的长度
const salt_length = clear_data;
// 解密数据并返回结果
const salt = {
salt: String.fromCharCode(...clear_data.slice(1, salt_length + 1)),
length: salt_length
};
const encrypted_data = clear_data.slice(salt_length + 1);
const decrypted_bytes = Crypt.decryptWithSalt(encrypted_data, salt);
return Crypt.bytesToString(decrypted_bytes);
}
/**
* 将字节数组转换为十六进制字符串
* @param {Uint8Array} bytes
* @returns {string}
*/
static toHex(bytes) {
let hex = '';
for (let i = 0; i < bytes.length; i++) {
hex += bytes.toString(16).padStart(2, '0');
}
return hex;
}
/**
* 将十六进制字符串转换为字节数组
* @param {string} hex
* @returns {Uint8Array}
*/
static fromHex(hex) {
let bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return new Uint8Array(bytes);
}
}
[*]PHP加密JavaScript解密(测试包含中文、英文、数字、心情)
echo Crypt::encrypt(json_encode([
'code' => 0,
'msg'=> 'success',
'data' => [
'name' => '张三',
'en_name' => 'SanZhang',
'age' => 20,
'mood_emoji' => '
页:
[1]