PHP加密解密与安全编码实战
PHP加密解密与安全编码实战安全编码是每个PHP开发者都应该掌握的技能。从密码存储到数据传输加密从输入验证到输出转义每一步都关系到系统的安全。密码存储是最基本的安全需求。PHP提供了password_hash和password_verify用起来很方便。php// 密码安全存储$password MySecurePassword123!;// 哈希密码$hash password_hash($password, PASSWORD_BCRYPT, [cost 12]);echo BCRYPT哈希: $hash\n;// PHP8.0 可以用Argon2id$hash2 password_hash($password, PASSWORD_ARGON2ID);echo Argon2id哈希: $hash2\n;// 验证密码if (password_verify($password, $hash)) {echo 密码验证成功\n;}// 检查是否需要重新哈希if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {$newHash password_hash($password, PASSWORD_ARGON2ID);echo 密码哈希已升级\n;}?对称加密用openssl或sodium扩展。sodium是PHP7.2以后内置的现代加密库。php// sodium加密function encrypt(string $plaintext, string $key): string{$nonce random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$ciphertext sodium_crypto_secretbox($plaintext, $nonce, $key);return base64_encode($nonce . $ciphertext);}function decrypt(string $encrypted, string $key): string{$decoded base64_decode($encrypted);$nonce substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$ciphertext substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$plaintext sodium_crypto_secretbox_open($ciphertext, $nonce, $key);if ($plaintext false) {throw new RuntimeException(解密失败);}return $plaintext;}// 生成密钥$key sodium_crypto_secretbox_keygen();// 加密$secret 这是需要加密的敏感数据;$encrypted encrypt($secret, $key);echo 加密后: $encrypted\n;// 解密$decrypted decrypt($encrypted, $key);echo 解密后: $decrypted\n;?openssl加密用于需要跨平台兼容的场景phpclass OpenSSLCrypto{private string $cipher aes-256-gcm;public function encrypt(string $plaintext, string $key): string{$iv openssl_random_pseudo_bytes(openssl_cipher_iv_length($this-cipher));$tag ;$ciphertext openssl_encrypt($plaintext, $this-cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);if ($ciphertext false) {throw new RuntimeException(加密失败: . openssl_error_string());}// 返回 base64(iv ciphertext tag)return base64_encode($iv . $ciphertext . $tag);}public function decrypt(string $data, string $key): string{$decoded base64_decode($data);$ivLen openssl_cipher_iv_length($this-cipher);$tagLen 16;$iv substr($decoded, 0, $ivLen);$tag substr($decoded, -$tagLen);$ciphertext substr($decoded, $ivLen, -$tagLen);$plaintext openssl_decrypt($ciphertext, $this-cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);if ($plaintext false) {throw new RuntimeException(解密失败);}return $plaintext;}}$crypto new OpenSSLCrypto();$key random_bytes(32); // 256位密钥$encrypted $crypto-encrypt(敏感数据, $key);$decrypted $crypto-decrypt($encrypted, $key);echo 解密: $decrypted\n;?数据签名用于验证数据的完整性和来源php// 签名验证class Signer{private string $secret;public function __construct(string $secret){$this-secret $secret;}public function sign(array $data): string{ksort($data);$message json_encode($data);return hash_hmac(sha256, $message, $this-secret);}public function verify(array $data, string $signature): bool{return hash_equals($this-sign($data), $signature);}}$signer new Signer(your-secret-key);$data [user_id 123, amount 99.99, timestamp time()];$signature $signer-sign($data);echo 签名: $signature\n;$verified $signer-verify($data, $signature);echo 验证: . ($verified ? 通过 : 不通过) . \n;// 篡改数据会导致签名验证失败$tampered $data;$tampered[amount] 9999.99;echo 篡改验证: . ($signer-verify($tampered, $signature) ? 通过 : 不通过) . \n;?输入验证是安全的第一道防线。filter_var系列函数提供了各种验证功能。phpfunction validateInput(array $input): array{$errors [];// 邮箱验证if (!empty($input[email]) !filter_var($input[email], FILTER_VALIDATE_EMAIL)) {$errors[email] 邮箱格式不正确;}// URL验证if (!empty($input[url]) !filter_var($input[url], FILTER_VALIDATE_URL)) {$errors[url] URL格式不正确;}// IP验证if (!empty($input[ip]) !filter_var($input[ip], FILTER_VALIDATE_IP)) {$errors[ip] IP地址格式不正确;}// 整数验证if (isset($input[age]) !filter_var($input[age], FILTER_VALIDATE_INT, [options [min_range 1, max_range 150]])) {$errors[age] 年龄必须在1-150之间;}// 布尔值验证if (isset($input[active])) {$input[active] filter_var($input[active], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);if ($input[active] null) {$errors[active] 必须为布尔值;}}return $errors;}$input [email not-an-email,url not-a-url,ip 999.999.999.999,age 200,active yes,];$errors validateInput($input);if (!empty($errors)) {foreach ($errors as $field $error) {echo $field: $error\n;}}?XSS防护是输出时必须考虑的问题。htmlspecialchars是基本的转义函数。php// XSS防护function escapeHtml(string $value): string{return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8, false);}function escapeJs(string $value): string{return json_encode($value, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);}function escapeUrl(string $value): string{return rawurlencode($value);}// 使用示例$userInput ;echo HTML上下文: . escapeHtml($userInput) . \n;echo JS上下文: . escapeJs($userInput) . \n;echo URL上下文: . escapeUrl($userInput) . \n;// 安全的SQL查询使用预处理语句function findUserByEmail(PDO $pdo, string $email): ?array{$stmt $pdo-prepare(SELECT * FROM users WHERE email ?);$stmt-execute([$email]);$result $stmt-fetch();return $result ?: null;}// 安全的文件上传function validateUploadedFile(array $file): void{$allowedMimes [image/jpeg, image/png, image/gif];$allowedExts [jpg, jpeg, png, gif];$maxSize 5 * 1024 * 1024;if ($file[error] ! UPLOAD_ERR_OK) {throw new RuntimeException(文件上传错误);}if ($file[size] $maxSize) {throw new RuntimeException(文件太大);}$ext strtolower(pathinfo($file[name], PATHINFO_EXTENSION));if (!in_array($ext, $allowedExts)) {throw new RuntimeException(不允许的文件扩展名);}$finfo finfo_open(FILEINFO_MIME_TYPE);$mime finfo_file($finfo, $file[tmp_name]);finfo_close($finfo);if (!in_array($mime, $allowedMimes)) {throw new RuntimeException(不支持的文件类型);}}?安全编码的几个基本原则。不要信任用户输入验证和过滤所有输入数据。输出到不同上下文要用不同的转义方式。用预处理语句防止SQL注入。密码用password_hash存储。敏感数据加密存储和传输。错误信息不要暴露给用户。遵循这些原则大部分安全问题都能避免。