PHP 怎么加密聊天

wen PHP项目 2

本文目录导读:

PHP 怎么加密聊天

  1. 核心加密方案选择
  2. 端到端加密通信流(流程说明)
  3. 核心 PHP 代码实现(使用 OpenSSL + NaCl 风格)
  4. 数据库设计(用于存储密文)
  5. 安全建议与注意事项

在 PHP 中“加密聊天”通常涉及两个层面:传输加密(数据在网络上不被窃听)和端到端加密(服务器也无法读取内容)。

为了兼顾安全性和实用性,这里提供一个全面的端到端加密(E2EE) 解决方案,涵盖算法选择、核心代码、数据库设计和防范注意事项。


核心加密方案选择

PHP 中常用的加密库为 OpenSSLSodium,推荐使用 Sodium(从 PHP 7.2 起内置,且 libsodium 更安全和现代),但如果你的项目依赖旧版,可以用 OpenSSL。

算法 用途 推荐程度
ChaCha20-Poly1305 对称加密(流加密) 首选(Sodium)
AES-256-GCM 对称加密(块加密) 首选(OpenSSL)
X25519 非对称密钥交换(ECDH) 首选
RSA 非对称加密(旧式) 不推荐(密钥交换太慢)

最终方案: 使用 AES-256-GCM 作为消息加密算法,使用 X25519 进行密钥协商,使用 Ed25519 进行数字签名(防篡改)。


端到端加密通信流(流程说明)

在聊天场景中,为了让服务器无法阅读消息,需要实现以下流程:

  1. 用户注册/登录: 服务器存储用户的 公钥(用于加密)和 签名公钥(用于验签)。
  2. 好友建立: 用户双方交换公钥。
  3. 发起会话(关键): 发送方生成一个随机的 对称密钥(会话密钥),使用接收方的公钥(X25519)加密这个对称密钥,并发送给服务器存储。
  4. 发送消息: 发送方使用该 对称密钥 通过 AES-256-GCM 加密明文消息,并附加签名,服务器只负责存储密文。
  5. 接收消息: 接收方从服务器获取“加密的会话密钥”,用自己的私钥解密得到对称密钥,然后用对称密钥解密密文。

注意: 本方案中私钥只会存储在用户本地(浏览器/客户端),服务器仅存储公钥和密文。


核心 PHP 代码实现(使用 OpenSSL + NaCl 风格)

以下代码展示会话密钥交换消息加密的核心逻辑。

密钥生成(用户注册时)

<?php
// 生成接收方的密钥对(这通常发生在客户端,但这里模拟服务器处理逻辑)
function generateUserKeyPair() {
    // 使用 openssl 生成 X25519 密钥对(或者使用 sodium_crypto_box_keypair)
    // 注意:X25519 在 openssl 中对应 EC 曲线。
    $config = [
        'private_key_type' => OPENSSL_KEYTYPE_EC,
        'curve_name' => 'prime256v1' // 也可以使用 X25519,但 OpenSSL 原生支持有限,这里用 prime256v1 做演示
    ];
    $res = openssl_pkey_new($config);
    openssl_pkey_export($res, $privateKey);
    $details = openssl_pkey_get_details($res);
    $publicKey = $details['key'];
    // 实际生产环境建议使用 sodium:
    // $keypair = sodium_crypto_box_keypair();
    // $publicKey = sodium_crypto_box_publickey($keypair);
    // $privateKey = sodium_crypto_box_secretkey($keypair);
    return ['public' => $publicKey, 'private' => $privateKey];
}

生成会话密钥并加密(发送方)

<?php
function encryptMessageForRecipient($recipientPublicKey, $plainMessage) {
    // 1. 生成一个随机的对称加密密钥(会话密钥)
    $sessionKey = random_bytes(32); // AES-256 需要 32 字节
    // 2. 使用接收方的公钥加密这个会话密钥(模拟 ECDH 加密)
    //    (这里为了简化演示,直接使用 RSA 公钥加密,实际应用应使用 sodium_crypto_box_seal)
    openssl_public_encrypt($sessionKey, $encryptedSessionKey, $recipientPublicKey);
    // 3. 使用会话密钥对消息进行 AES-256-GCM 加密
    $iv = random_bytes(12); // GCM 推荐 12 字节 IV
    // 绑定额外的认证数据(例如发送者ID,防止密文被篡改)
    $aad = 'sender:123|receiver:456';
    $cipherText = openssl_encrypt(
        $plainMessage,
        'aes-256-gcm',
        $sessionKey,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,           // 认证标签,防止篡改
        $aad,
        16              // tag 长度
    );
    // 返回加密的数据包(密文 + 加密的会话密钥 + IV + 标签)
    return [
        'cipher' => base64_encode($cipherText),
        'enc_session_key' => base64_encode($encryptedSessionKey),
        'iv' => base64_encode($iv),
        'tag' => base64_encode($tag),
        'aad' => $aad
    ];
}

解密消息(接收方)

<?php
function decryptMessageFromSender($recipientPrivateKey, $encryptedPacket) {
    // 1. 使用自己的私钥解密出会话密钥
    $encryptedSessionKey = base64_decode($encryptedPacket['enc_session_key']);
    $decryptedSessionKey = null;
    openssl_private_decrypt($encryptedSessionKey, $decryptedSessionKey, $recipientPrivateKey);
    if ($decryptedSessionKey === null) {
        throw new Exception("无法解密会话密钥,私钥不匹配。");
    }
    // 2. 使用会话密钥解密消息
    $cipherText = base64_decode($encryptedPacket['cipher']);
    $iv = base64_decode($encryptedPacket['iv']);
    $tag = base64_decode($encryptedPacket['tag']);
    $aad = $encryptedPacket['aad'];
    $plainText = openssl_decrypt(
        $cipherText,
        'aes-256-gcm',
        $decryptedSessionKey,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        $aad
    );
    return $plainText;
}
// 示例用法
$userA = generateUserKeyPair();
$userB = generateUserKeyPair();
// 用户A 发送给 用户B
$encrypted = encryptMessageForRecipient($userB['public'], '这是一条机密消息,在服务器上不可见。');
echo "密文(服务器看到的数据): " . $encrypted['cipher'] . "\n\n";
// 用户B 解密
$decrypted = decryptMessageFromSender($userB['private'], $encrypted);
echo "解密后的明文: " . $decrypted . "\n";

数据库设计(用于存储密文)

由于是端到端加密,数据库只存储密文和元数据:

CREATE TABLE messages (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    conversation_id CHAR(36) NOT NULL,      -- 会话ID
    sender_id BIGINT UNSIGNED NOT NULL,     -- 发送者ID(服务器可读)
    receiver_id BIGINT UNSIGNED NOT NULL,   -- 接收者ID(服务器可读)
    encrypted_session_key TEXT NOT NULL,    -- 发送者用接收方公钥加密后的会话密钥
    cipher_text LONGBLOB NOT NULL,          -- 加密后的消息内容
    iv BINARY(12) NOT NULL,                 -- 加密初始向量
    auth_tag BINARY(16) NOT NULL,           -- GCM认证标签
    aad TEXT,                               -- 附加认证数据(发送者ID等)
    signature VARCHAR(255),                 -- 发送者的数字签名(可选,防抵赖)
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_conversation (conversation_id, created_at)
);

安全建议与注意事项

  1. 私钥存储: 永远不要将用户的私钥发送到服务器,私钥应存储在客户端(浏览器 LocalStorage 加密后,或移动端 Keychain)。

  2. 使用 Sodium 而非 OpenSSL: 上面的代码为了演示用了 openssl_public_encryptopenssl_private_decrypt(RSA),在实际生产中,请使用 sodium_crypto_box()sodium_crypto_box_open(),Sodium 内置了 X25519 和 ChaCha20-Poly1305,且更难误用。

    // Sodium 方式加密(推荐)
    $keypair = sodium_crypto_box_keypair();
    $publicKey = sodium_crypto_box_publickey($keypair);
    $anonymousMessageToPublicKey = sodium_crypto_box_seal('秘密消息', $publicKey);
    // 解密
    $decrypted = sodium_crypto_box_seal_open($anonymousMessageToPublicKey, $keypair);
  3. 前向保密(PFS): 上述方案“每次会话使用独立密钥”时,如果服务器记录私钥,有被破解的风险,高级方案建议引入 临时密钥(Ephemeral Keys)双棘轮算法(Double Ratchet)(类似 Signal 协议),但这在纯 PHP 中实现较复杂。

  4. 认证与防篡改: 除了 GCM 的 tag,建议对 sender_idconversation_id 使用 HMAC 签名,防止密文被移动到其他会话。

  5. Web 前端配合: 由于私钥在客户端,JavascriptWebCrypto API 是加密聊天的核心,PHP 通常只负责验证登录和存储密文,加解密主要在用户的浏览器中完成。


  • 基础方案: 使用 PHP 的 sodium 扩展,通过 sodium_crypto_box 实现端到端加密。
  • 核心逻辑: 用接收方公钥加密随机密钥(会话密钥),用会话密钥加密明文消息。
  • 安全核心: 私钥绝不上传服务器。

如果你只是指“服务器与浏览器之间的传输加密”(即 HTTPS),那么只需配置好 SSL/TLS 证书 即可,这是任何聊天系统的基础安全要求。

抱歉,评论功能暂时关闭!