问题描述
我想使用 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 代码的值。