PHP域名加密须先标准化再AES-256-GCM加密:统一小写、去端口、可选去www;密钥用random_bytes(32),IV每次重置,密文含IV+tag+密文三部分;解密须校验tag和域名合法性,密钥轮换需兼容新旧密钥并重加密旧数据。

PHP中域名加密前必须先标准化
直接对原始 $_SERVER['HTTP_HOST'] 或 $_SERVER['SERVER_NAME'] 加密是危险的——不同访问方式(带www、不带www、含端口、HTTPS重定向)会导致同一站点产生多个“不同域名”,后续校验无法匹配。必须先做归一化处理:
- 统一转小写:
strtolower($host) - 剥离端口(如
:8080)和协议头(https://已不在HTTP_HOST中,但需注意反向代理场景) - 可选:强制去 www(
preg_replace('/^www\./i', '', $host)),但需确认业务是否允许子域共用密钥 - 最终建议格式:
$canonicalDomain = rtrim($host, '.')(防尾部点号)
用 openssl_encrypt() 替代 mcrypt 或简单 base64
mcrypt 已被废弃,base64_encode() 不是加密,只是编码,毫无安全性。真实加密必须用现代 AEAD 模式:
- 推荐算法:
'aes-256-gcm'(PHP 7.1+),兼顾安全与性能 - 密钥必须由
random_bytes(32)生成,**绝不能硬编码字符串或用 md5/SHA 拼接** - 每次加密必须用新 IV:
$iv = random_bytes(openssl_cipher_iv_length('aes-256-gcm')) - 完整密文需保存三部分:密文 + IV + 认证标签(
$tag),缺一不可,否则解密失败
示例关键片段:
$cipher = 'aes-256-gcm';
$key = hex2bin('your-32-byte-hex-key-from-config'); // 实际应从环境变量或密钥管理服务读取
$iv = random_bytes(openssl_cipher_iv_length($cipher));
$tag = '';
$ciphertext = openssl_encrypt($domain, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
$stored = base64_encode($iv . $tag . $ciphertext); // 合并后 Base64 存数据库字段
解密时验证完整性比解密本身更关键
攻击者可能篡改存储的密文,若只检查 openssl_decrypt() 返回 false 就报错,会暴露侧信道(如通过响应时间判断密文结构)。正确做法:
立即学习“PHP免费学习笔记(深入)”;
- 先完整解析出 IV、tag、密文三段(长度固定:
iv_len=12,tag_len=16for GCM) - 调用
openssl_decrypt()时传入$tag,函数内部自动校验;若 tag 错误,返回 false 且**不泄露任何中间状态** - 解密成功后,仍需用
hash_equals()校验解密结果是否为合法域名(防空字节截断或 null 字符注入) - 绝不使用
==或strcmp()做域名比对
密钥轮换时域名需重新加密,不能只更新密钥配置
很多团队以为改了配置里的密钥,旧数据就自动“升级”了——这是典型误解。加密是单向操作,旧密文只能用原密钥解密。若要轮换密钥:
- 必须在应用层加一层兼容逻辑:先尝试用新密钥解密,失败则用旧密钥解密并立即用新密钥重加密存储
- 数据库字段需预留足够长度(GCM 下约增加 40%),避免
varchar(255)存不下新格式 - 批量重加密任务必须加分布式锁,防止并发导致同一行被多次覆盖
- 切勿在低峰期一次性全量更新——DNS 解析、CDN 缓存、客户端 Cookie 都可能让旧域名短暂回流
真正难的不是加密函数怎么写,而是让加密后的值在分布式、多版本、长期演进的系统里始终可逆、可验证、可迁移。











