络腮胡菲菲 发表于 2025-2-15 16:30:00

万字总结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]
查看完整版本: 万字总结PHP与JavaScript、PHP与PHP 实现开箱即用的AES、RSA和较为安全的自