5

WARNING The following is not intended as an endorsement of converting passwords to ECDH keys. Create ECDH keys from high-entropy, crypto-safe PRNGs.

I want to take a secret and generate a ECDH public/private key from it.

In the browser, a usual way would be to use PBKDF2 (or other deterministic bytes) to generate an ECDH public/private key pair in WebCrypto.

The following sample code should do this, but it throws a DOM Exception in Chrome:

// 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.

The above works when the algorithm is AES-GCM (i.e. when curve is replaced with e.g. { name: 'AES-GCM', length: 256 }), but other algorithms throw exceptions as well, so I suspect I'm missing something ... subtle.

My hope was/is that WebCrypto would be suited to accepting random bits and generating the ECDH public/private key pair. It looks like this might not be the case.

The alternative would be to use PBKDF2 to deriveBits that can be used to manually create the ECDH key pair. If this is indeed the only option, what is the usual algorithm for turning random bits into a public/private key (i.e. references & public implementations)? If I have to develop something, I'll likely post it here interest & review.

EDIT: Additional details of the use-case

The use of PBKDF is an attempt to avoid having to generate the public key (x and y) of the ECDH keypair when given the (private) d parameter. The x and y are derivative and so needn't be stored, and we've a very limited datastore — suitable only for the private key e.g. 192 bits, more-or-less (PBKDF can smooth out the bit size, too, but that's an aside).

If WebCrypto computed the x and y when given (pseudo)random d parameter, the desired outcome could be achieved/illustrated as follows:

>>> 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 is used to generate (AES) keys in numerous examples. I was hoping the functionality for calculating x and y for elliptical curves, it already existing in WebCrypto, would be available through PBKDF2 deriveKey.

The do-it-yourself alternative in Javascript is to parse JWK/Base64 (URL variant), then use a large-integer function with modulo arithmetic (e.g. Fermat's Little Theorem), and finally write functions for elliptical curve point addition, doubling, and multiplication. Which is what I've done (ECC math here). But I was hoping that'd all be unnecessary, seeing as the code for doing exactly this exists in WebCrypto, and I was just hoping to use either importKey or deriveKey to wield it.

Just to reiterate: There are no user-generated passwords; using such to generate the ECDH key is considered unwise.

Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343
  • i think this is a super relevant question, and even if a user chosen password is far too biased, i recently went through the trouble to create keys in web crypto from mnemonic word lists... which is a legit idea to avoid having exportable keys in the browser and deterministic key generation with paper backup. – gotjosh Jun 28 '23 at 19:52

3 Answers3

5

Brian,

It is not possible to deterministically create a keypair from a password utilizing WebCrypto.

Sorry.

You could deterministically create a key pair from a password in Javascript and then import that keypair as long as its a secp256r1 key in the right representation.

There are a few javascript libraries out there that support deterministically creating a key pair from a password. I can not make any statements on the correctness or security of those libraries or if there are ones that do this for secp256r1. Of course, key generation is one of the most important aspects to consider when using crypto and doing so based on a weak source of entropy is bad.

If you decide to go forward with that approach despite this importing a key of a given type is quite straightforward, see: https://github.com/diafygi/webcrypto-examples#ecdsa---importkey

TL;DR your miles will vary with this approach but it is possible.

Ryan

rmhrisk
  • 1,814
  • 10
  • 16
  • 1
    Many thanks Ryan. I suspected as much and your suggestion was my fallback plan. Do you have any references to the standards that would indicate why PBKDF2 can be used to create AES but not ECDH keys? – Brian M. Hunt Dec 02 '18 at 23:45
  • Usually, you won't find standards saying why something isn't generally a good idea. That said, in the case of key generation you want a key that is hard to guess, the randomness feeds into that, even with ECDH. PBDKF2 is an attempt to sow a silk purse out of a sow's ear. The entropy you get out of a password is poor, 50k iterations of a PBDKF2 is better than nothing but it doesn't give you a good secret key unless the input was already a good secret key. – rmhrisk Dec 02 '18 at 23:50
  • Here is a article which links to a paper on brain wallet insecurities - https://news.bitcoin.com/brain-wallets-not-secure-no-one-use-says-study/. Brain wallets are ECC keys (secp256k1) derived from a passphrase. You can read it to get an idea of why this isnt something that is recomended. – rmhrisk Dec 02 '18 at 23:57
  • One more link, the accepted answer here does a good job explaining how you would go about doing the base concept. Once you do, you could then worry about getting the key in a format you could import into WebCrypto API. That said I still advise against this approach. https://crypto.stackexchange.com/questions/1662/how-can-one-securely-generate-an-asymmetric-key-pair-from-a-short-passphrase – rmhrisk Dec 03 '18 at 07:21
  • Thanks; as you'll see from my updated to the question, entropy is not a concern. There are no user generated passwords; sorry the question wasn't clearer on that, and I hope the updated question clarifies the use-case. – Brian M. Hunt Dec 03 '18 at 15:12
2

"subtle." Good one!

SubtleCrypto.generateKey()

After reading the key generation instructions for SubtleCrypto at the link above, I popped this into the browser console on Chrome and it worked fine. You may need to adapt it to fit your situation, especially the part about the 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);
})();
vrtjason
  • 523
  • 5
  • 12
  • 2
    Thanks for the post. So given a secret (either `Uint8Array` or `string`) how would one use the given function to (re)-generate an `ECDH` keypair? Generating a random ECDH key is straightforward i.e. `subtle.generateKey(curve, false, ['deriveKey'])` where `curve` is e.g. `{name: 'ECDH','namedCurve: 'P-384'}`, but it's not clear how to import, derive, or deterministcally generate a keypair. – Brian M. Hunt Nov 30 '18 at 19:01
1

From the update we can still not tell what the use case is, we can tell (I think) your goal is to save storage space for some reason. The ECC keys are quite small, it’s hard to imagine what the use case would be especially in a web application where such a small amount of storage would necessitate using a weaker approach. That said the earlier answer remains the same, it is not possible to do what you want with webcrypto directly.

rmhrisk
  • 1,814
  • 10
  • 16
  • Could you explain what you mean by "weaker approach"? As PBKDF2 doesn't reduce entropy for bit-length equivalent secrets, I assume you mean some other weakness, and if so I'd be grateful for clarification. I expect the most material weakness of DIY keygen is probably DoS & recovery process from faulty implementation (hence the desire for WebCrypto). In any case, your point on WebCrypto's inability is noted, and I'll mark your other answer with the award. – Brian M. Hunt Dec 03 '18 at 16:02
  • Incidentally, the goal of "`d`-only" serialization is for offline/"sneaker net" recovery keys that are to be recorded / transmitted by humans, and combined with a simple error correcting code. Not having thought of a simple way to explain it at the time, storage size is a metaphor that first came to mind as an analogous equivalent that would help explain it. – Brian M. Hunt Dec 03 '18 at 16:09
  • I see, that explains the concern over space; when looking at paper-based key management use cases it is usually a requirement to protect the written form as well. In key ceremonies we often use forms like [this](https://unmitigatedrisk.com/?p=406) to store a password that is either (a) gains access to a shard of a Shamir share used to protect the value, or (b) provides access to the key directly. – rmhrisk Dec 03 '18 at 16:22
  • As for your question about the security of this approach, it is concievable that if WebCrypto supported this directly and you did everything right that it wouldnt be bad in your case (based on my interpretation of what you have shared) but if it did surely people would adopt the practice of deriving keys from a password so it would be an anti-patern for a public API. If you go forward with the backup plan of generating the keys in JS, you expose the entropy used to generate the key in the browser sandbox along with everything that means from a threat model standpoint. – rmhrisk Dec 03 '18 at 16:25
  • As a general rule, crypto code is "special"; you worry about compilers optimizing out frees, about leakage of secrets via side channels (and other vectors) and protecting from this in JS is hard. While WebCrypto isnt perfect (by a long shot) you get verified trustworthy implementations that have mitigations for the most common risks, as soon as you start doing keygen or cryptioin JS directly that becomes your responsability and the enviroment just doesnt give you the tools to do it in a secure fashion. – rmhrisk Dec 03 '18 at 16:27
  • Great reference for the key ceremony; we were mulling exactly that this morning, thank you. We intend to employ a Hardware RNG for these curve secrets, incidentally; interesting that you noted it. From the public API standpoint, that's a valid concern. My experience is generally that folks who don't know what they're doing will find a way to circumvent the boundaries put there for their own safety, whereas folks who know the reason the boundaries exist tend not to need the barrier. But an in-browser API is peculiarly long-lived and carries enormous breadth of exposure. – Brian M. Hunt Dec 03 '18 at 16:42