url参数加密需避免直接base64_encode(),应使用strtr()替换+、/、=为url安全字符;真正防篡改须加hmac校验;推荐openssl_encrypt()配合aes-128-cbc、随机iv及pbkdf2派生密钥。

URL参数加密必须避开base64_encode()直接裸用
直接对URL参数做base64_encode()只是编码,不是加密,且+、/、=会破坏URL结构。浏览器可能把+当空格,/被路由截断,=在query String里也可能引发解析歧义。
实操建议:
- 用
strtr()把Base64结果里的+、/、=替换成URL安全字符(比如-、_、.),这是最轻量的兜底做法 - 若需真正防篡改,必须加MAC校验(如
hash_hmac('sha256', $data, $key)),把签名拼在密文后或单独传参,服务端严格比对 - 别用
md5()或sha1()单独做校验——它们不防碰撞,且无密钥,攻击者可重放或篡改
php 7.2+ 推荐用openssl_encrypt()而非mcrypt
mcrypt已废弃多年,PHP 7.2起彻底移除。现在标准做法是openssl_encrypt() + openssl_decrypt(),但默认行为容易踩坑。
关键点:
立即学习“PHP免费学习笔记(深入)”;
- 必须显式指定
$options = OPENSSL_RAW_DATA,否则返回base64格式,还得再处理一遍URL安全化 - 推荐AES-128-CBC模式,
$iv必须随机生成且和密文一起传(比如base64url编码后拼在密文末尾),不能复用或硬编码 - IV长度必须严格等于
openssl_cipher_iv_Length('AES-128-CBC')(通常是16字节),少一字节就会解密失败,错误信息是openssl_decrypt(): IV length is invalid - 密钥建议用
hash_pbkdf2('sha256', $password, $salt, 100000, 32, true)派生,别直接用短口令当密钥
解密失败常见报错及定位方法
用户最常遇到的是空白页、NULL返回或Warning: openssl_decrypt(): IV length is invalid。根本原因往往不在加解密逻辑本身,而在传输环节。
排查顺序:
- 先检查URL中密文是否被截断——nginx默认限制
large_client_header_buffers,过长参数直接400;apache可能因LimitRequestFieldSize丢弃 - 确认GET参数是否被框架自动
urldecode()过一轮:如果前端用encodeURIComponent(),PHP收到时已自动解码,你再urldecode()就错了 - 打印
strlen($encrypted)和base64_decode($encrypted, true)返回值,false说明base64不合法(常见于+号变空格) - 别依赖
$_GET原始值做解密——先用parse_str($_SERVER['QUERY_STRING'], $raw)拿到未处理的query string再拆分
简单场景够用的自定义函数模板
如果你只要快速防低级爬虫或误点,不需要金融级安全,下面这个函数平衡了简洁和可用性:
function urlSafeEncrypt(string $data, string $key): string { $iv = random_bytes(16); $encrypted = openssl_encrypt($data, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); return rtrim(strtr(base64_encode($iv . $encrypted), '+/=', '-_.'), '.'); } function urlSafeDecrypt(string $Token, string $key): ?string { $decoded = base64_decode(strtr($token, '-_.', '+/=')); if ($decoded === false || strlen($decoded) < 16) return null; [$iv, $ciphertext] = [substr($decoded, 0, 16), substr($decoded, 16)]; return openssl_decrypt($ciphertext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); }
注意:$key必须是32字节(AES-256)或16字节(AES-128),用hash_hmac()生成时要指定true返回二进制。传给前端的$token可直接拼在URL里,比如?id=abc123&token=xxx。
真正的难点不在写这两个函数,而在于密钥怎么安全存、怎么轮换、IV怎么不重复——这些没设计好,加密就形同虚设