马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
实操(下方有理论)
没有绝对安全的系统
- 对于前后端通信安全的声明:对于前端,网页代码是暴漏给用户的,用户可以恣意摆布,可以反编译混淆过的代码,也可以调试、研究代码去攻破,因此不能保证百分百的安全,能做的仅仅是让攻击者增加攻击资本。
- 明文硬编码毛病:如果是硬编码,把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 $iv string 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>
复制代码
- /**
- * @function PHP使用AES 256解密字符串数据
- * @param $str string 被解密的字符串
- * @param $key string 256位自定义key,是加密算法的核心,用于确保只有持有相同密钥的人才能解密数据,推荐随机生成32个随机字符,确保不可预测
- * @param $iv string 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[0] != static::$prefix) {
- return '';
- }
- // 去掉标签并从16进制字符串转换回二进制数据
- $clear_data = hex2bin(substr($data, strlen(static::$prefix)));
- // 获取盐的长度
- $salt_len = ord($clear_data[0]);
- // 将解密数据转换为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');
- }
- }
复制代码
- 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[i];
- 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[i];
- 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([salt_length.charCodeAt(0), ...salt.salt.split('').map(c => c.charCodeAt(0)), ...encrypted_data]);
- return Crypt.prefix + Crypt.toHex(all_data);
- }
- /**
- * 解密数据
- * @param {string} data 加密后的字符串
- * @returns {string} 解密后的明文
- */
- static decrypt(data) {
- if (data === '') return '';
- // 检查加密格式
- if (data[0] !== Crypt.prefix) return '';
- // 去掉前缀并解码16进制数据
- const clear_data = Crypt.fromHex(data.slice(1));
- // 获取盐的长度
- const salt_length = clear_data[0];
- // 解密数据并返回结果
- 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[i].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解密(测试包含中文、英文、数字、心情)
[code]echo Crypt::encrypt(json_encode([
'code' => 0,
'msg' => 'success',
'data' => [
'name' => '张三',
'en_name' => 'SanZhang',
'age' => 20,
'mood_emoji' => '
|