> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 加密（Encryption）

> 介绍如何使用 Laravel 的 Crypt 门面通过 AES-256-CBC 对数据进行加解密。涵盖 APP_KEY 的生成、模型转型（cast）、个人信息的安全存储等。

## 什么是 Crypt 门面

Laravel 的加密服务借助 **OpenSSL** 的 AES-256-CBC 加密（或 AES-128-CBC），通过简洁的接口提供数据加解密能力。

Laravel 加密后的值都会带上**消息认证码（MAC）** 签名。
一旦加密后的值被篡改，就无法再被解密。

```mermaid theme={null}
flowchart LR
    A["明文数据"] -->|"Crypt::encryptString()"| B["加密值<br>（带 MAC 签名）"]
    B -->|"Crypt::decryptString()"| A
    B -->|检测到篡改| C["DecryptException"]
```

***

## 配置

### 生成 APP\_KEY

在使用加密之前，需要设置 `config/app.php` 中的 `key`。
该值会从 `APP_KEY` 环境变量中读取。

使用 `php artisan key:generate` 命令生成安全的密钥。
它会通过 PHP 的安全随机源生成密码学上安全的密钥。

```shell theme={null}
php artisan key:generate
```

Laravel 在安装时通常会自动生成，并将其保存到 `.env` 文件中。

```ini theme={null}
APP_KEY=base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY=
```

<Warning>
  绝对不要公开 `APP_KEY`。一旦该密钥泄露，所有加密数据都可能被解密。
</Warning>

### 密钥轮换

更改加密密钥会导致所有已认证用户的会话被登出。
因为 Laravel 会加密所有 Cookie（包括 Session Cookie）。

而且旧密钥加密的数据也无法再被解密。

为了缓解此问题，可以在 `APP_PREVIOUS_KEYS` 中以逗号分隔列出旧密钥。

```ini theme={null}
APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY="
APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w="
```

Laravel 加密时始终使用当前密钥，但在解密时若当前密钥失败，则会按顺序尝试旧密钥。
这样在密钥轮换期间用户仍能继续使用应用。

***

## 加密

使用 `Crypt` 门面的 `encryptString` 方法进行加密。
加密值使用 OpenSSL 与 AES-256-CBC，且会带上 MAC 签名。

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;

class DigitalOceanTokenController extends Controller
{
    /**
     * 保存用户的 API 令牌
     */
    public function store(Request $request): RedirectResponse
    {
        $request->user()->fill([
            'token' => Crypt::encryptString($request->token),
        ])->save();

        return redirect('/secrets');
    }
}
```

<Tip>
  `encryptString` **不会**序列化字符串直接加密。若要加密对象或数组，请使用 `encrypt`。
</Tip>

| 方法                             | 用途               |
| ------------------------------ | ---------------- |
| `Crypt::encryptString($value)` | 直接加密字符串          |
| `Crypt::encrypt($value)`       | 先序列化再加密（支持数组、对象） |

***

## 解密

使用 `Crypt` 门面的 `decryptString` 方法解密。
若 MAC 无效或其它原因导致解密失败，会抛出 `DecryptException`。

```php theme={null}
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;

try {
    $decrypted = Crypt::decryptString($encryptedValue);
} catch (DecryptException $e) {
    // 解密失败时的处理
    abort(400, '无效的数据。');
}
```

| 方法                             | 用途               |
| ------------------------------ | ---------------- |
| `Crypt::decryptString($value)` | 作为字符串解密          |
| `Crypt::decrypt($value)`       | 解密并反序列化（支持数组、对象） |

***

## 模型转型（Cast）

在 Eloquent 模型中使用 `encrypted` 转型可以自动加解密属性。

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'secret_note'    => 'encrypted',           // 加密字符串
            'profile_data'   => 'encrypted:array',     // 加密数组存储
            'preferences'    => 'encrypted:collection',// 加密集合存储
            'metadata'       => 'encrypted:object',    // 加密对象存储
            'settings'       => 'encrypted:json',      // 作为 JSON 加密存储
        ];
    }
}
```

设置了 cast 之后，模型属性在赋值与读取时会自动加解密。

```php theme={null}
// 自动加密后保存到数据库
$user->secret_note = '秘密备忘';
$user->save();

// 自动解密后返回
echo $user->secret_note; // '秘密备忘'
```

<Info>
  `encrypted:*` cast 内部使用 `Crypt::encrypt` 与 `Crypt::decrypt`。
  数据库列类型建议使用 `text` 或 `longText`。
</Info>

***

## 实用示例：加密存储个人信息

以下是一个将个人信息（如个人编号、信用卡号等）安全存入数据库的典型模式。

### 迁移

```php theme={null}
Schema::create('profiles', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->text('my_number')->nullable();     // 加密值保存为 text
    $table->text('bank_account')->nullable();
    $table->timestamps();
});
```

### 模型

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Profile extends Model
{
    protected $fillable = ['user_id', 'my_number', 'bank_account'];

    protected function casts(): array
    {
        return [
            'my_number'    => 'encrypted',
            'bank_account' => 'encrypted',
        ];
    }
}
```

### 控制器

```php theme={null}
use App\Models\Profile;
use Illuminate\Http\Request;

class ProfileController extends Controller
{
    public function store(Request $request)
    {
        $request->validate([
            'my_number'    => ['required', 'string'],
            'bank_account' => ['required', 'string'],
        ]);

        // 模型会自动加密后保存
        Profile::create([
            'user_id'      => $request->user()->id,
            'my_number'    => $request->my_number,
            'bank_account' => $request->bank_account,
        ]);

        return redirect('/profile');
    }

    public function show(Request $request)
    {
        $profile = $request->user()->profile;

        // 模型会自动解密后返回
        return view('profile.show', ['profile' => $profile]);
    }
}
```

***

## 小结

| 想做的事        | 方法                                |
| ----------- | --------------------------------- |
| 生成 APP\_KEY | `php artisan key:generate`        |
| 加密字符串       | `Crypt::encryptString($value)`    |
| 解密字符串       | `Crypt::decryptString($value)`    |
| 加密数组/对象     | `Crypt::encrypt($value)`          |
| 自动加密模型属性    | 使用 `'column' => 'encrypted'` cast |
| 密钥轮换        | 在 `APP_PREVIOUS_KEYS` 中列出旧密钥      |

## 下一步

<Columns cols={2}>
  <Card title="哈希" icon="lock" href="/zh/hashing">
    学习如何安全地对密码进行哈希与校验。
  </Card>

  <Card title="授权" icon="shield" href="/zh/authorization">
    介绍如何使用 Policy 与 Gate 进行访问控制。
  </Card>
</Columns>


## Related topics

- [Crypto —— AT Protocol 加密](/zh/packages/laravel-bluesky/crypto.md)
- [配置](/zh/configuration.md)
- [教程 - Laravel Console Starter](/zh/packages/laravel-console-starter/tutorial.md)
- [Redis](/zh/redis.md)
- [OAuth 2.0 认证 - Google Sheets API for Laravel](/zh/packages/laravel-google-sheets/oauth.md)
