前几天在做一个需求,想要使用对称加密算法来加密一个即将被保存在数据库中的值,而从 Eloquent 模型访问该属性时自动解密其值。中间发现encrypt加密出的数据每次都会变化,希望能改为每次得到的加密字符串结果一致,但是加解密文件在vender下面,这就需要重写或覆盖vender下面的文件方法
结果:
新增 fixedencrypt() 函数, 你也可以覆盖原来的encrypt(), 因为我是希望保留一份原来的加密方式
实现:
1. 加密方式使用laravel自带的加密函数 encrypt 和 decrypt,文档链接: 官方文档
2. 使用修改器,来实现保存数据库时自动加密,取出时自动解密。文档: 修改器-官方文档
3. 重写加密函数,文档:扩展包开发, 服务容器,服务提供者
因为我使用的是lumen框架,稍有些出入,但实现思路是一样的
自动加解密数据库字段:
- config/app.php文件 配置 APP_KEY 如下, 我本地需要在.env文件中配置APP_KEY
执行 php artisan key:generate 命令来生成 APP_KEY, 如果用的是 lumen 框架,框架内部简化了 artisan 命令, 执行命令时会报错,可用str_random(32);
- 数据库文件,修改set 和get方法,以下代码希望password字段保存时自动加密,取出自动加密
<?php
namespace BaisonBundle\Entities;
use Doctrine\ORM\Mapping as ORM;
/**
* Test 测试表
*
* @ORM\Table(name="test", options={"comment"="测试表"})
* @ORM\Entity(repositoryClass="BaisonBundle\Repositories\TestRepository")
*/
class Test
{
/**
* @var integer
*
* @ORM\Id
* @ORM\Column(name="test_id", type="bigint", options={"comment":"test id"})
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $test_id;
/**
* @var string
*
* @ORM\Column(name="password", type="string", options={"comment":"password"})
*/
private $password;
/**
* Get testId.
*
* @return int
*/
public function getTestId()
{
return $this->test_id;
}
/**
* Set password.
*
* @param string $password
*
* @return Test
*/
public function setPassword($password)
{
$this->password = encrypt($password); //保存数据库时会自动加密
return $this;
}
/**
* Get password.
*
* @return string
*/
public function getPassword()
{
return decrypt($this->password);//取出时自动解密
}
}
3. 新增一条数据
app('registry')->getManager('default')->getRepository(Test::class)->create(['password'=>'123456']);
4. 数据库最终保存结果
重写或者覆盖vender,扩展fixedencryp函数:
- 创建service文件,src/EspierBundle/Services/EncrypterService.php 目录位置自定义
<?php
namespace EspierBundle\Services;
use Illuminate\Encryption\Encrypter;
/**
* Class EncrypterService 继承vender下要扩展或重写的类
*
*/
class EncrypterService extends Encrypter
{
public function __construct($key, $cipher = 'AES-128-CBC')
{
parent::__construct($key, $cipher);
}
//要重写的方法及内容
public function encrypt($value, $serialize = true)
{
// $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
$iv = '5322ff2432432422';
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
}
2. 新增Provider文件,src/EspierBundle/Providers/FixedEncryptionServiceProvider.php目录位置自定义
<?php
namespace EspierBundle\Providers;
use EspierBundle\Services\EncrypterService;
use RuntimeException;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
/**
* Class FixedEncryptionServiceProvider 继承ServiceProvider;
*
* 所有服务提供器都会继承 Illuminate\Support\ServiceProvider 类。
* 大多数服务提供器都包含 register 和 boot 方法。
* 在 register 方法中,你只需要绑定类到 服务容器中。
* 而不需要尝试在 register 方法中注册任何事件监听器、路由或任何其他功能。
*/
class FixedEncryptionServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('fixedencrypt', 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 EncrypterService($key, $config['cipher']);
});
}
/**
* Extract the encryption key from the given configuration.
*
* @param array $config
* @return string
*/
protected function key(array $config)
{
return tap($config['key'], function ($key) {
if (empty($key)) {
throw new RuntimeException(
'No application encryption key has been specified.'
);
}
});
}
}
3. bootstrap/app.php 添加以下内容
$app->loadComponent('app', 'EspierBundle\Providers\FixedEncryptionServiceProvider', 'fixedencrypt');
$app->register(EspierBundle\Providers\FixedEncryptionServiceProvider::class);
到此可使用 app('fixedencrypt')->encrypt($value) 调用,如果想直接使用fixedencrypt()
4. 修改app/helpers.php 文件,新增以下内容;参考文档:如何在 Laravel 中创建自己的 PHP 辅助函数
if (! function_exists('fixedencrypt')) {
/**
* Encrypt the given value.
*
* @param string $value
* @return string
*/
function fixedencrypt($value)
{
return app('fixedencrypt')->encrypt($value);
}
}
可直接使用fixedencrypt()
扩展需求:为每台服务器设置独立的key值,而且又要保证数据能正确解密
- config/app.php 可自定义多个key值
2. src/EspierBundle/Providers/FixedEncryptionServiceProvider.php文件内容
<?php
namespace EspierBundle\Providers;
use EspierBundle\Services\EncrypterService;
use RuntimeException;
use Illuminate\Support\Str;
use Illuminate\Support\ServiceProvider;
/**
* Class FixedEncryptionServiceProvider
*
*/
class FixedEncryptionServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('fixedencrypt', function ($app) {
return new EncrypterService();
});
}
}
3. src/EspierBundle/Services/EncrypterService.php 文件
<?php
namespace EspierBundle\Services;
use Illuminate\Encryption\Encrypter;
/**
* Class EncrypterService
*
*/
class EncrypterService extends Encrypter
{
// 定义初始化
public 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 = config('app.'.$key);
return $this;
}
public function encrypt($value, $serialize = true)
{
// $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
$iv = 'X3xMx3dnoCtSprda';
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'));
if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
}
4. app/helpers.php
if (! function_exists('fixedencrypt')) {
/**
* Encrypt the given value.
*
* @param string $value
* @return string
*/
function fixedencrypt($value, $keyid='key')
{
return app('fixedencrypt')->setKey($keyid)->encrypt($value);
}
}
if (! function_exists('fixeddecrypt')) {
/**
* Encrypt the given value.
*
* @param string $value
* @return string
*/
function fixeddecrypt($value, $keyid='key')
{
return app('fixedencrypt')->setKey($keyid)->decrypt($value);
}
}
5. 调用
# 加密
fixedencrypt('123456', 'key1');//eyJpdiI6IldETjRUWGd6Wkc1dlEzUlRjSEprWVE9PSIsInZhbHVlIjoiaU9ycVwvNDJQd2tWeDZrV0NXNzhQZ3c9PSIsIm1hYyI6IjRmODY5NGRjZGE2NWZmNmI0MmM3OTQ4YmYwYWUyNTkwNDk1NmYyOWUzZWIyYjJkZjYxMTA4MTRjMDcxNDRiY2EifQ
# 解密
$str = "eyJpdiI6IldETjRUWGd6Wkc1dlEzUlRjSEprWVE9PSIsInZhbHVlIjoiaU9ycVwvNDJQd2tWeDZrV0NXNzhQZ3c9PSIsIm1hYyI6IjRmODY5NGRjZGE2NWZmNmI0MmM3OTQ4YmYwYWUyNTkwNDk1NmYyOWUzZWIyYjJkZjYxMTA4MTRjMDcxNDRiY2EifQ";
fixeddecrypt($str,'key1'); // 123456