You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PDO安全实现SQL Server加密密码的显示、隐藏与更新

你给出的password_hash+password_verify是单向哈希方案,不可逆无法解密出原始明文,仅适用于用户登录密码校验场景。你需要支持查看明文密码,必须改用对称加密方案,以下是完整实现方案:

一、基础架构设计

1. 加密方案选择

使用AES-256-GCM对称加密算法,自带完整性校验防篡改,完全满足加解密需求。

2. 存储规则

  • 加密密钥:存储在服务端非Web可访问目录的配置文件(如.env)中,禁止硬编码到代码或存入数据库,文件权限设置为仅PHP运行用户可读。
  • 数据库存储:SQL Server中存储base64编码后的密文(包含IV、校验Tag、加密内容),字段类型可用VARCHAR(255)。
  • 权限表:存储用户权限,区分view_pwd(查看密码)、edit_pwd(编辑密码)两种权限。
  • 审计日志表:记录用户ID、操作类型、操作时间、操作的密码记录ID,日志需设置为不可删除、不可修改。
二、后端PHP+PDO实现

1. 加解密工具函数

// 从配置读取密钥,AES-256要求密钥长度为32位
define('AES_KEY', $_ENV['AES_256_KEY']);

// 加密函数
function encryptPassword(string $plainText): string|false
{
    $ivLength = openssl_cipher_iv_length('aes-256-gcm');
    $iv = openssl_random_pseudo_bytes($ivLength);
    $tag = '';
    $encrypted = openssl_encrypt(
        $plainText,
        'aes-256-gcm',
        AES_KEY,
        OPENSSL_RAW_DATA,
        $iv,
        $tag
    );
    // 拼接IV、校验Tag、密文后base64编码,方便存储
    return base64_encode($iv . $tag . $encrypted);
}

// 解密函数
function decryptPassword(string $encryptedText): string|false
{
    $decoded = base64_decode($encryptedText);
    $ivLength = openssl_cipher_iv_length('aes-256-gcm');
    $iv = substr($decoded, 0, $ivLength);
    $tag = substr($decoded, $ivLength, 16); // GCM校验Tag固定为16位
    $encrypted = substr($decoded, $ivLength + 16);
    return openssl_decrypt(
        $encrypted,
        'aes-256-gcm',
        AES_KEY,
        OPENSSL_RAW_DATA,
        $iv,
        $tag
    );
}

2. PDO操作封装(连接SQL Server)

需提前安装pdo_sqlsrv扩展支持SQL Server连接:

// 初始化PDO连接
$pdo = new PDO("sqlsrv:Server=你的数据库地址;Database=你的库名", "数据库账号", "数据库密码");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 查询解密密码接口
function getDecryptedPwd(PDO $pdo, int $recordId, int $currentUserId): string|false
{
    // 先校验查看权限
    $permCheck = $pdo->prepare("SELECT COUNT(1) FROM user_permissions WHERE user_id = ? AND permission_code = 'view_pwd'");
    $permCheck->execute([$currentUserId]);
    if ($permCheck->fetchColumn() < 1) {
        return false;
    }
    // 查询加密密文
    $stmt = $pdo->prepare("SELECT encrypted_pwd FROM pwd_storage WHERE id = ?");
    $stmt->execute([$recordId]);
    $encrypted = $stmt->fetchColumn();
    if (!$encrypted) return false;
    // 写入审计日志
    $logStmt = $pdo->prepare("INSERT INTO audit_log (user_id, operation, target_id, operate_time) VALUES (?, 'view_password', ?, GETDATE())");
    $logStmt->execute([$currentUserId, $recordId]);
    // 解密返回
    return decryptPassword($encrypted);
}

// 更新密码接口
function updatePwd(PDO $pdo, int $recordId, int $currentUserId, string $newPlainPwd): bool
{
    // 校验编辑权限
    $permCheck = $pdo->prepare("SELECT COUNT(1) FROM user_permissions WHERE user_id = ? AND permission_code = 'edit_pwd'");
    $permCheck->execute([$currentUserId]);
    if ($permCheck->fetchColumn() < 1) {
        return false;
    }
    // 加密新密码
    $encrypted = encryptPassword($newPlainPwd);
    if (!$encrypted) return false;
    // 更新数据库
    $stmt = $pdo->prepare("UPDATE pwd_storage SET encrypted_pwd = ? WHERE id = ?");
    $res = $stmt->execute([$encrypted, $recordId]);
    // 写入审计日志
    if ($res) {
        $logStmt = $pdo->prepare("INSERT INTO audit_log (user_id, operation, target_id, operate_time) VALUES (?, 'edit_password', ?, GETDATE())");
        $logStmt->execute([$currentUserId, $recordId]);
    }
    return $res;
}
三、前端交互实现

禁止初始渲染明文密码,必须通过异步请求触发权限校验后再返回明文,避免页面缓存泄露敏感信息:

<!DOCTYPE html>
<html>
<head>
    <title>密码管理</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<input type="text" id="pwdInput" value="***********" readonly>
<button id="showBtn">查看密码</button>
<button id="editBtn" style="display: none;margin-left: 8px;">编辑密码</button>

<script>
// 页面初始化时根据用户权限控制编辑按钮显示
const hasEditPerm = <?php echo $currentUserHasEditPerm ? 'true' : 'false' ?>;
if (hasEditPerm) {
    document.getElementById('editBtn').style.display = 'inline-block';
}

// 查看密码逻辑
document.getElementById('showBtn').addEventListener('click', async () => {
    try {
        const res = await axios.post('/api/get-password.php', {
            record_id: 1, // 要查询的密码记录ID
            _csrf: '你的CSRF校验Token'
        });
        if (res.data.success) {
            document.getElementById('pwdInput').value = res.data.password;
        } else {
            alert('无权限查看密码');
        }
    } catch (e) {
        alert('请求失败,请重试');
    }
});

// 编辑密码逻辑可自行扩展:点击编辑按钮弹出输入框,提交新密码调用更新接口即可
</script>
</body>
</html>
四、安全加固建议
  • 所有敏感操作接口必须校验登录态、权限、CSRF Token,防止越权和跨站请求伪造。
  • 查看密码操作建议增加二次身份验证(如输入当前用户的登录密码、短信验证码)。
  • 全链路使用HTTPS传输,避免明文密码被抓包窃取。
  • 禁止前端日志、本地缓存存储明文密码,查看后的密码可设置30秒后自动隐藏。

内容的提问来源于stack exchange,提问作者Wimpie Norman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 10:24:06