Laravel 框架加密解密如何实现 key 值多变的需求

需求分析

我们有一台总的数据处理服务器,又有很多台数据源服务器。为了让他们相互通信,我们在每个服务器上搭建了相同的laravel网站框架。分服务器之会和总服务器进行数据交互。
同时,为了数据安全,总服务器和分服务器之间的数据传输使用laravel默认的encrypt加密和decrypt解密。
起初,为了数据能被正确解密,每一台服务器的key值要保持一致。但是随之,问题也就来了。如果后期我们的一台分服务器数据泄露了的话,那么就意味着,别人掌握了我们的所有的数据,有很大的安全隐患。所以,我们需要为每台服务器设置独立的key值,而且又要保证数据能正确解密。

虽然非对称加密是个很好的解决方案,但是要自己额外安装依赖包,额外设置

原理分析

我通过对laravel的源码分析发现,encrypt()其实是在app启动的时候,使用默认的key配置注册了一次类。

public function register()
    {
        $this->app->singleton('encrypter', function ($app) {
            $config = $app->make('config')->get('app');

            // If the key starts with "base64:", we will need to decode the key before handing
            // it off to the encrypter. Keys may be base-64 encoded for presentation and we
            // want to make sure to convert them back to the raw bytes before encrypting.
            if (Str::startsWith($key = $this->key($config), 'base64:')) {
                $key = base64_decode(substr($key, 7));
            }

            return new Encrypter($key, $config['cipher']);
        });
    }

以上代码摘自\vendor\laravel\framework\src\Illuminate\Encryption\EncryptionServiceProvider.php

既然是这样,我们就可以对该Encrypter类进行多次实例化来达成我们的目的。

解决方法

  • 我习惯在控制器Controllers目录多生成一个Service目录,来放置控制类中经常使用的类和方法。这次我们使用如下命令新建了EncryptService类。

    php artisan make:controller Service/EncryptService
  • 使用单例模式来构建。

    // 单例函数的实例化
      static private $instance;
    
      static public function getInstance(){
          if(!self::$instance){
              self::$instance = new self();
          }
          return self::$instance;
      }
      // 定义初始化
      private function __construct()
      {
          // If the key starts with "base64:", we will need to decode the key before handing
          // it off to the encrypter. Keys may be base-64 encoded for presentation and we
          // want to make sure to convert them back to the raw bytes before encrypting.
          // 如果没有传key的值,可以使用默认的key值
          $this->default();
      }
  • getEncrypter对配置进行判断和处理,并new一个新的Encrypter类,如下:

      /*
       * 加密之前的验证
       */
      private function getEncrypter(){
          if (Str::startsWith($this->key, 'base64:')) {
              $this->key = base64_decode(substr($this->key, 7));
          }
          $this->key = (string) $this->key;
          if (!static::supported($this->key, $this->cipher)) {
              throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.');
          }
    
          return new Encrypter($this->key, $this->cipher);
      }
  • encrypt为例,使用$this->getEncrypter()方法来帮我们做加密中转。

      /*
       * 定义公开的加密函数
       */
      public function encrypt($value, $serialize = true){
          // 处理之前进行一步验证
          return $this->getEncrypter()->encrypt($value, $serialize);
      }
  • 使用setKey()来设置每次的加密的值,使用default()来恢复使用默认的值

      /*
       * 设置默认的属性
       * 本类是一个单例模式的类,如果前期设置了配置,后期又想使用默认配置,你需要调用default方法才行
       */
      public function default(){
          $this->key = config('app.key');
          $this->cipher = config('app.cipher');
          return $this;
      }
      // 设置key的值
      public function setKey($key){
          $this->key = $key;
          return $this;
      }
  • 调用的时候,我们只需要对我们自己的EncryptService来操作就行了。使用setKey()来设置新的key的值。如下

    use App\Http\Controllers\Controller;
    use App\Http\Controllers\Service\EncryptService;
    class HomeController extends Controller
    {
        public function index(Content $content)
        {
            $encrypt = EncryptService::getInstance()->setKey('base64:uHNY0XQRxDLtQzl8ZWEMUDIbFGteGZA0o9BMP+L5sa8=')->encrypt('123');
            dd($encrypt);
        }
    }   

附录

以下是类的完整源码,有不足的地方,还请多多指教。

<?php

namespace App\Http\Controllers\Service;

use RuntimeException;
use Illuminate\Encryption\Encrypter;
use Illuminate\Support\Str;

class EncryptService
{
    // 定义加密参数
    private $key;
    // 定义加密方式
    private $cipher;

    // 单例函数的实例化
    static private $instance;

    static public function getInstance(){
        if(!self::$instance){
            self::$instance = new self();
        }
        return self::$instance;
    }
    // 定义初始化
    private function __construct()
    {
        // If the key starts with "base64:", we will need to decode the key before handing
        // it off to the encrypter. Keys may be base-64 encoded for presentation and we
        // want to make sure to convert them back to the raw bytes before encrypting.
        // 如果没有传key的值,可以使用默认的key值
        $this->default();
    }
    /*
     * 设置默认的属性
     * 本类是一个单例模式的类,如果前期设置了配置,后期又想使用默认配置,你需要调用default方法才行
     */
    public function default(){
        $this->key = config('app.key');
        $this->cipher = config('app.cipher');
        return $this;
    }
    // 设置key的值
    public function setKey($key){
        $this->key = $key;
        return $this;
    }
    // 设置cipher的值
    public function setCipher($cipher){
        $this->cipher = $cipher;
        return $this;
    }
    /*
     * 定义公开的加密函数
     */
    public function encrypt($value, $serialize = true){
        // 处理之前进行一步验证
        return $this->getEncrypter()->encrypt($value, $serialize);
    }
    /*
     * 直接加密
     */
    public function encryptString($value)
    {
        return $this->getEncrypter()->encrypt($value, false);
    }
    /*
     * 定义公开的解密函数
     */
    public function decrypt($payload, $unserialize = true){
        // 处理之前进行一步验证
        return $this->getEncrypter()->decrypt($payload, $unserialize);
    }
    /*
     * 直接解密
     */
    public function decryptString($payload)
    {
        return $this->getEncrypter()->decrypt($payload, false);
    }
    /**
     * Determine if the given key and cipher combination is valid.
     *
     * @param  string  $key
     * @param  string  $cipher
     * @return bool
     */
    private function supported($key, $cipher)
    {
        $length = mb_strlen($key, '8bit');

        return ($cipher === 'AES-128-CBC' && $length === 16) ||
            ($cipher === 'AES-256-CBC' && $length === 32);
    }
    /*
     * 加密之前的验证
     */
    private function getEncrypter(){
        if (Str::startsWith($this->key, 'base64:')) {
            $this->key = base64_decode(substr($this->key, 7));
        }
        $this->key = (string) $this->key;
        if (!static::supported($this->key, $this->cipher)) {
            throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.');
        }

        return new Encrypter($this->key, $this->cipher);
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

不是直接有这个嘛,直接生成,php artisan make:generate ,解密的时候对应APP_KEY值解密就好了

3年前 评论

@Q956569011 “key 值多变”,一个网站同时维护多个key值,你生成这个key值,前一个key值就失效了

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!