必须在应用层实现端到端加密:上传前用 openssl_encrypt(AES-256-GCM) 本地加密,nonce 随机生成且不复用,密钥从环境变量或 KMS 动态获取,严禁与密文共存;大文件需分块处理或调用 OpenSSL CLI;解密后代理播放并设置正确响应头。

PHP上传音频文件时,不能只靠又拍云或OSS的“encryption => true”开关就认为数据安全了——那只是服务端传输加密或存储层透明加密,密钥由服务商托管,你无法控制解密权限,也不防内部越权访问。真要保护用户上传的音频(比如问诊录音、会议存档、版权样音),必须在应用层完成端到端加密:上传前本地加密,存储为二进制密文,解密仅在授权后由服务端用私钥/密钥动态完成。
用 openssl_encrypt 对音频流做 AES-256-GCM 加密
这是最可控、兼容性好、且带认证的方案。GCM 模式能同时保证机密性与完整性,避免篡改后静默解密成功。
-
openssl_encrypt要求 PHP ≥ 7.1,且 OpenSSL 扩展启用(检查extension_loaded('openssl')) - 别用
AES-256-CBC:它不验证密文完整性,攻击者可篡改 IV 或密文导致解密出错或信息泄露;AES-256-GCM自动附带 16 字节认证标签(tag),解密时必须校验 - 每次加密都需新生成 12 字节 nonce(不是 IV),用
random_bytes(12);不能复用,否则 GCM 安全性崩塌 - 密钥必须是 32 字节(AES-256),建议从环境变量读取:
hex2bin(getenv('AUDIO_ENC_KEY')),而非硬编码或 base64_decode 静态字符串 - 加密后把 nonce + 密文 + tag 拼成一个 blob 存储(顺序固定),例如:
$ciphertext = $nonce . $encrypted . $tag;解密时按偏移拆分
大音频文件别用 file_get_contents 全量加载
一个 100MB 的 WAV 文件调 file_get_contents 会直接触发内存溢出(OOM),尤其在共享主机或低配容器里。
- 改用
fopen+fread分块读取,每块 ≤ 1MB,配合openssl_encrypt的流式上下文(需手动处理 PKCS#7 填充和 GCM 的 nonce 分段逻辑) - 更稳妥的做法是:先写临时文件 → 用系统命令调
openssl enc -aes-256-gcm -salt -in temp.wav -out encrypted.aenc -pass env:AUDIO_ENC_KEY→ 再删临时文件。前提是服务器允许exec且信任 OpenSSL CLI 版本(≥ 1.1.1) - 如果必须纯 PHP 流式加密,phpseclib 的
Crypt\AES(v3.x)支持 GCM 模式和自定义 nonce,但需自行管理分块边界与 final tag 合并,复杂度陡增
密钥绝对不能和音频文件存在同一台机器
哪怕加密再强,密钥跟密文放一起,等于把保险柜钥匙焊死在柜门上。
立即学习“PHP免费学习笔记(深入)”;
- 禁止把密钥写进 Webroot 下的
.env或配置文件——一旦 PHP 解析失败或路径泄露,密钥即失守 - 推荐方案:密钥存放在 HashiCorp Vault 中,上传时用短时效 Token 动态拉取;或使用云厂商 KMS(如 AWS KMS、阿里云 KMS)的
DecryptAPI 在解密环节调用,密钥永不落地 - 若只能用文件存储,密钥文件权限设为
600,且路径完全脱离 Web 可访问范围(例如/etc/app-keys/audio.key),Web 进程以独立用户运行并仅对该路径有读权限 - 注意:又拍云等平台的“服务端加密”密钥不由你掌控,其加密仅防磁盘窃取,不防平台管理员或 API 泄露,不能替代应用层加密
最容易被忽略的一点:音频文件的元信息(采样率、时长、格式头)在加密后全部丢失,前端播放前必须先解密还原原始二进制流,不能指望浏览器直接播 .mp3.enc。这意味着所有播放逻辑都要走 PHP 代理(如 /play?id=abc123),并在响应头中正确设置 Content-Type 和 Content-Length,否则 Safari 和部分安卓 WebView 会拒绝播放。



















