网络密码学为 ECDH 的输出实现 HKDF

问题描述

我想使用 HKDF 作为关键推导函数来实现椭圆曲线 diffie hellman。我在前端使用 python 后端和(香草)javascript。我在后端使用 python cryptography 库,在前端使用 Web Crypto api 作为加密库。我在双方创建了 ECDH 密钥对并交换了公共密钥。现在我正在尝试使用交换的公钥和私钥以及 HKDF 算法创建 AES 共享密钥。我可以在 python 后端执行此操作(我按照 this example 获取 python 代码):

descending

但我不知道如何使用网络加密 API 来做到这一点。这是我的尝试:(但不起作用)

Get-ImmediatePhotos | sort-object @{ ascending=$true; expression={$_.Tag.DateTime.Year} }

如何使用 Web 加密 API 在前端创建共享 AES 密钥和 HKDF(与 python 相同)?

解决方法

referenced Python code 使用 P-384(又名 secp384r1)作为椭圆曲线。这与支持三种曲线 P-256(又名 secp256r1)、P-384 和 P-521(又名 secp521r1)的 WebCrypto API 兼容,请参阅 EcKeyImportParams

以下 WebCrypto 代码使用 ECDH 生成共享密钥,并使用 HKDF 从共享密钥派生 AES 密钥。详细情况如下:

  • 为了允许将派生密钥与引用的 Python 代码的密钥进行比较,应用了预定义的 EC 密钥。私钥导入为 PKCS#8,公钥导入为 X.509/SPKI。请注意,由于 Firefox 与 EC 密钥导入相关的错误,以下脚本无法在 Firefox 浏览器中运行。
  • 导入后,使用 deriveBits()(而不是 deriveKey())通过 ECDH 创建共享密钥。
  • 使用 importKey() 导入共享密钥,然后使用 HKDF 派生出 AES 密钥,再次使用 deriveBits()

(async () => {
    await deriveKey();
})();

async function deriveKey() {

    //
    // Key import
    //
    var server_x509 =  `-----BEGIN PUBLIC KEY-----
                        MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEd7fej9GYVI7Vt6x5B6XhruHvmE/rnzIj
                        HmpxP8PKfnfWgrJbyG2cgQc3mf9uusqk1FKImA86rx2+avK8+7xIK9wxuF3x2KQq
                        nxNp7bUBit3phyhp72Nt/QLXmZHcDKXL
                        -----END PUBLIC KEY-----`;
    var client_pkcs8 = `-----BEGIN PRIVATE KEY-----
                        MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBjr4EGktNtx+3xErsC
                        MzldruzzfAEEO8Oth1/3b8sNfrqRsAgMnB/oVy024I+15wOhZANiAASbTF7LLedW
                        dik6nH8JX8WeU0R1ZRlqq0EAZ/t+UrFcSOaVJSOx5jMJ3nrqwuk2DnobDqFwXH6t
                        ZMsZHh4NFZ+bCVeHJRqy4SCZvQFB/xcksF29p1v14XHYI/XKMGyLLx4=
                        -----END PRIVATE KEY-----`;

    var client_private_key = b64_to_ab(client_pkcs8.replace('-----BEGIN PRIVATE KEY-----','').replace('-----END PRIVATE KEY-----',''));
    var server_public_key = b64_to_ab(server_x509.replace('-----BEGIN PUBLIC KEY-----','').replace('-----END PUBLIC KEY-----',''));
    var privateKey = await window.crypto.subtle.importKey( 
        'pkcs8',client_private_key,{ name: "ECDH",namedCurve: "P-384" },true,["deriveKey","deriveBits"] 
    );
    var publicKey = await window.crypto.subtle.importKey(
        "spki",server_public_key,[] 
    );
    
    //
    // Determine shared secret
    //
    var sharedSecret = await window.crypto.subtle.deriveBits(
        { name: "ECDH",namedCurve: "P-384",public: publicKey },privateKey,384 
    );
    console.log("Shared secret:\n",ab_to_b64(sharedSecret).replace(/(.{48})/g,'$1\n'));
    
    //
    // Derive key from shared secret via HKDF
    //
    var sharedSecretKey = await window.crypto.subtle.importKey(
        "raw",sharedSecret,{ name: "HKDF" },false,"deriveBits"]
    );
    var derived_key = await crypto.subtle.deriveBits(
        { name: "HKDF",hash: "SHA-256",salt: new Uint8Array([]),info: new Uint8Array([]) },sharedSecretKey,256
    );
    console.log("Derived key:\n",ab_to_b64(derived_key).replace(/(.{48})/g,'$1\n'))
}; 

function b64_to_ab(base64_string){
    return Uint8Array.from(atob(base64_string),c => c.charCodeAt(0));
}

function ab_to_b64(arrayBuffer){
    return btoa(String.fromCharCode.apply(null,new Uint8Array(arrayBuffer)));
}

具有以下输出:

Shared secret:
 xbU6oDHMTYj3O71liM5KEJof3/0P4HlHJ28k7qtdqU/36llCizIlOWXtj8v+IngF
Derived key:
 Yh0FkhqrT9XDQqIiSrGv5YmBjCSj9jhR5fF6HusbN1Q=

为了将生成的 AES 密钥与引用的 Python 代码的密钥进行比较,使用以下 Python 代码,该代码基于引用的代码,但应用预定义的密钥,这些密钥与 WebCrypto 代码中使用的密钥对应。由于这里的重点是密钥派生,因此不考虑 AES 部分:

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
import base64

def deriveKey():

  server_pkcs8 = b'''-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBReGpDVmoVTzxNbJx6
aL4L9z1EdB91eonAmAw7mKDocLfCJITXZPUAmM46c6AipTmhZANiAAR3t96P0ZhU
jtW3rHkHpeGu4e+YT+ufMiMeanE/w8p+d9aCslvIbZyBBzeZ/266yqTUUoiYDzqv
Hb5q8rz7vEgr3DG4XfHYpCqfE2nttQGK3emHKGnvY239AteZkdwMpcs=
-----END PRIVATE KEY-----'''

  client_x509 = b'''-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEm0xeyy3nVnYpOpx/CV/FnlNEdWUZaqtB
AGf7flKxXEjmlSUjseYzCd566sLpNg56Gw6hcFx+rWTLGR4eDRWfmwlXhyUasuEg
mb0BQf8XJLBdvadb9eFx2CP1yjBsiy8e
-----END PUBLIC KEY-----'''

  client_public_key = serialization.load_pem_public_key(client_x509)
  server_private_key = serialization.load_pem_private_key(server_pkcs8,password=None)
  shared_secret = server_private_key.exchange(ec.ECDH(),client_public_key)
  print('Shared secret: ' + base64.b64encode(shared_secret).decode('utf8')) # Shared secret: xbU6oDHMTYj3O71liM5KEJof3/0P4HlHJ28k7qtdqU/36llCizIlOWXtj8v+IngF

  derived_key = HKDF(
    algorithm=hashes.SHA256(),length=32,salt=None,info=None,).derive(shared_secret) 
  print('Derived key:   ' + base64.b64encode(derived_key).decode('utf8')) # Derived key:   Yh0FkhqrT9XDQqIiSrGv5YmBjCSj9jhR5fF6HusbN1Q=

deriveKey()

具有以下输出:

Shared secret: xbU6oDHMTYj3O71liM5KEJof3/0P4HlHJ28k7qtdqU/36llCizIlOWXtj8v+IngF
Derived key:   Yh0FkhqrT9XDQqIiSrGv5YmBjCSj9jhR5fF6HusbN1Q=

对应于 WebCrypto 代码的值。