javascript - .NET Core 5.0 到 Javascript DFH key 交换不起作用

标签 javascript c# diffie-hellman

我们正在尝试使用 JS 获取基于浏览器的应用程序,以使用椭圆曲线 Diffie Hellman 与 .Net Core 5.0 服务器交换 key 。 我们的应用程序需要在两端共享 secret 以进行我们进行的某些特定处理(不是加密,而是我们自己的过程),并且我们希望导出该 secret 而不是出于安全目的传输。 我们四处寻找合适的解决方案并将各种解决方案拼凑在一起,我们想出了这个,但它失败了,C# 端出现异常。 基本上,我们的伪代码如下(到目前为止我们只进行了第 3 步):

  1. 客户端 (Bob) 使用 window.crypto.subtle 库在客户端中生成 ECDH/P-256 key 对。
  2. Bob 从这对导出公钥并向服务器 (Alice) 发出 GET 以获取她的公钥(将 bobPublicKeyB64 作为查询参数传递)。
  3. Alice 接收传入请求并调用 C# 方法以使用 Bob 的公钥创建共享 key 。
  4. Alice 将此共享 secret 存储在内存缓存中,并将她的公钥返回给 Bob。
  5. 然后 Bob 使用 Alice 的公钥获取他自己的共享 secret 并将其存储在浏览器的“ session 存储”中。

将 Bob 的公钥发送给 Alice 的代码在这里没有显示,它是一个标准的 XMLHttpRequest。然而,它确实有效,并且服务器被调用,正如 C# 代码中的失败所证明的那样。我们已经验证 bobPublicKeyB64 在 Bob 发送时和 Alice 收到时完全相同。

一旦成功,我们将加强两端的存储方法,但首先我们需要让交换正常工作。 Alice (C#) 代码块中的注释显示了它失败的地方。 getDerivedKey 方法(目前已注释掉)来自这篇文章 - ECDH nodejs and C# key exchange 并且它因不同的异常而失败(我确信这两次失败都是由于 JS 库和 .Net 实现之间的某种不匹配,但我们无法处理它)。 非常感谢任何帮助 - 基本问题是“当 Bob 是 JS 而 Alice 是 C# 时,我们如何让 Bob 和 Alice 说话?

以下是Bob上的JS代码:

async function setUpDFHKeys() {
let bobPublicKeyB64;
let bobPrivateKeyB64;
try {

    const bobKey = await window.crypto.subtle.generateKey(
        { name: 'ECDH', namedCurve: 'P-256' },
        true,
        ["deriveKey"]
    );
            
    const publicKeyData = await window.crypto.subtle.exportKey("raw", bobKey.publicKey);
    const publicKeyBytes = new Uint8Array(publicKeyData);
    const publicKeyB64 = btoa(publicKeyBytes);        
    bobPublicKeyB64 = publicKeyB64;

    const privateKeyData = await window.crypto.subtle.exportKey("pkcs8", bobKey.privateKey);
    const privateKeyBytes = new Uint8Array(privateKeyData);
    const privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
    bobPrivateKeyB64 = privateKeyB64;
}
catch (error) {
    console.log("Could not setup DFH Keys " + error);
}};

以下是 Alice 上的 C# 代码:

  private string WorkWithJSPublicKey(string bobPublicKeyB64, out string alicePublicKey)
    {
        try
        {
            //
            // Alice is this server.
            // Bob is a browser that uses the window.crypto.subtle.generateKey with 'ECDH' and 'P-256' as parameters
            // and window.crypto.subtle.exportKey of the key.publicKey in "raw" format (this is then converted to a B64 string using btoa)
            //
            using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
            {
                //
                // Get the public key info from Bob and convert to B64 string to return to Alice
                //
                Span<byte> exported = new byte[alice.KeySize];
                int len = 0;
                alice.TryExportSubjectPublicKeyInfo(exported, out len);
                alicePublicKey = Convert.ToBase64String(exported.Slice(0, len));
                //
                // Get Alice's private key to use to generate a shared secret
                //
                byte[] alicePrivateKey = alice.ExportECPrivateKey();
                //
                // Import Bob's public key after converting it to bytes
                //
                var bobPubKeyBytes = Convert.FromBase64String(bobPublicKeyB64);
                //
                // TRY THIS... (Bombs with "The specified curve 'nistP256' or its parameters are not valid for this platform").
                //
                // getDerivedKey(bobPubKeyBytes, alice);
                //
                // This throws exception ("The provided data is tagged with 'Universal' class value '20', but it should have been 'Universal' class value '16'.")
                //
                alice.ImportSubjectPublicKeyInfo(bobPubKeyBytes, out len);
                //
                // Once Alice knows about Bob, create a shared secret and return it.
                //
                byte[] sharedSecret = alice.DeriveKeyMaterial(alice.PublicKey);
                return Convert.ToBase64String(sharedSecret);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, ex.Message);
            alicePublicKey = string.Empty;
            return string.Empty;
        }
    }

getDerivedKey 的代码(借自 ECDH nodejs and C# key exchange)如下所示:

static byte[] getDerivedKey(byte[] key1, ECDiffieHellman alice)
    {
        byte[] keyX = new byte[key1.Length / 2];
        byte[] keyY = new byte[keyX.Length];
        Buffer.BlockCopy(key1, 1, keyX, 0, keyX.Length);
        Buffer.BlockCopy(key1, 1 + keyX.Length, keyY, 0, keyY.Length);
        ECParameters parameters = new ECParameters
        {
            Curve = ECCurve.NamedCurves.nistP256,
            Q = {
                    X = keyX,
                    Y = keyY,
                },
        };
        byte[] derivedKey;
        using (ECDiffieHellman bob = ECDiffieHellman.Create(parameters))
        using (ECDiffieHellmanPublicKey bobPublic = bob.PublicKey)
        {
            return derivedKey = alice.DeriveKeyFromHash(bobPublic, HashAlgorithmName.SHA256);
        }
    }

最佳答案

如果 Web Crypto 端的公钥以 X.509/SPKI 格式而不是原始 key 导出,则解决方案会更简单,因为 .NET 5 有专用的导入方法,ImportSubjectPublicKeyInfo() , 对于这种格式。此外,这与 C# 代码一致,其中公钥也以 X.509/SPKI 格式导出。在以下示例中,Web Crypto 代码以 X.509/SPKI 格式导出公钥。

第 1 步 - Web 加密端 (Bob):生成 EC key 对

以下 Web Crypto 代码创建一个 ECDH key 对并以 X.509/SPKI 格式导出公钥(以及以 PKCS8 格式导出私钥)。请注意,由于错误,以 PKCS8 格式导出在 Firefox 下不起作用,另请参阅 here :

setUpDFHKeys().then(() => {});

async function setUpDFHKeys() {
    var bobKey = await window.crypto.subtle.generateKey(
        { name: 'ECDH', namedCurve: 'P-256' },
        true,
        ["deriveKey"]
    );
    var publicKeyData = await window.crypto.subtle.exportKey("spki", bobKey.publicKey);
    var publicKeyBytes = new Uint8Array(publicKeyData);
    var publicKeyB64 = btoa(String.fromCharCode.apply(null, publicKeyBytes));        
    console.log("Bob's public: \n" + publicKeyB64.replace(/(.{56})/g,'$1\n'));
    var privateKeyData = await window.crypto.subtle.exportKey("pkcs8", bobKey.privateKey);
    var privateKeyBytes = new Uint8Array(privateKeyData);
    var privateKeyB64 = btoa(String.fromCharCode.apply(null, privateKeyBytes));
    console.log("Bob's private:\n" + privateKeyB64.replace(/(.{56})/g,'$1\n'));
};

一个可能的输出是下面的 key 对,在后续类(class)中作为Web Crypto端的 key 对使用:

Bob's public:  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2X9cW7P2g4Db3BEgfjs8lwpgukPY4Qg3mcLwVJW+WwA7lbiz+N1MIL3y+JumBF1qIdyx24r5+Sr4c4iYsTWh2w== 
Bob's private: MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg72fE/+7WX5aKAMiy8kTkCTVeGR8oOlKuoQ8iXTQWmxGhRANCAATZf1xbs/aDgNvcESB+OzyXCmC6Q9jhCDeZwvBUlb5bADuVuLP43UwgvfL4m6YEXWoh3LHbivn5KvhziJixNaHb

第 2 步 - C# 端 (Alice):从 Web Crypto 端导入公钥并生成共享 key

以下 C# 代码生成 ECDH key 对,导出 X.509/SPKI 格式的公钥(和 PKCS8 格式的私钥)并确定共享 key 。为获取共享 key ,使用 ImportSubjectPublicKeyInfo() 导入 X.509/SPKI 格式的 Web Crypto 端公钥。

string alicePublicKey;
string bobPublicKeyB64 = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2X9cW7P2g4Db3BEgfjs8lwpgukPY4Qg3mcLwVJW+WwA7lbiz+N1MIL3y+JumBF1qIdyx24r5+Sr4c4iYsTWh2w==";
string sharedSecret = WorkWithJSPublicKey(bobPublicKeyB64, out alicePublicKey);
Console.WriteLine("Alice's shared secret: " + sharedSecret);

private static string WorkWithJSPublicKey(string bobPublicKeyB64, out string alicePublicKey)
{
    alicePublicKey = null;
    using (ECDiffieHellman alice = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256))
    {
        alicePublicKey = Convert.ToBase64String(alice.ExportSubjectPublicKeyInfo());
        Console.WriteLine("Alice's public:        " + alicePublicKey);
        Console.WriteLine("Alice's private:       " + Convert.ToBase64String(alice.ExportPkcs8PrivateKey()));
        ECDiffieHellman bob = ECDiffieHellman.Create();
        bob.ImportSubjectPublicKeyInfo(Convert.FromBase64String(bobPublicKeyB64), out _);
        byte[] sharedSecret = alice.DeriveKeyMaterial(bob.PublicKey);
        return Convert.ToBase64String(sharedSecret);
    }           
}

可能的输出是以下 key 对和共享 secret 。该 key 对作为后续类(class)中C#端的 key 对使用:

Alice's public:        MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJVUW57L2QeswZhnIp5gjMSiHhqyOVTsPUq2QwHv+R4jQetMQ8JDT+3VQyP/dPpskUhzDd3lKxdRBaiZrWby+VQ==
Alice's private:       MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbho81UNFdNwULs7IoWk1wSy2PP9soSlt4/bveAtoPBOhRANCAAQlVRbnsvZB6zBmGcinmCMxKIeGrI5VOw9SrZDAe/5HiNB60xDwkNP7dVDI/90+myRSHMN3eUrF1EFqJmtZvL5V
Alice's shared secret: hayYCAA23oC98d1SxhFpfiYgY5DVElmEno4851HtgKM=
    

第 3 步 - Web 加密端 (Bob):从 C# 端导入公钥并生成共享 key

以下 Web Crypto 代码创建共享 key 。为此,必须导入 C# 端的公钥。请注意(类似于导出)PKCS8 格式的导入在 Firefox 下不起作用,这是由于一个错误:

getSharedSecret().then(() => {});

async function getSharedSecret() {      
    var bobPrivateKeyB64 = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg72fE/+7WX5aKAMiy8kTkCTVeGR8oOlKuoQ8iXTQWmxGhRANCAATZf1xbs/aDgNvcESB+OzyXCmC6Q9jhCDeZwvBUlb5bADuVuLP43UwgvfL4m6YEXWoh3LHbivn5KvhziJixNaHb';
    var alicePublicKeyB64 = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJVUW57L2QeswZhnIp5gjMSiHhqyOVTsPUq2QwHv+R4jQetMQ8JDT+3VQyP/dPpskUhzDd3lKxdRBaiZrWby+VQ==';     
    var privateKey = await window.crypto.subtle.importKey(
        "pkcs8", 
        new Uint8Array(_base64ToArrayBuffer(bobPrivateKeyB64)),
        { name: "ECDH", namedCurve: "P-256" },
        true, 
        ["deriveKey", "deriveBits"] 
    );
    var publicKey = await window.crypto.subtle.importKey(
        "spki", 
        new Uint8Array(_base64ToArrayBuffer(alicePublicKeyB64)),
       { name: "ECDH", namedCurve: "P-256"},
       true, 
       [] 
    );
    var sharedSecret = await window.crypto.subtle.deriveBits(
        { name: "ECDH", namedCurve: "P-256", public: publicKey },
        privateKey, 
       256 
    );
    var sharedSecretHash = await crypto.subtle.digest('SHA-256', sharedSecret);
    var sharedSecretHashB64 = btoa(String.fromCharCode.apply(null, new Uint8Array(sharedSecretHash)));
    console.log("Bob's shared secret: " + sharedSecretHashB64.replace(/(.{64})/g,'$1\n'));
};

// from https://stackoverflow.com/a/21797381/9014097
function _base64ToArrayBuffer(base64) {
    var binary_string = window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}

在 Web Crypto 方面,这导致共享 secret :

Bob's shared secret: hayYCAA23oC98d1SxhFpfiYgY5DVElmEno4851HtgKM=

按照C#端。


请注意,C# 端的DeriveKeyMaterial() 返回的不是实际的共享 key S,而是共享 key 的SHA-256 哈希值H(S)。由于哈希不可逆,因此无法确定实际的共享 secret 。因此,唯一的选择是通过使用 SHA-256 的显式哈希在 Web Crypto 端创建 H(S),另请参见 here .

关于javascript - .NET Core 5.0 到 Javascript DFH key 交换不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65726058/

相关文章:

java - DHGEX在Java 8下使用2048位 key 失败,但是在1024位 key 下成功

c - 如何使用 openssl lib c 语言在算法 Diffie Hellman 中计算 2 个用户的共享 secret ?

javascript - 如何使用 React 和 Typescript 处理 div 上的点击事件

javascript - Backbone 路由器

javascript - 在查询生成器 Jquery 中获取 SQL 规则时,“IN”运算符无法正常工作

c# - DLL hell 解析在 CLR 中未按预期工作

javascript - 无法在 dom 元素中看到 append 文本

c# - 参数可以通用访问吗?

c# - 使用单个接口(interface)注册多个实现

Javascript Web Crypto - 无法导入 ECDH P-256 公钥