使用 ECDiffieHellman P256 派生密钥

Derive Key with ECDiffieHellmanP256

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

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

转换为 .NET base64 的示例是

BOAiqZO6ucAzDlZKKhF1aLjNpU8+R2Pfsz4bQzNpV145D+agNxvLqyu5Q2tLalK2w31RpoDHE8Sipo0m2jiX4WA=

但是我 运行 生成派生的问题 Material。

var key1 = Convert.FromBase64String("<stringFromAbove>").ToList() // You can criticize my .toList inefficiencies later

// .NET doesn't like the key without these prefixes. See here
// 
// 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: The requested operation is not supported.

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

作为替代方案,如果有人可以解释如何将此 Node JS library 移植到 .NET,那也可以(我认为这有点难以实现)

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

由于这个测试,我可以确认其余代码(此处未包含)可以正常工作,所以问题肯定出在上面的代码中(如果我无法生成派生密钥 material HashAlgorithm 指定为 CngAlgorithm.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 publication定义标签为:

Label – A string that identifies the purpose for the derived keying material, which is encoded as a binary string.

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

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

多亏 让我走上了正确的轨道。

KeyDerivationFunction 正在 post 处理:https://docs.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