先搞懂 KMS 是什么8)KMS=密钥管理服务,专门管"加密用的钥匙"问题:你用密钥加密数据,但密钥存哪? 存数据库?万一数据库泄露,密钥和数据一起完蛋 存代码?更危险 解决:密钥交给专业的 KMS 管,你的代码只存加密后的密钥 信封加密(最核心概念): 明文数据 ↓ 用 DEK(数据密钥)加密 加密数据 ← 存数据库 DEK(数据密钥) ↓ 用 KMS 里的 CMK(主密钥)加密 加密的DEK ← 也存数据库 CMK 永远不离开 KMS,你只能让 KMS 帮你加解密---三种方案选一个 ┌─────────────────┬─────────────┬────────────┐ │ 方案 │ 适合场景 │ 成本 │ ├─────────────────┼─────────────┼────────────┤ │ AWS KMS │ 已在用AWS │ 按调用收费 │ ├─────────────────┼─────────────┼────────────┤ │ HashiCorp Vault │ 自建/私有云 │ 免费自托管 │ ├─────────────────┼─────────────┼────────────┤ │ 本地软KMS │ 开发/小项目 │ 免费 │ └─────────────────┴─────────────┴────────────┘---安装 # AWS KMS composer require aws/aws-sdk-php # HashiCorp Vault composer require mittwald/vault-php # 加密基础库(三种方案都用) composer require defuse/php-encryption---目录结构 app/Service/Kms/KmsInterface.php # 统一接口 AwsKmsService.php # AWS实现 VaultKmsService.php # Vault实现 LocalKmsService.php # 本地实现(开发用) EncryptionService.php # 信封加密核心 Middleware/FieldEncryptMiddleware.php # 自动加解密敏感字段 config/autoload/kms.php---1.配置// config/autoload/kms.phpreturn[// 选择驱动:aws / vault / local'driver'=>env('KMS_DRIVER','local'),'aws'=>['region'=>env('AWS_REGION','ap-northeast-1'),'key_id'=>env('AWS_KMS_KEY_ID'),// CMK的ARN或别名'access_key'=>env('AWS_ACCESS_KEY_ID'),'secret_key'=>env('AWS_SECRET_ACCESS_KEY'),],'vault'=>['url'=>env('VAULT_URL','http://127.0.0.1:8200'),'token'=>env('VAULT_TOKEN'),'key_name'=>env('VAULT_KEY_NAME','my-app-key'),// Transit引擎的密钥名],'local'=>[// 本地主密钥,生产环境绝对不能用这个'master_key'=>env('LOCAL_MASTER_KEY','base64:'.base64_encode(random_bytes(32))),],];---2.统一接口// app/Service/Kms/KmsInterface.php<?php namespace App\Service\Kms;interfaceKmsInterface{// 生成一个数据密钥,返回[明文DEK, 加密后的DEK]publicfunctiongenerateDataKey():array;// 用主密钥解密DEK,拿回明文DEKpublicfunctiondecryptDataKey(string $encryptedDek):string;// 轮换主密钥(定期换密钥)publicfunctionrotateKey():void;}---3.AWS KMS 实现// app/Service/Kms/AwsKmsService.php<?php namespace App\Service\Kms;use Aws\Kms\KmsClient;classAwsKmsServiceimplementsKmsInterface{private KmsClient $client;private string $keyId;publicfunction__construct(){$cfg=config('kms.aws');$this->keyId=$cfg['key_id'];$this->client=newKmsClient(['region'=>$cfg['region'],'version'=>'latest','credentials'=>['key'=>$cfg['access_key'],'secret'=>$cfg['secret_key'],],]);}publicfunctiongenerateDataKey():array{// AWS直接帮你生成DEK,同时返回明文和加密版本$result=$this->client->generateDataKey(['KeyId'=>$this->keyId,'KeySpec'=>'AES_256',]);return['plaintext'=>base64_encode($result['Plaintext']),// 用完立即丢弃'encrypted_dek'=>base64_encode($result['CiphertextBlob']),// 存数据库];}publicfunctiondecryptDataKey(string $encryptedDek):string{$result=$this->client->decrypt(['CiphertextBlob'=>base64_decode($encryptedDek),]);returnbase64_encode($result['Plaintext']);}publicfunctionrotateKey():void{// AWS KMS 支持自动轮换,也可手动触发$this->client->enableKeyRotation(['KeyId'=>$this->keyId]);}}---4.HashiCorp Vault 实现// app/Service/Kms/VaultKmsService.php<?php namespace App\Service\Kms;use GuzzleHttp\Client;classVaultKmsServiceimplementsKmsInterface{private Client $http;private string $baseUrl;private string $keyName;publicfunction__construct(){$cfg=config('kms.vault');$this->keyName=$cfg['key_name'];$this->baseUrl=rtrim($cfg['url'],'/').'/v1/transit';$this->http=newClient(['headers'=>['X-Vault-Token'=>$cfg['token']],'timeout'=>3,]);}publicfunctiongenerateDataKey():array{// 本地生成随机DEK,用Vault加密它$plainDek=random_bytes(32);$resp=$this->http->post("{$this->baseUrl}/encrypt/{$this->keyName}",['json'=>['plaintext'=>base64_encode($plainDek)],]);$data=json_decode($resp->getBody(),true);return['plaintext'=>base64_encode($plainDek),'encrypted_dek'=>$data['data']['ciphertext'],// vault:v1:xxxxx];}publicfunctiondecryptDataKey(string $encryptedDek):string{$resp=$this->http->post("{$this->baseUrl}/decrypt/{$this->keyName}",['json'=>['ciphertext'=>$encryptedDek],]);$data=json_decode($resp->getBody(),true);return$data['data']['plaintext'];// 已经是base64}publicfunctionrotateKey():void{// Vault Transit 引擎一键轮换$this->http->post("{$this->baseUrl}/keys/{$this->keyName}/rotate");}}---5.本地软KMS(开发/测试用)// app/Service/Kms/LocalKmsService.php<?php namespace App\Service\Kms;classLocalKmsServiceimplementsKmsInterface{private string $masterKey;publicfunction__construct(){$key=config('kms.local.master_key');// 支持 base64: 前缀$this->masterKey=str_starts_with($key,'base64:')?base64_decode(substr($key,7)):$key;}publicfunctiongenerateDataKey():array{$plainDek=random_bytes(32);$iv=random_bytes(16);// 用主密钥加密DEK$encrypted=openssl_encrypt($plainDek,'AES-256-CBC',$this->masterKey,OPENSSL_RAW_DATA,$iv);$encryptedDek=base64_encode($iv.$encrypted);return['plaintext'=>base64_encode($plainDek),'encrypted_dek'=>$encryptedDek,];}publicfunctiondecryptDataKey(string $encryptedDek):string{$raw=base64_decode($encryptedDek);$iv=substr($raw,0,16);$enc=substr($raw,16);$plainDek=openssl_decrypt($enc,'AES-256-CBC',$this->masterKey,OPENSSL_RAW_DATA,$iv);returnbase64_encode($plainDek);}publicfunctionrotateKey():void{// 本地模式不支持轮换,仅开发用thrownew\RuntimeException('本地KMS不支持密钥轮换,请使用AWS或Vault');}}---6.信封加密服务(核心业务层)// app/Service/EncryptionService.php<?php namespace App\Service;use App\Service\Kms\KmsInterface;use App\Service\Kms\{AwsKmsService,VaultKmsService,LocalKmsService};classEncryptionService{private KmsInterface $kms;publicfunction__construct(){$this->kms=match(config('kms.driver')){'aws'=>make(AwsKmsService::class),'vault'=>make(VaultKmsService::class),default=>make(LocalKmsService::class),};}/** * 加密数据(信封加密) * 返回可以直接存数据库的字符串 */publicfunctionencrypt(string $plaintext):string{// 1. 让KMS生成一个DEK['plaintext'=>$dekPlain,'encrypted_dek'=>$dekEnc]=$this->kms->generateDataKey();// 2. 用DEK加密实际数据$dek=base64_decode($dekPlain);$iv=random_bytes(16);$enc=openssl_encrypt($plaintext,'AES-256-CBC',$dek,OPENSSL_RAW_DATA,$iv);// 3. 明文DEK用完立即销毁(PHP里置空)$dek=str_repeat("\0",strlen($dek));unset($dek);// 4. 把加密数据 + 加密DEK打包存储returnjson_encode(['v'=>1,// 版本号,方便以后升级'dek'=>$dekEnc,// 加密后的DEK'iv'=>base64_encode($iv),'enc'=>base64_encode($enc),]);}/** * 解密数据 */publicfunctiondecrypt(string $ciphertext):string{$data=json_decode($ciphertext,true);// 1. 让KMS解密DEK,拿回明文DEK$dekPlain=$this->kms->decryptDataKey($data['dek']);$dek=base64_decode($dekPlain);// 2. 用DEK解密实际数据$iv=base64_decode($data['iv']);$enc=base64_decode($data['enc']);$plaintext=openssl_decrypt($enc,'AES-256-CBC',$dek,OPENSSL_RAW_DATA,$iv);// 3. 明文DEK用完销毁$dek=str_repeat("\0",strlen($dek));unset($dek);return$plaintext;}/** * 加密数组里的指定字段(批量处理) */publicfunctionencryptFields(array $data,array $fields):array{foreach($fields as $field){if(isset($data[$field])){$data[$field]=$this->encrypt((string)$data[$field]);}}return$data;}publicfunctiondecryptFields(array $data,array $fields):array{foreach($fields as $field){if(isset($data[$field])){$data[$field]=$this->decrypt($data[$field]);}}return$data;}/** * 密钥轮换(定期执行) */publicfunctionrotateKey():void{$this->kms->rotateKey();}}---7.实际使用示例// app/Controller/UserController.php<?php namespace App\Controller;use App\Service\EncryptionService;use Hyperf\HttpServer\Annotation\{Controller,PostMapping,GetMapping};use Hyperf\HttpServer\Contract\RequestInterface;#[Controller(prefix:'/api/user')]classUserController{// 需要加密的敏感字段private array $sensitiveFields=['id_card','phone','bank_card'];publicfunction__construct(private EncryptionService $enc){}#[PostMapping(path:'/create')]publicfunctioncreate(RequestInterface $request){$data=$request->all();// 存库前加密敏感字段$encrypted=$this->enc->encryptFields($data,$this->sensitiveFields);// $encrypted['id_card'] 现在是加密后的字符串,安全存库// DB::table('users')->insert($encrypted);return['code'=>200,'msg'=>'创建成功'];}#[GetMapping(path:'/{id}')]publicfunctionshow(int $id){// $user = DB::table('users')->find($id);// 从库里取出后解密// $user = $this->enc->decryptFields($user, $this->sensitiveFields);return['code'=>200,'data'=>'用户信息'];}}---8.定时密钥轮换命令// app/Cron/KeyRotationCron.php<?php namespace App\Cron;use App\Service\EncryptionService;use Hyperf\Crontab\Annotation\Crontab;// 每90天轮换一次密钥#[Crontab(rule:'0 2 1 */3 *',name:'KeyRotation',memo:'季度密钥轮换')]classKeyRotationCron{publicfunction__construct(private EncryptionService $enc){}publicfunctionexecute():void{$this->enc->rotateKey();// 记录轮换日志logger()->info('密钥轮换完成',['time'=>date('Y-m-d H:i:s')]);}}---整体流程图 写入数据库 用户提交{id_card:"110101..."}↓ EncryptionService::encrypt()↓ KMS生成DEK(随机256位密钥) ↓ DEK加密id_card → 密文 CMK加密DEK → 加密DEK ↓ 存库:{id_card:{v:1,dek:"xxx",iv:"xxx",enc:"xxx"}}明文DEK内存销毁 读取数据库 取出加密字段 ↓ EncryptionService::decrypt()↓ KMS用CMK解密DEK → 明文DEK ↓ 明文DEK解密密文 → 原始数据 明文DEK内存销毁 ↓ 返回{id_card:"110101..."}---三种方案对比 ┌──────────┬────────────┬────────────┬──────────┐ │ │ AWS KMS │ Vault │ 本地 │ ├──────────┼────────────┼────────────┼──────────┤ │ 适合 │ 云上生产 │ 私有化部署 │ 开发测试 │ ├──────────┼────────────┼────────────┼──────────┤ │ 密钥安全 │ AWS托管 │ 自己管 │ 不安全 │ ├──────────┼────────────┼────────────┼──────────┤ │ 轮换 │ 自动 │ 一键 │ 不支持 │ ├──────────┼────────────┼────────────┼──────────┤ │ 审计 │ CloudTrail │ Vault审计 │ 无 │ ├──────────┼────────────┼────────────┼──────────┤ │ 费用 │ 按调用 │ 免费 │ 免费 │ └──────────┴────────────┴────────────┴──────────┘.env 里切换 KMS_DRIVER=aws/vault/local,代码不用改。Hyperf方案 密钥管理服务(KMS)
张小明
前端开发工程师
jQuery 遍历 - 后代
jQuery 遍历 - 后代元素 (Descendants) 在 jQuery 中,后代遍历用于从当前选中的元素向下查找其子元素、孙元素等所有后代节点。这是 DOM 操作中最常用的功能之一。 一、核心方法 1. children() - 获取直接子元素 只返回一级子元素(直接后代)&…
SeanLib系列函数库-MyFlash
查看其它库函数说明,请点击此处跳转到SeanLib主页 1. 本篇内容 本篇提供了一个适用于STM32F429的片内Flash操作库,支持读(一般不使用)、扇区擦除、写(按字节形式)三个方法,相比于HAL库中的函数…
解锁BilibiliDown的5大隐藏功能:从基础下载到批量管理的完整探索指南
解锁BilibiliDown的5大隐藏功能:从基础下载到批量管理的完整探索指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.co…
3、IoT物理极限架构最佳实践:一文讲透端边双主(可分可合,非传统高可用)
核心概念:端边双主、非对称双主、物理极限、物理约束IoT物理极限架构思想前提是:物理极限,物理约束,而最佳实践准则是:非对称端边双主,轻量云赋能,个体自治(端、边、云),降级服务&am…
C#合并首尾相连多段线实战
问题解构 用户需要在C#中实现合并所有首尾相连的多段线。核心需求包括: 识别连接性:判断多段线是否首尾相连(端点重合)合并算法:将相连的多段线合并为单一多段线CAD环境:通常在AutoCAD等CAD二次开发环境中…
基于TMS320F28035的汇川变频器源码:MD290、MD380、MD500三种型号及新的...
变频器汇川的三种变频器源码,包括MD290,MD380,MD500变频器源码,基于TMS320F28035 新的SVC3算法,高速速度波动小,新的转子电阻,漏感辩识算法.系统概述 本文基于汇川技术股份有限公司开发的MD290、MD380、MD500系列变频器的嵌入式软…