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

如何在PHP中解密Mastodon Web Push通知

Mastodon PHP Web Push通知解密方案

问题背景

已通过Mastodon订阅API创建Web Push订阅,POST数据如下:

$post_data = array(
    "subscription" => array(
        "endpoint" => $env["endpoint"], // PHP脚本所在的端点URL
        "keys" => array(
            "p256dh" => $env["vapid_public"], // 公钥
            "auth" => $env["vapid_private"] // 私钥
        )
    ),
    "data" => array(
        "alerts" => array(
            "mention" => true // 接收提及通知
        )
    )
);

收到Mastodon推送通知时,通过file_get_contents("php://input")获取到二进制Payload,需解密但无法将请求头密钥、订阅密钥对应到openssl_decrypt()参数中。

收到的请求头(敏感值已替换):

[Authorization] => WebPush some_key_1.some_key_2.some_key_3 // JSON Web Token
[Crypto-Key] => dh=crypto_key_1;p256ecdsa=crypto_key_2
[Encryption] => salt=salt_key
[Content-Encoding] => aesgcm
[Urgency] => normal
[Ttl] => 172800
[Content-Type] => application/octet-stream
[Digest] => SHA-256=digest_key
[Accept-Encoding] => gzip
[Date] => Wed, 22 Nov 2023 16:02:27 GMT
[User-Agent] => http.rb/5.1.1 (Mastodon/4.2.1; +https://hidden_url.com/)
[Content-Length] => 334
[Connection] => close
[Host] => other_hidden_url.com
[X-Real-Port] => xxxxx
[X-Port] => 443
[X-Https] => on
[X-Real-Ip] => hidden_ip
[X-Forwarded-By] => other_hidden_ip

openssl_decrypt()函数参数:

function openssl_decrypt(
    string $data,
    string $cipher_algo,
    string $passphrase,
    int $options = 0,
    string $iv = "",
    string|null $tag = null,
    string $aad = ""
): string|false 

解密步骤

Web Push AES-GCM解密需遵循Web Push协议规范,具体实现如下:

1. 解析请求头关键参数

  • 从Crypto-Key头提取dh值(对方公钥,base64url编码)
  • 从Encryption头提取salt值(base64url编码)
  • 确认Content-Encoding为aesgcm,对应加密算法为aes-128-gcm

2. 编码转换:base64url转标准base64

Web Push使用base64url编码,需先转换为标准base64再解码为二进制:

// 工具函数:base64url转标准base64
function base64url_to_base64($base64url) {
    $base64 = strtr($base64url, '-_', '+/');
    return str_pad($base64, strlen($base64) + (4 - strlen($base64) % 4) % 4, '=');
}

// 解析salt
$salt = base64_decode(base64url_to_base64(explode('=', $_SERVER['Encryption'])[1]));
// 解析对方公钥dh
$dh_value = explode(';', $_SERVER['Crypto-Key'])[0];
$remote_public_key = base64_decode(base64url_to_base64(explode('=', $dh_value)[1]));
// 订阅时的本地私钥、公钥
$local_private_key = base64_decode(base64url_to_base64($env["vapid_private"]));
$local_public_key = base64_decode(base64url_to_base64($env["vapid_public"]));

3. 生成共享密钥与派生密钥

用ECDH算法计算共享密钥,再通过HKDF派生解密所需的密钥和IV:

// 创建ECDH上下文,使用P-256曲线
$ecdh = openssl_pkey_new([
    'curve_name' => 'prime256v1',
    'private_key_type' => OPENSSL_KEYTYPE_EC,
]);
// 导入本地私钥
openssl_pkey_import($ecdh, $local_private_key);
// 计算共享密钥
openssl_pkey_derive($ecdh, $remote_public_key, $shared_secret, 32);

// HKDF派生函数
function hkdf($salt, $ikm, $info, $length) {
    $prk = hash_hmac('sha256', $ikm, $salt, true);
    $t = '';
    $last = '';
    $i = 1;
    while (strlen($t) < $length) {
        $last = hash_hmac('sha256', $last . $info . chr($i), $prk, true);
        $t .= $last;
        $i++;
    }
    return substr($t, 0, $length);
}

// 派生加密密钥(16字节)
$encryption_key = hkdf($salt, $shared_secret, 'Content-Encoding: aesgcm' . chr(0), 16);
// 派生IV(12字节)
$iv = hkdf($salt, $shared_secret, 'Content-Encoding: nonce' . chr(0), 12);

4. 拆分Payload中的密文与Tag

Web Push AES-GCM Payload结构为:密文 + 16字节Auth Tag,需拆分处理:

$payload = file_get_contents('php://input');
$ciphertext = substr($payload, 0, -16);
$tag = substr($payload, -16);

5. 调用openssl_decrypt解密

指定OPENSSL_RAW_DATA选项(数据为二进制):

$decrypted = openssl_decrypt(
    $ciphertext,
    'aes-128-gcm',
    $encryption_key,
    OPENSSL_RAW_DATA,
    $iv,
    $tag,
    '' // Web Push中AAD字段为空
);

if ($decrypted === false) {
    die('解密失败:' . openssl_error_string());
}

// 解密后为JSON格式,可解码使用
$notification = json_decode($decrypted, true);
print_r($notification);

关键注意事项

  • 所有base64url编码必须转换为标准base64后解码,否则密钥无效
  • ECDH必须使用P-256曲线(prime256v1),这是Web Push标准曲线
  • HKDF的info参数需严格按协议指定,末尾必须加chr(0)
  • Payload必须拆分出16字节的Tag,否则解密失败

内容的提问来源于stack exchange,提问作者Marcel Bootsman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 14:40:28