.NET下WebPush加密推送Payload解密实现方法(附参考加密代码)
基于现有加密逻辑的WebPush Payload解密实现
参数匹配规则
解密流程为现有加密逻辑的严格逆向推导,需保证所有入参与服务端加密时的取值严格对应:
serverKey:服务端加密时生成的临时EC公钥(未压缩格式)salt:服务端加密时随机生成的16字节盐值clientPrivateKey:客户端订阅时生成的EC密钥对中的私钥,与服务端加密时使用的客户端公钥配对clientAuthSecret:客户端订阅时生成的共享认证密钥clientPublicKey:客户端订阅时生成的EC公钥encryptedData:服务端返回的包含GCM认证标签的完整密文
完整解密代码实现
public static byte[] DecryptData(byte[] serverKey, byte[] salt, byte[] clientPrivateKey, byte[] clientAuthSecret, byte[] clientPublicKey, byte[] encryptedData) { // 初始化P-256曲线参数,与加密时保持一致 X9ECParameters curve = ECNamedCurveTable.GetByName("prime256v1"); ECDomainParameters spec = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed()); // 完成ECDH密钥协商,得到与加密端一致的IKM ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(new BigInteger(1, clientPrivateKey), spec); ECPublicKeyParameters serverPublicParams = new ECPublicKeyParameters(spec.Curve.DecodePoint(serverKey), spec); ECDHBasicAgreement agreement = new ECDHBasicAgreement(); agreement.Init(privateKeyParams); BigInteger ikm = agreement.CalculateAgreement(serverPublicParams); // 生成与加密端一致的PRK byte[] prk = GenerateHKDF(clientAuthSecret, ikm.ToByteArrayUnsigned(), Encoding.UTF8.GetBytes("Content-Encoding: auth\0"), 32); // 生成与加密端一致的内容加密密钥(CEK) byte[] cek = GenerateHKDF(salt, prk, CreateInfoChunk("aesgcm", clientPublicKey, serverKey), 16); // 生成与加密端一致的Nonce byte[] nonce = GenerateHKDF(salt, prk, CreateInfoChunk("nonce", clientPublicKey, serverKey), 12); // 初始化解密器 IBufferedCipher cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding"); cipher.Init(false, new AeadParameters(new KeyParameter(cek), 128, nonce)); byte[] plainText = new byte[cipher.GetOutputSize(encryptedData.Length)]; int plainLength = cipher.DoFinal(encryptedData, 0, encryptedData.Length, plainText, 0); // 解析前置padding长度,剔除padding得到原始业务数据 byte[] paddingBytes = plainText.Take(2).ToArray(); if (BitConverter.IsLittleEndian) Array.Reverse(paddingBytes); ushort paddingLength = BitConverter.ToUInt16(paddingBytes, 0); int dataStartIndex = 2 + paddingLength; return plainText.Take(plainLength).Skip(dataStartIndex).ToArray(); }
注:上述代码可直接复用你现有实现中的
GenerateHKDF、CreateInfoChunk、ConvertInt三个工具方法,无需重复实现。
实现注意事项
- 所有EC公钥需使用未压缩格式(0x04开头,固定65字节长度),压缩格式会导致曲线点解码失败
- GCM模式下的128位认证标签默认附带在密文末尾,无需单独拆分传给解密器
- 2字节的padding长度为大端编码,解析时需做字节序反转
内容的提问来源于stack exchange,提问作者Yosif Chumpov
相关产品推荐
相关产品推荐

