2023-01-29 10:26:52 +08:00

240 lines
7.4 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Haotong Lin <lofanmi@gmail.com>
// +----------------------------------------------------------------------
namespace org;
use org\transform\driver\Base64;
/**
* ThinkPHP加密模块(AES加密)
*
* 基于Laravel 5.1 Illuminate\Encryption\Encrypter实现, 使用时需打开openssl扩展
*
* 注: 关于key的生成
*
* 当 CIPHER_MODE = AES-128-CBC, IV_SIZE = 16 时,
*
* * key可以由16位随机字符串组成, 如:
* $key = 'gm9?lh=ngV.w86!Q';
*
* * 也可以使用32位十六进制字符串pack而成, 如:
* $key = pack('H*', 'd40e1a08a07fb2e21abe2abc5910f533');
*
* 当使用AES-256-CBC时, 密钥长度和初始化向量大小为32!
*
* 测试一下~
* // 明文
* $data = 'ThinkPHP框架 | 中文最佳实践PHP开源框架,专注WEB应用快速开发8年';
* // 密钥
* $key = 'gm9?lh=ngV.w86!Q';
* // $key = pack('H*', 'd40e1a08a07fb2e21abe2abc5910f533');
* // 1秒后失效
* $expire = 1;
* // 加密结果
* var_dump($test = Crypt::encrypt($data, $key, $expire));
* // 解密结果
* var_dump($test = Crypt::decrypt($test, $key));
* // true
* var_dump($data === $test);
* // 等一下下~
* sleep(0.1 + $expire);
* // false
* var_dump(Crypt::decrypt($test, $key));
*/
class Crypt
{
/**
* 校验码长度
* sha256的长度为32个字节, 这里只取前4个字节
*/
const HMAC_SIZE = 4;
/**
* 初始化向量大小
* AES-128-CBC 长度为16
* AES-256-CBC 长度为32
*/
const IV_SIZE = 16;
/**
* 加密模式
* AES-128-CBC
* AES-256-CBC
*/
const CIPHER_MODE = 'AES-128-CBC';
/**
* 密码有效期长度(4个字节)
*
*/
const EXPIRE_SIZE = 4;
/**
* 加密字符串
*
* @param mixed $value 待加密的数据(数字, 字符串, 数组或对象等)
* @param string $key 加密密钥
* @param int $expire 加密有效期(几秒后加密失效)
* @param string $target 编码目标
*
* @return string
*/
public static function encrypt($value, $key, $expire = 0, $target = 'url')
{
// 随机生成初始化向量, 增加密文随机性
$iv = static::createIV(self::IV_SIZE);
// 序列化待加密的数据(支持数组或对象的加密)
$value = static::packing($value);
// 加密数据
$value = openssl_encrypt($value, self::CIPHER_MODE, $key, OPENSSL_RAW_DATA, $iv);
if (false === $value) {
return false;
}
// 加密有效期
$expire = $expire ? dechex(time() + $expire) : 0;
$expire = sprintf('%08s', $expire);
// 生成密文校验码
$hmac = static::hmac($iv, $value, $key);
// 组合加密结果并base64编码
$base = new Base64();
return $base->encode(pack('H*', $hmac . $expire) . $iv . $value, $target);
}
/**
* 解密字符串
*
* @param string $value 待加密的数据(数字, 字符串, 数组或对象等)
* @param string $key 解密密钥
* @param string $target 解码目标
*
* @return string
*/
public static function decrypt($value, $key, $target = 'url')
{
// Base64解码
$base = new Base64();
$value = $base->decode($value, $target);
// 拆分加密结果(校验码, 有效期, 初始化向量, 加密数据)
$hmac = substr($value, 0, self::HMAC_SIZE);
$expire = substr($value, self::HMAC_SIZE, self::EXPIRE_SIZE);
$iv = substr($value, self::HMAC_SIZE + self::EXPIRE_SIZE, self::IV_SIZE);
$value = substr($value, self::HMAC_SIZE + self::EXPIRE_SIZE + self::IV_SIZE);
// 超出有效期
if (time() > hexdec(bin2hex($expire))) {
return false;
}
// 验证密文是否被篡改
if (static::compareString(static::hmac($iv, $value, $key), bin2hex($hmac)) === false) {
return false;
}
// 解密数据
$value = openssl_decrypt($value, self::CIPHER_MODE, $key, OPENSSL_RAW_DATA, $iv);
if (false === $value) {
return false;
}
// 反序列化
$value = static::unpacking($value);
// 返回解密结果
return $value;
}
/**
* 随机生成指定长度的初始化向量
*
* @param int $size 初始化向量长度
*
* @return string
*/
protected static function createIV($size)
{
if (function_exists('openssl_random_pseudo_bytes')) {
$bytes = openssl_random_pseudo_bytes($size, $strong);
}
if (is_null($bytes) || false === $bytes || false === $strong) {
$size *= 2;
$pool = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$bytes = pack('H*', substr(str_shuffle(str_repeat($pool, $size)), 0, $size));
}
return $bytes;
}
/**
* 生成指定长度的加密校验码, 保证密文安全
*
* @param string $iv 初始化向量
* @param string $value 加密后的数据
* @param string $key 加密密钥
*
* @return string
*/
protected static function hmac($iv, $value, $key)
{
return substr(hash_hmac('sha256', $iv . $value, $key), 0, self::HMAC_SIZE * 2);
}
/**
* 数据打包(数据如何序列化)
* serialize or json_encode
*
* @param mixed $value 待加密的数据
*
* @return string 返回序列化后的数据
*/
protected static function packing($value)
{
return serialize($value);
}
/**
* 数据解包(数据如何反序列化)
* unserialize or json_decode
*
* @param string $value 被序列化的数据
*
* @return mixed 返回被加密的数据
*/
protected static function unpacking($value)
{
return unserialize($value);
}
/**
* 比较字符串是否相等
*
* @param string $known 参考字符串
* @param string $input 待测试字符串
*
* @return boolean
*/
protected static function compareString($known, $input)
{
// 强制转换为字符串类型
$known = (string) $known;
$input = (string) $input;
if (function_exists('hash_equals')) {
return hash_equals($known, $input);
}
// 字符串长度不相等可直接返回
$length = strlen($known);
if (strlen($input) !== $length) {
return false;
}
// 逐位比较字符串
// 遇到字符不一致, 并不是直接返回, 这样就无法猜解字符串在哪里出错
$result = 0;
for ($i = 0; $i < $length; $i++) {
$result |= (ord($known[$i]) ^ ord($input[$i]));
}
return 0 === $result;
}
}