使用ECDiffieHellmanP256派生密钥

15
我正在处理一个与Firefox的新Push API集成的项目,该API正在作为W3C标准开发中。
其中一部分是加密数据。服务器将接收Diffie Hellman P256 Curve(使用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
// https://dev59.com/uWAf5IYBdhLWcg3w0VUf
// 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端进行进一步开发。节点代码仅生成本地公共密钥和共享密钥,并将这些值返回给我。我仍然需要在没有Node包装器的情况下使其工作。 由于这个测试,我可以确认代码的其余部分(未在此处包括)有效,因此问题肯定出现在上面的代码中(以及我的无法在指定HashAlgorithm为CngAlgorithm.ECDiffieHellmanP256时生成派生密钥材料的能力)。

运行在 Windows 上。.NET Framework 4.6。 - Dan Drews
1
@SimonMourier 我得到的错误是:“附加信息:DER长度超过4个字节:38” - Dan Drews
1
但是,ECDiffieHellmanP256不是哈希算法,对吧?你说的“这不是Firefox给我的”是什么意思? - Simon Mourier
问题可能出现在你的样本代码之后。是的,你需要展示一下当你从DeriveKeyMaterial得到那个密钥之后你做了什么,同时推送服务器在它这边对密钥做了什么,这样你就能进行比较。这可能是一个小问题,类似于你使用的keyType+keyLength修复的问题。 - Simon Mourier
好的,那就更加复杂了。规格实际上在这里:https://tools.ietf.org/html/draft-ietf-webpush-encryption-01#section-3 和这里:https://tools.ietf.org/html/draft-thomson-http-encryption-01#section-3.2。你提供的链接是这种实现的一个例子,但它依赖于多个node.js包(有一个要使用的盐等)。你的代码只是整个过程中非常小的一部分。在.NET中做到这一点肯定是可能的,但还有工作要做。 - Simon Mourier
显示剩余8条评论
2个回答

5
注意:此解决方案仅在 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 的评论,让我找对了方向。

@DanDrews 这很奇怪,在我的.NET 4.6上它运行得很好。除了你提到的,当你的初始“key1”无效时,你可能会遇到这个异常。通过在我的示例中暂时硬编码字符串来确保这不是问题(我从你的示例中获取)。 - Gediminas Masaitis
刚在一个新项目中尝试了一下,直接复制/粘贴了你的代码,并将框架设置为.NET 4.6。我正在运行Windows 8.1,你呢? - Dan Drews
如果在Windows 10上能够正常运行,我会立即授予您赏金。我们很快将升级所有开发机器到Windows 10,并且我们的服务器是最新的服务器操作系统。目前我们有NODE包装器,希望能帮助到其他人。 - Dan Drews
1
@DanDrews 不要着急,等一两天。尽管我很想“赚钱”,但完全有可能其他人或者我自己会提出更好的跨平台解决方案。现在正确的做法是点赞,因为它确实起作用了。只是如果长时间没有其他进展,不要忘记它 :) - Gediminas Masaitis
1
@DanDrews 我认为您误解了赏金自动授予的工作方式。有一些标准(在这种情况下,我的回答不符合它们),在任何情况下都不会全部自动授予赏金。更多详情请见此处 - Gediminas Masaitis
显示剩余7条评论

-1

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)

因此,在 PRF 中必须使用 LabelSeed,我认为这有点奇怪,为什么 ECDiffieHellman 要用 PRF 呢

抱歉我应该在接受的答案中添加评论,但是你必须拥有50个声望才能发表评论


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接