使用 ECDiffieHellmanP256 派生密钥

本文关键字:密钥 派生 ECDiffieHellmanP256 使用 | 更新日期: 2023-09-27 18:36:15

我正在做一个项目,与Firefox中存在的新的Push API集成,并且正在开发为W3C标准。

其中一部分是加密数据。服务器将收到 Diffie Hellman P256 曲线(使用 var key = subscription.getKey('p256dh'); 在 JS 中生成)

转换为 .NET base64 时的一个示例是

BOAiqZO6ucAzDlZKKhF1aLjNpU8+R2Pfsz4bQzNpV145D+agNxvLqyu5Q2tLalK2w31RpoDHE8Sipo0m2jiX4WA=

但是,我在生成派生材料时遇到了问题。

var key1 = Convert.FromBase64String("<stringFromAbove>").ToList() // You can criticize my .toList inefficiencies later
// .NET doesn't like the key without these prefixes. See here
// http://stackoverflow.com/questions/24251336/import-a-public-key-from-somewhere-else-to-cngkey
// I know the bytes don't match that post, but that is because the key type is different between their example and mine.
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
key1.RemoveAt(0);
key1 = keyType.Concat(keyLength).Concat(key1).ToList();
ECDiffieHellmanCng a = new ECDiffieHellmanCng();
a.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
// If I set this as CngAlgorithm.Sha256 it works, but that's not what Firefox gives me.
a.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256; 
a.KeySize = 256; // It complains if I don't add this since keys are different lengths.
// Now time to actually import the key
CngKey k = CngKey.Import(key1.ToArray(), CngKeyBlobFormat.EccPublicBlob); // Works successfully
byte[] derivedMaterial = a.DeriveKeyMaterial(k); // Exception Here

System.Security.Cryptography.CryptographicException:不支持请求的操作。

我有什么不理解正确(或者更可悲的是,在 windows/.NET 中没有正确(或根本没有)实现什么)?

作为替代方案,如果有人可以解释如何将这个 Node JS 库移植到 .NET 也可以(我认为这有点范围)

更新
我需要继续解决其余的问题,而不是被加密所阻碍,所以我使用了 Node.JS 包装器来允许在 .NET 端进行进一步开发。节点代码只是生成本地公钥和共享密钥,并将这些值返回给我。我仍然需要在没有节点包装器的情况下让它工作。

由于这个测试,我可以确认其余代码(此处未包含)有效,因此问题肯定在于上面的代码(如果将 HashAlgorithm 指定为CngAlgorithm.ECDiffieHellmanP256,我无法生成派生的密钥材料

使用 ECDiffieHellmanP256 派生密钥

此解决方案仅在 Windows 10 64 位上确认有效。已确认在 Windows 8.1 64 位上不起作用,并且未经其他平台测试。

问题是ECDiffieHellmanP256不是哈希算法,但您指定使用哈希键派生函数。您的KeyDerivationFunction应设置为 ECDiffieHellmanKeyDerivationFunction.Tls ,并且您需要指定 KDF 的种子和标签。

您的固定代码如下所示:

var key1 = Convert.FromBase64String("BOAiqZO6ucAzDlZKKhF1aLjNpU8+R2Pfsz4bQzNpV145D+agNxvLqyu5Q2tLalK2w31RpoDHE8Sipo0m2jiX4WA=").ToList();
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
key1.RemoveAt(0);
key1 = keyType.Concat(keyLength).Concat(key1).ToList();
ECDiffieHellmanCng a = new ECDiffieHellmanCng();
a.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Tls;
byte[] label = new byte[32];
string labelStr = "The purpose";
Encoding.ASCII.GetBytes(labelStr, 0, labelStr.Length, label, 0);
a.Label = label;
byte[] seed = new byte[32];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(seed);
a.Seed = seed;
a.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256;
a.KeySize = 256;
CngKey k = CngKey.Import(key1.ToArray(), CngKeyBlobFormat.EccPublicBlob);
byte[] derivedMaterial = a.DeriveKeyMaterial(k);

请注意,我为 a.Label 属性设置了一个无意义的值。

NIST SP 800-108 出版物将标签定义为:

标签 – 标识派生密钥材料的用途的字符串,该密钥材料编码为二进制字符串。

我不确定在您的特定上下文中应该将目的设置为什么。如果有人更好地了解这个字符串应该是什么,请发表评论。

另请注意,如果要重复调用此函数,则可能应该保留RNGCryptoServiceProvider的持久副本并使用它。

感谢Simon Mourier的评论,它让我走上了正确的轨道。

KeyDerivationFunction 是后处理: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmancng?view=netframework-4.7#remarks

所以也许它是计算主密钥 https://www.rfc-editor.org/rfc/rfc5246#section-8.1

ECDiffieHellmanCng.Label = Encoding.ASCII.GetBytes("master secret");
ECDiffieHellmanCng.Seed = clientRandom.Concat(serverRandom).ToArray();
//there is also a function like this:
//ECDiffieHellmanCng.DeriveKeyTls(ECDiffieHellmanPublicKey otherPartyPublicKey, byte[] prfLabel, byte[] prfSeed)

所以LabelSeed必须在PRF中使用,我认为这有点奇怪,为什么ECDiffieHellman无论如何都要做PRF

抱歉,我应该在接受的答案中添加评论,但you must have 50 reputation to comment