PHP 加密:AES & RSA

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP 加密:AES & RSA相关的知识,希望对你有一定的参考价值。

参考技术A

最近两年一直从事与金融相关项目的开发与维护。但是,关于 php 加密解密的最佳实践,网上没有人给出一个完美的总结。恰逢最近看了《图解密码技术》一书,对 PHP 加解密有了更深刻的认识。

为了避免各位看枯燥的文字理论,开篇我就把总结给出:

一、对称加密
对称加密的特点是加解密速度快,加密后的密文强度目前还没有硬解的可能性。但是,在未来随着计算机性能的提升有可能会出现被破解的可能性。

对称加密的缺点也很明显。对称加密的加密过程与解密过程使用的是同一把密钥。一旦泄漏密钥,加密就失去了任何意义。

根据《图解密码技术》一书的推荐,对称加密目前推荐使用 AES。在 PHP 当中要实现 AES 加解密,是使用 openssl 扩展来实现。所以,请确保你的 PHP 已经开启了 openssl 扩展。

可以通过如下方式检测:

或者如下方式检测:

AES 的加密模式属于分组密码模式。所谓分组密码,是加密时把明文按照固定的长度分组,然后再进行加密。当然,细节之处很很多不同。AES 分组模式有多种:ECB、CBC、CFB、OFB、CTR 五种分组模式。目前优先推荐使用 CBC 模式。

如果使用 CBC 模式,那么在加密的时候,就需要一个前置的加密向量 IV。当初博主在使用 AES 来加密的时候,就很奇怪一个对称加密为何要这个向量。因为,在博主寒冰的潜意识里,对称加密只需要一个密钥就 Ok 了。没想到 AES 加密还有多种模式,而这个 CBC 模式恰恰就需要一个这样的向量值。关于这个向量大家可以在网上查阅相关的资料。这个东西非常重要,也非常好理解。

关于 PHP AES 加解密会用到的相关方法:

AES 支持三种强度:128、192、256。128 位的强度最低,但是,加密解密速度较快。256 位强度最高,但是,加密解密速度最低。所以,大家根据自己系统的重要程度选择使用对应强度。通常普通的金融项目使用 192 位完整够用了。顶级的就用 256 位。其他的就用 128 位吧。

二、非对称加密
非对称加密是指公钥加密私钥解密,私钥加密公钥解密的算法。非对称加密的算法有很多。《图解密码技术》一书推荐使用 RSA 算法。它使用起来也非常简单。

要使用 RSA 算法。首先,我们必须生成一对公钥私钥。其实生成公钥私钥很简单。

在 Linux 系统,直接使用如下命令生成:

此命令会生 ~/.ssh/ 目录下生成两个文件:

id_rsa 是私钥, is_rsa.pub 是公钥。

关于 PHP RSA 加解密会用到的相关方法:

以上就是关于在 PHP 项目开发中,我们使用的加密解密算法的一个总结。博主寒冰在总结过程中难免会有不足之处,还请大家指正!谢谢!

php RSA和AES的加密

# RSA和AES的加密
<?php
/**
 * Created by PhpStorm.
 * User: code
 * Date: 2019/4/15
 * Time: 10:55
 */

namespace App\Helper\Classes\ApiCipher;

use App\Helper\Traits\Singleton;

/**
 * RSA(非对称)和AES(对称)加密数据
 *
 * 模拟一个https流程
 *
 * 有分:
 * 1、服务器单向RSA + AES(已实现)
 * 2、双向RSA + AES
 */
abstract class BaseRSAAndAES
{
    use Singleton;

    /**
     * @return mixed|array
     */
    abstract public function getConfig();

    public function getPublicKey()
    {
        return $this->getConfig()['public'] ?? '';
    }

    /**
     * 直接获取解密后的数据
     *
     * @param string $key  被RSA公钥加密过的AES的key,此key经过base64和url的转码
     * @param string $dataString 被加密的数据
     * @return array
     */
    public function getData($key, $dataString = '')
    {
        // 有时候处理xdebug就报错
        $aesKey = $this->getDecrypt(urldecode(base64_decode(urldecode($key)))); // 拿到公钥

        return [
            'aes' => $aesKey,
            'data' => $dataString ? $this->getAESData($aesKey, $dataString) : '',
        ];
    }

    /**
     * 获取AES数据
     *
     * @param string $aesKey AES的key
     * @param string $dataString 被AES加密的数据
     * @return string
     */
    public function getAESData($aesKey, $dataString)
    {
        $aes = new \phpseclib\Crypt\AES(\phpseclib\Crypt\AES::MODE_ECB);
        $aes->setKey($aesKey);

        return $aes->decrypt(urldecode(base64_decode(urldecode($dataString))));
    }

    /**
     * 根据RSA的私钥 解密AES的key
     *
     * @param string $key
     * @return string
     */
    public function getDecrypt($key)
    {
        $rsa = new \phpseclib\Crypt\RSA();
        $rsa->loadKey($this->getPrivateKey());

        return $rsa->decrypt($key);
    }

    /**
     * RSA根据公钥加密$text
     *
     * @param string $text
     * @return string
     */
    public function getEncryptRSA($text)
    {
        $rsa = new \phpseclib\Crypt\RSA();
        $rsa->loadKey($this->getPublicKey());

        return $rsa->encrypt($text);
    }

    /**
     * AES加密
     *
     * @param $key
     * @param $text
     * @return string
     */
    public function getEncryptAES($key, $text)
    {
        $aes = new \phpseclib\Crypt\AES(\phpseclib\Crypt\AES::MODE_ECB);
        $aes->setKey($key);

        return $aes->encrypt($text);
    }

    /**
     * 根据公钥加密$text,并base64转码,然后url转码
     *
     * @param $text
     * @return string
     */
    public function getEncryptRSAForBase64($text)
    {
        return urlencode(base64_encode(urlencode($this->getEncryptRSA($text))));
    }

    public function getEncryptAESForBase64($key, $text)
    {
        return urlencode(base64_encode(urlencode($this->getEncryptAES($key, $text))));
    }

    /**
     * 创建公钥和私钥
     *
     * @return array
     */
    public function createKey()
    {
        $rsa = new \phpseclib\Crypt\RSA();

        return $rsa->createKey();
    }

    protected function getPrivateKey()
    {
        return $this->getConfig()['private'] ?? '';
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: code
 * Date: 2019/4/17
 * Time: 14:08
 */

namespace App\Helper\Classes\ApiCipher;


use App\Helper\Vendor\Laravel\Cache;

class LaravelRSAAndAES extends BaseRSAAndAES
{

    /**
     * @return mixed|array
     */
    public function getConfig()
    {
        return config('hashing.rsa');
    }

    protected function getAESKeyCacheMinute()
    {
        return config('sys.cache.user-aes-key-minute');
    }

    /**
     * 获取token的缩小版
     *
     * @return string
     */
    protected function getMd5Token()
    {
        return md5(\Illuminate\Support\Facades\Auth::getToken());
    }

    /**
     * 获取aes的key
     *
     * @return mixed
     */
    public function getUserAESKeyMap()
    {
        return Cache::get($this->getUserAESKeyToCacheKey(), '');
    }

    /**
     * 保存aes的key
     *
     * @param $aesKey
     */
    public function saveUserAESKeyMap($aesKey)
    {
        if (empty($aesKey)) {
            return;
        }

        Cache::put($this->getUserAESKeyToCacheKey(), $aesKey, $this->getAESKeyCacheMinute());
    }

    /**
     * 每个接口的AES绑定接口的token
     *
     * @return string
     */
    protected function getUserAESKeyToCacheKey()
    {
        return 'AES:' . $this->getMd5Token();
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: code
 * Date: 2019/4/15
 * Time: 11:41
 */

namespace App\Helper\Classes\ApiCipher;

trait RSAAndAESControllerTrait
{

    /**
     * 保存aes的key,中间件会保存,直接返回成功即可
     *
     * @return mixed
     */
    public function saveAESKey()
    {
        return $this->success();
    }
}
<?php
/**
 * Created by PhpStorm.
 * User: code
 * Date: 2019/4/15
 * Time: 11:46
 */

namespace App\Helper\Classes\ApiCipher;

use \App\Helper\Classes\ApiCipher\LaravelRSAAndAES as CipherClass;

class RSAAndAESLaravelInputMiddleware
{
    use \Illuminate\Foundation\Validation\ValidatesRequests;

    protected $errorText = [
        'data_required' => 'data加密的数据不能为空',
        'data_error' => 'data加密的数据错误',
        'key_error' => 'key密钥错误',
    ];

    /**
     * 指定需要加密的AES的key
     * 是否加密返回结果
     *
     * @var string
     */
    protected $aesKey = '';

    /**
     * 处理定义多次的问题
     *
     * @var bool
     */
    protected static $cipherBool = false;

    protected function getCipherClass()
    {
        return CipherClass::instance(); // 系统继承的类
    }

    /**
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @param bool $force 强制校验
     * @return mixed
     * @throws \Illuminate\Validation\ValidationException
     */
    public function handle($request, \Closure $next, $force = false)
    {
        if (
            (!$force && ifNotProduction()) // 只有正式环境才加密
            || !ifFirstBool(__CLASS__)
        ){
            return $next($request);
        }

        // 中间件结束后执行
        \Illuminate\Support\Facades\Event::listen(\Illuminate\Foundation\Http\Events\RequestHandled::class, function ($event){
            /** @var \Illuminate\Foundation\Http\Events\RequestHandled $event */
            $this->cipherResponse($event->response);
        });

        // 处理加密
        if (empty($this->getRequestKey($request))){ // 已保存AES的key,不需要再传参
            $this->handleTokenData($request);
        }else{
            $this->handleKeyData($request);
        }

        return $next($request);
    }

    /**
     * 根据token找到AES的key
     *
     * @param \Illuminate\Http\Request $request
     * @throws \Illuminate\Validation\ValidationException
     */

    protected function handleTokenData($request)
    {
        $this->aesKey = $this->getCipherClass()->getUserAESKeyMap();
        if (empty($this->aesKey)) {
            $this->throwValidateError($request, $this->errorText['key_error']);
        }

        $requestData = $this->getRequestData($request);

        $data = [];
        try{
            $data = (array)json_decode_not_throw($this->getCipherClass()->getAESData($this->aesKey, $requestData['data']));
        }catch (\Exception $exception){ // 处理Decryption error报错
            $this->throwValidateError($request, $exception->getMessage());
        }

        $this->requestSet($request, $data);
    }

    /**
     * 每次都传key
     *
     * @param \Illuminate\Http\Request $request
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function handleKeyData($request)
    {
        $requestKey = $this->getRequestKey($request);
        $requestData = $this->getRequestData($request);

        $data = '';
        try{
            $this->aesKey = $this->getCipherClass()->getRSADecrypt($requestKey);
            $data = !empty($requestData['data']) ? $this->getCipherClass()->getAESData($this->aesKey, $requestData['data']) : '';
        }catch (\Exception $exception){ // 处理Decryption error报错
            $this->throwValidateError($request, $exception->getMessage());
        }

        if ($data !== false){
            $data = json_decode_not_throw($data);
        }else{ // 解密失败,如果是空则返回空字符串
            $this->throwValidateError($request, $this->errorText['data_error']);
        }

        $this->requestSet($request, (array)$data);
        $this->getCipherClass()->saveUserAESKeyMap($this->aesKey);
    }

    /**
     * 获取请求参数里的AES的key的字符串
     *
     * @param \Illuminate\Http\Request $request
     * @return array|string|null
     */
    protected function getRequestKey($request)
    {
        return $request->header('aes-key');
    }

    /**
     * 获取请求参数里的被AES加密的数据
     *
     * @param \Illuminate\Http\Request $request
     * @return array
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function getRequestData($request)
    {
        $requestData = $this->validate($request, [
            'data' => 'sometimes|required'
        ], ['data.required' => $this->errorText['data_required'],]);
        $request->offsetUnset('data');

        $requestData['data'] = $requestData['data'] ?? '';

        return $requestData;
    }

    /**
     * 抛出验证错误
     *
     * @param \Illuminate\Http\Request $request
     * @param $message
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function throwValidateError($request, $message)
    {
        $request->merge(['aesKey' => $this->aesKey]); // 通过request来传参
        $this->validate(\Illuminate\Http\Request::create(''), ['key' => 'required'], ['key.required' => $message]);
    }

    /**
     * @param \Illuminate\Http\Request $request
     * @param $data
     */
    protected function requestSet($request, $data)
    {
        foreach ($request->keys() as $key) {
            unset($request[$key]);
        }

        $request->merge($data);
    }

    /**
     * 加密response
     *
     * @param $dataResponse
     */
    protected function cipherResponse($dataResponse)
    {
        if ($this->aesKey){ // 需要加密返回的response
            $aesKey = $this->aesKey;
            $this->aesKey = '';
            if (is_object($dataResponse) && $dataResponse instanceof \Illuminate\Http\JsonResponse){ // 全加密
                // 请求要考虑get,所以请求时加密的数据要放在
                $dataResponse->setContent($this->getCipherClass()->getEncryptAES($aesKey, $dataResponse->getContent()));
            }

        }
    }
}

以上是关于PHP 加密:AES & RSA的主要内容,如果未能解决你的问题,请参考以下文章

基于RSA+AES实现前后端(VUE+PHP)参数加密传输

php aes和rsa加密的区别

java加密用PHP解密

PHP如何实现AES加解密

RSA+AES请求组合加密

前端AES + RSA加密