【问题标题】:Using WebCrypto to generate ECDH key from PBKDF2使用 WebCrypto 从 PBKDF2 生成 ECDH 密钥
【发布时间】:2019-05-03 02:30:05
【问题描述】:

警告 以下内容不作为将密码转换为 ECDH 密钥的认可。从高熵、加密安全的 PRNG 创建 ECDH 密钥。

我想获取一个秘密并从中生成一个 ECDH 公钥/私钥。

在浏览器中,通常的方法是使用 PBKDF2(或其他确定性字节)在 WebCrypto 中生成 ECDH 公钥/私钥对。

以下示例代码应该这样做,但它会在 Chrome 中引发 DOM 异常:

// Generate a random KDF key.
const priv = new Uint8Array(24)
crypto.getRandomValues(priv)
const kdfKey = await crypto.subtle.importKey(
  'raw', priv, { name: 'PBKDF2' }, false, ['deriveKey'])

// Derive the ECDH key.
const salt = new Uint8Array(16)
const iterations = 2000
const hash = { name: 'SHA-512' }
const curve = { name: 'ECDH', namedCurve: 'P-384' }
const usages = ['deriveKey']

crypto.getRandomValues(salt)

const ecdhKey = await crypto.subtle.deriveKey({
  name: 'PBKDF2', salt, iterations, hash
}, kdfKey, curve, true, usages) // throws.

当算法为AES-GCM(即当曲线被替换为例如{ name: 'AES-GCM', length: 256 })时,上述方法有效,但其他算法也会抛出异常,所以我怀疑我遗漏了一些……微妙的东西。

我的希望是/是 WebCrypto 适合接受随机位并生成 ECDH 公钥/私钥对。看起来情况可能并非如此。

替代方法是使用PBKDF2deriveBits,可用于手动创建ECDH 密钥对。如果这确实是唯一的选择,将随机位转换为公钥/私钥的常用算法是什么(即参考和公共实现)?如果我必须开发一些东西,我可能会在此处发布兴趣和评论。

编辑:用例的其他详细信息

使用 PBKDF 是为了避免在给定(私有)d 参数时必须生成 ECDH 密钥对的公钥(xy)。 xy 是派生的,因此不需要存储,而且我们的数据存储非常有限——仅适用于私钥,例如192 位,或多或少(PBKDF 也可以平滑位大小,但这是一个问题)。

如果 WebCrypto 在给定(伪)随机 d 参数时计算了 xy,则期望的结果可以如下实现/说明:

>>> curve = { name: 'ECDH', namedCurve: 'P-256' }
>>> k = await crypto.subtle.generateKey(curve, true, ['deriveKey'])
>>> pri = await crypto.subtle.exportKey('jwk', k.privateKey)
>>> delete pri.x
>>> delete pri.y
>>> k2 = await crypto.subtle.importKey('jwk', pri)
    ^^ errors

PBKDF 在许多示例中用于生成 (AES) 密钥。我希望计算椭圆曲线的xy 的功能已经存在于WebCrypto 中,可以通过PBKDF2 deriveKey 获得。

在 Javascript 中自己动手的替代方法是解析 JWK/Base64(URL 变体),然后使用带模运算的大整数函数(例如 Fermat 小定理),最后编写椭圆曲线点加法的函数,加倍和乘法。这就是我所做的 (ECC math here)。但我希望这一切都是不必要的,因为 WebCrypto 中存在执行此操作的代码,我只是希望使用 importKeyderiveKey 来使用它。

重申一下:没有用户生成的密码;使用这种方法生成 ECDH 密钥被认为是不明智的。

【问题讨论】:

    标签: javascript cryptography diffie-hellman


    【解决方案1】:

    布赖恩,

    不可能使用 WebCrypto 从密码确定性地创建密钥对。

    对不起。

    您可以确定性地从 Javascript 中的密码创建一个密钥对,然后导入该密钥对,只要它是正确表示的 secp256r1 密钥。

    有一些 javascript 库支持从密码确定性地创建密钥对。我无法就这些库的正确性或安全性发表任何声明,或者是否有针对 secp256r1 执行此操作的库。当然,密钥生成是使用加密时要考虑的最重要方面之一,基于弱熵源这样做是不好的。

    如果您决定继续采用这种方法,尽管导入给定类型的密钥非常简单,请参阅:https://github.com/diafygi/webcrypto-examples#ecdsa---importkey

    TL;DR 您的里程会因这种方法而异,但这是可能的。

    瑞恩

    【讨论】:

    • 非常感谢瑞恩。我也有同样的怀疑,你的建议是我的后备计划。您是否有任何标准可以说明为什么 PBKDF2 可用于创建 AES 但不能用于创建 ECDH 密钥?
    • 通常,您不会找到标准来说明为什么某事通常不是一个好主意。也就是说,在密钥生成的情况下,您需要一个难以猜测的密钥,即使使用 ECDH,随机性也会产生影响。 PBDKF2 尝试从母猪的耳朵中播种出丝绸钱包。您从密码中获得的熵很差,PBDKF2 的 50k 次迭代总比没有好,但它不会给您一个好的密钥,除非输入已经是一个好的密钥。
    • 这里有一篇文章链接到关于大脑钱包不安全感的论文 - news.bitcoin.com/brain-wallets-not-secure-no-one-use-says-study。大脑钱包是从密码短语派生的 ECC 密钥 (secp256k1)。您可以阅读它以了解为什么这不是推荐的东西。
    • 还有一个链接,这里接受的答案很好地解释了您将如何进行基本概念。完成后,您可能会担心以可以导入 WebCrypto API 的格式获取密钥。也就是说,我仍然建议不要使用这种方法。 crypto.stackexchange.com/questions/1662/…
    • 谢谢;正如您从我对问题的更新中看到的那样,熵不是问题。没有用户生成的密码;抱歉,问题并不清楚,我希望更新后的问题能澄清用例。
    【解决方案2】:

    “微妙。”不错!

    SubtleCrypto.generateKey()

    在阅读了上面链接中 SubtleCrypto 的密钥生成说明后,我将其弹出到 Chrome 的浏览器控制台中,它运行良好。您可能需要对其进行调整以适应您的情况,尤其是关于 keyUsages 的部分:

    (async function() {
        const algo = {
            "name": "RSASSA-PKCS1-v1_5",
            "modulusLength": 256,
            "publicExponent": new Uint8Array([0x01, 0x00, 0x01]),
            "hash": {
                "name": "SHA-256"
            }
        };
        const extractable = true;
        const keyUsages = ["sign","verify"];
        const result = await crypto.subtle.generateKey(algo, extractable, keyUsages);
        console.log(result);
    })();
    

    【讨论】:

    • 感谢您的帖子。所以给定一个秘密(Uint8Arraystring)如何使用给定的函数来(重新)生成一个ECDH 密钥对?生成随机 ECDH 密钥很简单,即 subtle.generateKey(curve, false, ['deriveKey']) 其中 curve 是例如{name: 'ECDH','namedCurve: 'P-384'},但不清楚如何导入、派生或确定性地生成密钥对。
    【解决方案3】:

    从更新中我们仍然无法确定用例是什么,我们可以告诉(我认为)您的目标是出于某种原因节省存储空间。 ECC 密钥非常小,很难想象用例会是什么,尤其是在 Web 应用程序中,如此少量的存储需要使用较弱的方法。也就是说,之前的答案保持不变,不可能直接用 webcrypto 做你想做的事。

    【讨论】:

    • 您能解释一下“较弱的方法”是什么意思吗?由于 PBKDF2 不会减少比特长度等效秘密的熵,我认为您的意思是其他一些弱点,如果是这样,我将不胜感激澄清。我预计 DIY keygen 的最大弱点可能是 DoS 和从错误实现中恢复的过程(因此需要 WebCrypto)。在任何情况下,您对 WebCrypto 无能的观点都会被记录下来,我会用奖励标记您的其他答案。
    • 顺便说一句,“d-only”序列化的目标是用于离线/“sneaker net”恢复密钥,由人类记录/传输,并结合简单的纠错码。当时没有想到一个简单的方法来解释它,存储大小是一个比喻,首先想到的是一个类似的等价物,可以帮助解释它。
    • 我明白了,这就解释了对空间的担忧;在查看基于纸张的密钥管理用例时,通常还需要保护书面形式。在密钥仪式中,我们经常使用this 之类的形式来存储密码,该密码可以 (a) 访问用于保护值的 Shamir 共享的分片,或者 (b) 直接提供对密钥的访问权限。
    • 至于您对这种方法的安全性的问题,可以想象,如果 WebCrypto 直接支持这一点并且您做的一切正确,那么在您的情况下它不会是坏事(基于我对您所拥有的解释共享)但如果确实如此,人们肯定会采用从密码中派生密钥的做法,因此它将成为公共 API 的反模式。如果您继续在 JS 中生成密钥的备份计划,您将暴露用于在浏览器沙箱中生成密钥的熵以及从威胁模型的角度来看意味着的一切。
    • 一般来说,加密代码是“特殊的”;你担心编译器会优化释放,担心通过侧通道(和其他向量)泄露秘密,并且在 JS 中保护它是困难的。虽然 WebCrypto 并不完美(从长远来看),但一旦您开始直接执行 keygen 或 cryptioin JS,您就会获得经过验证的可信赖实现,这些实现会成为您的责任,而环境不会为您提供工具来做以安全的方式进行。
    猜你喜欢
    • 2017-10-28
    • 2020-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-03
    • 2020-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多