使用ECDiffieHellmanP256导出密钥

时间:2016-02-25 01:50:09

标签: c# elliptic-curve diffie-hellman

我正在开发一个项目,以便与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 library移植到.NET也可以工作(我认为这有点可用)

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

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

2 个答案:

答案 0 :(得分: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 publication将标签定义为:

  

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

我不确定在特定情况下应该设定的目的是什么。如果有人更好地理解这个字符串应该是什么,请发表评论。

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

感谢comment by Simon Mourier让我走上正轨。

答案 1 :(得分:-1)

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

所以也许是计算主密钥 https://tools.ietf.org/html/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