Web密码学实现HKDF以输出ECDH

Tah*_*lil 5 javascript encryption cryptography webcrypto-api

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

def encrypt(public_key, secret):
global loaded_public_key
loaded_public_key = public_key
shared_key = server_private_key.exchange(ec.ECDH(), public_key)
IV = bytes("ddfbccae-b4c4-11", encoding="utf-8")
derived_key = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=None,
        info=None,
).derive(shared_key)
aes = Cipher(algorithms.AES(derived_key), modes.GCM(IV))
encryptor = aes.encryptor()
padder = padding.PKCS7(128).padder()
padded_data = padder.update(secret.encode()) + padder.finalize()
return encryptor.update(secret.encode()) + encryptor.finalize()
Run Code Online (Sandbox Code Playgroud)

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

async function deriveSecretKey(privateKey, publicKey) {
  let sharedKey = await window.crypto.subtle.deriveKey(
    {
      name: "ECDH",
      public: publicKey
    },
    privateKey,
    {
      name: "AES-GCM",
      length: 256
    },
    false,
    ["encrypt", "decrypt"]
  );
  return window.crypto.subtle.deriveKey(
    {
      name: "HKDF",
      hash:  {name: "SHA-256"} ,
      salt: new ArrayBuffer(0),
      info: new ArrayBuffer(0)
    },
    sharedKey,
    {
      name: "AES-GCM",
      length: 256
    },
    false,
    ["encrypt", "decrypt"]
  );
}
Run Code Online (Sandbox Code Playgroud)

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

Top*_*aco 8

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

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

  • 为了允许将派生密钥与引用的 Python 代码的密钥进行比较,将应用预定义的 EC 密钥。私钥导入为 PKCS#8,公钥导入为 X.509/SPKI。请注意,由于 Firefox 存在有关 EC 密钥导入的错误,因此以下脚本无法在 Firefox 浏览器中运行。
  • deriveBits()导入后,使用(而不是)通过 ECDH 创建共享密钥deriveKey()
  • 导入共享密钥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,
        { name: "ECDH", namedCurve: "P-384" },
        true, 
        [] 
    );
    
    //
    // 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, 
        ["deriveKey", "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)));
}
Run Code Online (Sandbox Code Playgroud)

输出如下:

Shared secret:
 xbU6oDHMTYj3O71liM5KEJof3/0P4HlHJ28k7qtdqU/36llCizIlOWXtj8v+IngF
Derived key:
 Yh0FkhqrT9XDQqIiSrGv5YmBjCSj9jhR5fF6HusbN1Q=
Run Code Online (Sandbox Code Playgroud)

为了将生成的 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()
Run Code Online (Sandbox Code Playgroud)

输出如下:

Shared secret: xbU6oDHMTYj3O71liM5KEJof3/0P4HlHJ28k7qtdqU/36llCizIlOWXtj8v+IngF
Derived key:   Yh0FkhqrT9XDQqIiSrGv5YmBjCSj9jhR5fF6HusbN1Q=
Run Code Online (Sandbox Code Playgroud)

它对应于 WebCrypto 代码的值。