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

.NET中Telegram Web推送加密消息解密实现问询

解密Telegram AES-GCM加密推送消息

核心流程概述

Telegram推送遵循Web Push标准的AES-GCM加密机制,解密需完成解析请求参数、计算ECDH共享密钥、派生加密密钥/IV、AES-GCM解密四个关键环节,以下是具体实现细节:

1. 解析请求头中的加密参数

从收到的HTTP请求头中提取三个核心值:

  • Salt:从Encryption头(格式为salt=xxx)中取出值,用WebEncoders.Base64UrlDecode解码为字节数组
  • Telegram公钥:从Crypto-Key头(格式为dh=xxx)中取出值,用WebEncoders.Base64UrlDecode解码为字节数组
  • 本地Auth密钥:即你注册时生成的"rnd96257410358jh"对应的原始字节数组(无需再解码)

2. 计算ECDH共享密钥

利用你已生成的本地EC私钥,结合Telegram的公钥计算共享密钥:

// 初始化P-256曲线参数
var ecParameters = Org.BouncyCastle.Asn1.Nist.NistNamedCurves.GetByName("P-256");
var ecSpec = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H, ecParameters.GetSeed());

// 解析Telegram的公钥
var telegramPubKeyPoint = ecParameters.Curve.DecodePoint(telegramDhBytes);
var telegramPubKey = new ECPublicKeyParameters(telegramPubKeyPoint, ecSpec);

// 加载本地私钥(替换为你持久化存储的私钥字节数组)
var localPrivateKey = new ECPrivateKeyParameters(new BigInteger(1, _localPrivateKey), ecSpec);

// 计算ECDH共享密钥
var agreement = AgreementUtilities.GetBasicAgreement("ECDH");
agreement.Init(localPrivateKey);
var sharedSecret = agreement.CalculateAgreement(telegramPubKey).ToByteArrayUnsigned();

// 修正共享密钥长度为32字节(P-256曲线标准长度)
if (sharedSecret.Length != 32)
{
    var temp = new byte[32];
    Array.Copy(sharedSecret, 0, temp, 32 - sharedSecret.Length, sharedSecret.Length);
    sharedSecret = temp;
}

3. 用HKDF派生AES密钥和IV

Web Push标准使用HKDF算法派生加密所需的AES密钥和IV:

// 初始化HKDF生成器(基于HMAC-SHA256)
var hkdf = new HkdfBytesGenerator(new Sha256Digest());
hkdf.Init(new HkdfParameters(
    sharedSecret, 
    saltBytes, 
    Encoding.UTF8.GetBytes("Content-Encoding: aesgcm\0") // 固定信息字段
));

// 派生28字节数据:16字节AES密钥 + 12字节IV
var derivedBytes = new byte[28];
hkdf.GenerateBytes(derivedBytes, 0, derivedBytes.Length);

var aesKey = derivedBytes.Take(16).ToArray();
var iv = derivedBytes.Skip(16).Take(12).ToArray();

4. AES-GCM解密消息体

Telegram推送的消息体结构为16字节认证标签 + 密文内容,拆分后使用.NET原生AesGcm解密:

// 读取请求体字节数组
var encryptedData = await Request.BodyReader.ReadAsync().AsTask();
var encryptedBytes = encryptedData.Buffer.ToArray();

// 拆分标签和密文
var tag = encryptedBytes.Take(16).ToArray();
var ciphertext = encryptedBytes.Skip(16).ToArray();

// 执行解密
var plaintext = new byte[ciphertext.Length];
using var aesGcm = new AesGcm(aesKey);
aesGcm.Decrypt(iv, ciphertext, tag, plaintext);

// 解密后为JSON格式,可直接转换为字符串处理
var pushContent = Encoding.UTF8.GetString(plaintext);

完整ASP.NET Core端点示例

将以上逻辑整合到控制器中:

[ApiController]
[Route("telegrampush")]
public class TelegramPushController : ControllerBase
{
    // 从持久化存储加载本地私钥和Auth密钥(示例中需替换为实际存储逻辑)
    private readonly byte[] _localPrivateKey = /* 你的本地私钥字节数组 */;
    private readonly byte[] _authKey = Encoding.UTF8.GetBytes("rnd96257410358jh");

    [HttpPost]
    public async Task<IActionResult> ReceivePush()
    {
        // 解析请求头
        var encryptionHeader = Request.Headers["Encryption"].FirstOrDefault();
        var cryptoKeyHeader = Request.Headers["Crypto-Key"].FirstOrDefault();
        if (string.IsNullOrEmpty(encryptionHeader) || string.IsNullOrEmpty(cryptoKeyHeader))
            return BadRequest("缺失加密相关请求头");

        var saltBytes = WebEncoders.Base64UrlDecode(encryptionHeader.Split('=')[1]);
        var telegramDhBytes = WebEncoders.Base64UrlDecode(cryptoKeyHeader.Split('=')[1]);

        // 计算ECDH共享密钥
        var ecParameters = Org.BouncyCastle.Asn1.Nist.NistNamedCurves.GetByName("P-256");
        var ecSpec = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H, ecParameters.GetSeed());
        var telegramPubKeyPoint = ecParameters.Curve.DecodePoint(telegramDhBytes);
        var telegramPubKey = new ECPublicKeyParameters(telegramPubKeyPoint, ecSpec);
        var localPrivateKey = new ECPrivateKeyParameters(new BigInteger(1, _localPrivateKey), ecSpec);

        var agreement = AgreementUtilities.GetBasicAgreement("ECDH");
        agreement.Init(localPrivateKey);
        var sharedSecret = agreement.CalculateAgreement(telegramPubKey).ToByteArrayUnsigned();
        if (sharedSecret.Length != 32)
        {
            var temp = new byte[32];
            Array.Copy(sharedSecret, 0, temp, 32 - sharedSecret.Length, sharedSecret.Length);
            sharedSecret = temp;
        }

        // 派生AES密钥和IV
        var hkdf = new HkdfBytesGenerator(new Sha256Digest());
        hkdf.Init(new HkdfParameters(sharedSecret, saltBytes, Encoding.UTF8.GetBytes("Content-Encoding: aesgcm\0")));
        var derivedBytes = new byte[28];
        hkdf.GenerateBytes(derivedBytes, 0, derivedBytes.Length);
        var aesKey = derivedBytes.Take(16).ToArray();
        var iv = derivedBytes.Skip(16).Take(12).ToArray();

        // 解密消息体
        var encryptedData = await Request.BodyReader.ReadAsync().AsTask();
        var encryptedBytes = encryptedData.Buffer.ToArray();
        var tag = encryptedBytes.Take(16).ToArray();
        var ciphertext = encryptedBytes.Skip(16).ToArray();

        var plaintext = new byte[ciphertext.Length];
        using var aesGcm = new AesGcm(aesKey);
        aesGcm.Decrypt(iv, ciphertext, tag, plaintext);

        // 处理解密后的业务逻辑(示例:输出日志)
        var messageContent = Encoding.UTF8.GetString(plaintext);
        Console.WriteLine("解密后的推送内容:" + messageContent);

        return Ok();
    }
}

注意事项

  • 本地私钥需安全持久化存储,禁止硬编码到代码中
  • 需要安装BouncyCastle.Cryptography NuGet包以支持ECDH和HKDF操作
  • 解密后的内容为JSON格式,包含payload字段,对应实际的Telegram通知内容

内容的提问来源于stack exchange,提问作者Hamid Z

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 23:10:22