nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.41k stars 29.51k forks source link

How can OpenSSL x25519 keys be used? #44317

Closed markg85 closed 2 years ago

markg85 commented 2 years ago

Version

v18.7.0

Platform

Linux

Subsystem

crypto

What steps will reproduce the bug?

let myPrivateKey = crypto.createDiffieHellman(384);
myPrivateKey.setPrivateKey("MC4CAQAwBQYDK2VuBCIEILAkqrzZ8/bjcSlJ1gaFo3aBSS3BgUD/noXx8EriOMhf", "base64")
let sharedSecret = myPrivateKey.computeSecret("MCowBQYDK2VuAyEA7/Jsf8Hq8GcsBEdGhX8XvKjFimgJuXYA8eo+q0hpL00=", "base64", "base64")

console.log(sharedSecret)

Should output: DXzMU6j9etPvTCRVslGIL42nyX/EqzH/+JTOlaBtzl8= Outputs: HXMCCiKnUwo4Zs3EGsW3VYGtPrzLFGIvEhz8YPPlmzaCSRJYfdShn38XkMroVMSg

How often does it reproduce? Is there a required condition?

Always

What is the expected behavior?

OpenSSL commands.

PRIVATE key:
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEILAkqrzZ8/bjcSlJ1gaFo3aBSS3BgUD/noXx8EriOMhf
-----END PRIVATE KEY-----
OTHER public key
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEA7/Jsf8Hq8GcsBEdGhX8XvKjFimgJuXYA8eo+q0hpL00=
-----END PUBLIC KEY-----

With the command openssl pkeyutl -derive -inkey priv.pem -peerkey other_pub.pem | base64 outputs:

DXzMU6j9etPvTCRVslGIL42nyX/EqzH/+JTOlaBtzl8=

What do you see instead?

Wrong output. See other text.

Additional information

I could be wrong in all of this. I know a bit of encryption to "make use of it" but am by no means skilled at it.

I'm trying to generate x25519 keys on the command line with openssl (before one asks, i need the shared secret feature). In another nodejs application I want to use those same keys to encrypt and decrypt data.

The secret key nodejs thinks to generate doesn't match the openssl one. Also, not in the above snippets, if i generate the keys to get my public key from the given private key i get a different public key when compared to openssl.

I might very well miss an option somewhere that makes it all magically work. There isn't much info i can find on this specific usecase making it definitely more complex. The goal is to have x25519 keys from openssl be usable in nodejs and preferably the other way around too.

tniessen commented 2 years ago
  1. crypto.createDiffieHellman() only works with standard Diffie-Hellman (that is, within a multiplicative group of integers modulo n). It does not support elliptic curves (ECDH or X25519 or X448).
  2. The base64-encoded strings within your PEM files are not raw key material: they are ASN.1 encoded data structures.

You probably want this:

// To import PEM keys:
const publicKey = crypto.createPublicKey(`-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEILAkqrzZ8/bjcSlJ1gaFo3aBSS3BgUD/noXx8EriOMhf
-----END PRIVATE KEY-----`);
const privateKey = crypto.createPrivateKey(`-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEA7/Jsf8Hq8GcsBEdGhX8XvKjFimgJuXYA8eo+q0hpL00=
-----END PUBLIC KEY-----`);

// The actual key exchange:
const secret = crypto.diffieHellman({ publicKey, privateKey });
console.log(secret.toString('base64'));
// Prints DXzMU6j9etPvTCRVslGIL42nyX/EqzH/+JTOlaBtzl8=

You might also want to use crypto.generateKeyPairSync('x25519') to create ephemeral key pairs.

markg85 commented 2 years ago

Hi @tniessen, you've helped me before with encryption nightmares :) Glad you know your stuff and are active here! Thank you for that!

That being said, i both hate and like the nodejs crypto part. I hate it because it's soooooo freaking difficult to find what you need when you try to mix tools. Like i am now with openssl + node. What i need to do in openssl to get things done don't appear to map 1-on-1 with node making it very difficult to find what you're looking for.

And i like it because it does allow me to get there eventually. With some help.

While your example works just fine, i'm now stuck in the decryption part. The reason why i needed the shared key in the first place.

Say in the openssl side i encrypt a blob like this:

openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:$SHARED_SECRET" -e -in $FILE_TO_ENCRYPT | <do something with the blob>

Here the $SHARED_SECRET is the secret in hex. Done like so:

openssl pkeyutl -derive -inkey $OUR_PRIV_KEY -peerkey $THEIR_PUB_KEY | xxd -p -c 1000000

To decrypt this same data with openssl i use:

cat <encrypted blob> | openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f" -d

This works just fine! Leading me to think that my openssl side is fine and working properly.

Now on the nodejs site things become head scratching again. There i do:

            let decipher = crypto.createDecipheriv('aes256', secret.toString("hex"), null);
            let content = decipher.update(/* base64 encoded blob */, 'base64', 'utf8')
            let decryptedData = content + decipher.final('utf8')

I verified that the secret is indeed what i expect it to be (0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f). However, using crypto.createDecipheriv here doesn't work as it demands an IV. I passed in null, but that doesn't seem to silence the demand as it gives me TypeError: Invalid initialization vector.

Using the deprecated crypto.createDecipher (note without the iv) does give me another error:

Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

I'm assuming that i somehow need to explicitly tell crypto to not apply an IV, but the way to do so (providing null) doesn't seem to be working how i'd expect it. The docs say i need a KeyObject of type secret to omit the IV, yet when i do exactly that with say:

...
let decipher = crypto.createDecipheriv('aes256', crypto.createSecretKey(secret), null);
...

I'm again welcomed with the TypeError: Invalid initialization vector error...

No clue what i'm doing wrong and how it should work.

markg85 commented 2 years ago

To elaborate a little more. I have a bit of a feeling I'm doing something fundamentally wrong here.

Usually with AES encryption, which is the eventual goal here, i'd provide:

Now with the shared secret i'm using the secret in openssl terminology as "password" and i have no IV at all. Yet this:

❯ echo "hello world" | openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f" -P
key=E2A06D3CB4B2A91406BE2B328A69A71CE642AA4A323D11A4EA2721FB524376E5
iv =0662A0A1C5996CF5DC08FC8F72021E69

Very clearly shows that there is an IV.. Where is that coming from? How is that calculated? How do i get the same effect in nodejs?

Also, the key i see in the output isn't the password i provided. Now i can see why that'd be the case, the key is in hex and the passphrase can be any latin string i think. Regardless, this is freaking vague to follow and give way to many points of confusion to figure out.

So i'm thinking.. Should i perhaps just hash (sha256) the "password" and generate a random IV? Conceptually that fits much more in my "how i think it works" logic and seems to be much more fitting for node's crypto.createDecipheriv. What do you think about this?

tniessen commented 2 years ago

Say in the openssl side i encrypt a blob like this:

openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:$SHARED_SECRET" -e -in $FILE_TO_ENCRYPT | <do something with the blob>

Here the $SHARED_SECRET is the secret in hex. Done like so:

openssl pkeyutl -derive -inkey $OUR_PRIV_KEY -peerkey $THEIR_PUB_KEY | xxd -p -c 1000000

To decrypt this same data with openssl i use:

cat <encrypted blob> | openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f" -d

This works just fine! Leading me to think that my openssl side is fine and working properly.

It might look like it's working fine, but you probably don't want this.


Using the deprecated crypto.createDecipher (note without the iv) does give me another error:

Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt

Neither crypto.createDecipheriv nor the old crypto.createDecipher magically do PBKDF2 key derivation; you'd have to first derive the key using PBKDF2 and then start decrypting.


I'm assuming that i somehow need to explicitly tell crypto to not apply an IV, but the way to do so (providing null) doesn't seem to be working how i'd expect it.

Side note: ciphers that do not use IVs (e.g., AES in ECB mode) are usually bad choices.

But you are using an IV; OpenSSL is secretly generating one for you. Add -P to your openssl enc command and it'll show you the IV:

$ openssl enc -aes256 -nosalt -pbkdf2 -base64 -A -pass "pass:0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f" -e -in README.md  -P
key=E2A06D3CB4B2A91406BE2B328A69A71CE642AA4A323D11A4EA2721FB524376E5
iv =0662A0A1C5996CF5DC08FC8F72021E69

Run this multiple times and you will see that the key and IV are deterministic because both are derived from the password using PBKDF2, and -nosalt removes all randomness from PBKDF2. You do not want this.

As you can see, OpenSSL is not using the X25519 output as the AES key. Instead, it uses PBKDF2 to derive the key and the IV from the X25519 output that you treat as a password. You could now copy these values into Node.js and decryption should work just fine. (Remember to hex-decode the values before passing them to createDecipheriv.)


You should explicitly select a block cipher mode. If you do not want to deal with authentication, aes-256-cbc is fine. Otherwise, if you do want authenticity, use aes-256-gcm.

X25519 already provides you with a 256-bit key, so you could use its output as the 256-bit AES key directly. However, this approach comes with some concerns, so passing the X25519 output through a key derivation function (with a random salt!) is still a good idea. PBKDF2 is a rather unusual choice though, I think. TLS uses a variant of HKDF, which is faster than PBKDF2 and well-suited for deriving strong key material from potentially weaker input keying material.

In the end, it does not matter much if you use PBKDF2 or HKDF. Here's how you could do HKDF using (a sufficiently recent version of) OpenSSL and Node.js.

$ export X25519_OUTPUT=0d7ccc53a8fd7ad3ef4c2455b251882f8da7c97fc4ab31fff894ce95a06dce5f
$ export HKDF_SALT=$(openssl rand -hex 16)
$ echo $HKDF_SALT
e72e613128f4ffa929042c1f7dc2706d
$ openssl kdf -keylen 32 -kdfopt digest:sha256 -kdfopt hexkey:$X25519_OUTPUT -kdfopt hexsalt:$HKDF_SALT HKDF
F4:F5:8F:31:0C:A4:0D:0C:74:63:E5:C8:BF:C5:F6:53:96:2E:FE:CF:EF:3F:CD:5B:7E:F6:F5:F1:3F:48:FF:93
const bytes = crypto.hkdfSync('sha256',
                              Buffer.from(process.env.X25519_OUTPUT, 'hex'),
                              Buffer.from(process.env.HKDF_SALT, 'hex'),
                              '', 32);
console.log(Buffer.from(bytes).toString('hex'));
// Prints f4f58f310ca40d0c7463e5c8bfc5f653962efecfef3fcd5b7ef6f5f13f48ff93

Technically, if you derive a new AES key for every encryption operation, you do not need an IV. However, I'll still use an IV because it's less questionable than relying on unique keys.

$ export IV=$(openssl rand -hex 16)
$ echo $IV
5b5e6c8ce57bafca99aabbb1fdf11468

Now let's encrypt some data:

$ export KEY=f4f58f310ca40d0c7463e5c8bfc5f653962efecfef3fcd5b7ef6f5f13f48ff93
$ openssl enc -aes-256-cbc -e -base64 -A -K $KEY -iv $IV -in README.md -out README.encrypted.md

Decrypt using Node.js:

const ciphertextBase64 = fs.readFileSync('README.encrypted.md', 'utf8');
const ciphertext = Buffer.from(ciphertextBase64, 'base64');
const decipher = crypto.createDecipheriv('aes-256-cbc',
                                         Buffer.from(process.env.KEY, 'hex'),
                                         Buffer.from(process.env.IV, 'hex'));
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
console.log(plaintext.toString());
// Prints the contents of README.md
markg85 commented 2 years ago

That is so amazing! Thank you so much for that very verbose and clear explanation! Based on that i got it working, yay :) I went for the last example as that is already the line of thought i had that made sense to me.

With regards to this actual bug report, you've answered it all and there is no bug. Yet documentation wise it could vastly improve. When there is a nodejs communication where both ends of the communication use nodejs then the current documentation is probably vague but ok'ish. When nodejs is used with other tooling (then openssl quickly comes into the picture if there's any encryption involved) then documentation is just missing completely.

I do want to clear one thing up and have a couple questions still.

Clearing up: PBKDF2 When it comes to encryption i have a sort of base knowledge that just guides me to the base concepts. Like i know i want async encryption for key sharing purposes and want to use sync encryption for the actual data. With that i want to use "todays" standards, not yesterdecades like RSA. That along with reading how for example wireguard works gives a bit of a sense of the direction - tech wise - i'm looking for. I Actually wanted to go for ed25519 instead because you can convert them to x25519. More on this later. I ended up on x25519 as that seemed to make everything much easier if the real goal is just secret sharing + encryption. Sign/verify isn't something i need (yet). Once you get to the point where you use x25519 in openssl and generate a secret then you're up to the "fairly normal" point of sync encryption (aes). Here the examples i used were using the -p option to provide the secret as password. I just went along with that, not thinking twice about it. Openssl complans about that option being deprecated and -pass "pass:<secret>" should be used instead. Somewhere along the lines an example with PBKDF2 sneaked up in there and kinda stayed. That's the point where my encryption/key knowledge fails and the rational "ohh this works, i'll use that instead" wins. It seemed to make some sense as it was talking about deriving keys which i needed to get that shared secret. I'm glad you explained this in a way that makes sense to me :)

Question: ed25519, x25519 and key reuse The eventual endgoal is to be able to sign, verify and encrypt. I need ed25519 for the sign/verify parts and x25519 for the encryption. In the security world it's close to a death sentence if you reuse keys. Yet from a high level overview that seems to be exactly what you'd want with these. As far as i understand it you would want to be able to determine if given x25519 public key can be converted from ed25519 to know both are originating from the same user. Yet somehow this, in my mind, very convenient feature seems to be frowned upon by security experts. That makes me really curious to know how you'd do sign/verify/encrypt! With this i have to add that - as a user - i'd only want to have one sort of "master" key. Be it a ed25519 or a x25519. I definitely don't want to maintain a set of both keys. So what is the proper way to do this?

Question: Salts. I don't get them in the AES context You were very clear about using salts. I should never disable that. I get the use of salts in, say, password hashing to make them "more unique" and effectively give brute forcing a much harder job to crack such a salted password. However, in this very example that given salt is - say for a website with a database and a users table - a property stored in the users table. With that in mind your encryption (or hashing more in this context) requires 2 inputs. The salt from the site and the users input. Both together form a new hash that is the users password. This makes sense to me. In the openssl encryption it makes no sense to me at all. I never store the salt anywhere yet the decryption - without me providing a salt - can decrypt it just fine. Hence the salt, at least in my thought, must be derived from one of the inputs that are given (either the key or the encrypted data itself). If decryption can "find" the salt in such a way then what use does it have to enable it? As in my mind it's just an added layer of obfuscation that is gone if you know how to find the salt. Another thing is the IV here. If i were to be using the same AES key over and over (bad practice, i know, but just to see the output stay the same) then the random IV would also cause the output to be different every time. And you do need to provide that IV when decrypting. This again makes me think that using salt in this context serves no purpose? I hope you could elaborate on this as it's a very vague concept that i couldn't find an understandable explanation of other then claims like "use it or have fun dying"...

tniessen commented 2 years ago

I need ed25519 for the sign/verify parts and x25519 for the encryption. In the security world it's close to a death sentence if you reuse keys. Yet from a high level overview that seems to be exactly what you'd want with these. As far as i understand it you would want to be able to determine if given x25519 public key can be converted from ed25519 to know both are originating from the same user. Yet somehow this, in my mind, very convenient feature seems to be frowned upon by security experts.

As a rule of thumb, never use the same key (or key pair) with different algorithms. While X25519 and Ed25519 are designed well, it's still a potential attack vector that's usually easy to avoid.

That makes me really curious to know how you'd do sign/verify/encrypt! With this i have to add that - as a user - i'd only want to have one sort of "master" key. Be it a ed25519 or a x25519. I definitely don't want to maintain a set of both keys. So what is the proper way to do this?

There are many possible approaches. A pretty good strategy are so called ephemeral keys. I'll describe a simplified version that is similar to what TLS uses.

The idea is to only maintain an Ed25519 key pair, where the public key is your identity (e.g., included in a certificate) and the private key is what you need to keep secret, permanently (the "master" key, as you describe it).

To exchange a shared secret with another party, generate a new X25519 key pair on the fly and use the Ed25519 key pair to sign the X25519 public key. Send the signed X25519 public key to the other party. The other party can verify that the X25519 key does indeed belong to the sender since it has been signed using the sender's Ed25519 key. (Depending on your threat model, the other side can also use ephemeral keys.) Now perform the key exchange as usual using X25519 to obtain a shared secret.


I get the use of salts in, say, password hashing to make them "more unique" and effectively give brute forcing a much harder job to crack such a salted password.

Salts do not help against (pure) brute force attacks much; they rarely significantly increase the computational complexity of such attacks.

In password hashing, the main benefit is that an attacker cannot tell if two hashed passwords are the same password or not: pass1 = pass2 implies H(pass1) = H(pass2) for a deterministic function H (e.g., a hash or key derivation function), but when adding random salts, pass1 = pass2 does not imply H(pass1, salt1) = H(pass2, salt2). This prevents attacks that utilize lookup tables of known hash values and has some practical benefits as well. (For example, assume that the user database is leaked; unsalted hashes would immediately allow cross-referencing the user database with other leaked databases without knowing the passwords at all.)

In the openssl encryption it makes no sense to me at all. I never store the salt anywhere yet the decryption - without me providing a salt - can decrypt it just fine. Hence the salt, at least in my thought, must be derived from one of the inputs that are given (either the key or the encrypted data itself). If decryption can "find" the salt in such a way then what use does it have to enable it? As in my mind it's just an added layer of obfuscation that is gone if you know how to find the salt.

I'm assuming you are referring to the OpenSSL command that derives a key from a password, e.g., when using -pbkdf2. (In my previous example, I explicitly passed a salt to HKDF, so I assume that's not what you are referring to.)

With -nosalt, the key and IV are derived from the password deterministically, which is bad. In fact, that's why crypto.createCipher and crypto.createDecipher are deprecated: these functions also derive the key and IV from a password, thus reusing a password causes key and IV to be reused, which can be fatal (e.g., in an AES counter mode such as CTR or GCM). Anyway, with -nosalt, there is no salt that would need to be stored, and during decryption, OpenSSL simply derives the same key and IV from the password as it did during encryption (again with no salt).

With -salt (default behavior unless -nosalt or -S is specified), OpenSSL generates a random salt for you and prepends it to the ciphertext. This is non-standard and mostly a convenience feature so that you don't have to worry about storing the salt separately. During decryption, OpenSSL simply takes those first few bytes of the input, which are not encrypted, and uses them to derive key and IV, which it then uses to decrypt the rest of the input, i.e., the actual ciphertext. This is secure and leads to pseudo-random keys and IVs, but the receiver needs to be aware of this mechanism. (Also, OpenSSL only uses 64-bit salts in this case, which might not be enough depending on the threat model.)

Another thing is the IV here. If i were to be using the same AES key over and over (bad practice, i know, but just to see the output stay the same) then the random IV would also cause the output to be different every time. And you do need to provide that IV when decrypting. This again makes me think that using salt in this context serves no purpose?

As I wrote in my previous comment, if you generate a new pseudo-random key for each encryption operation, you technically do not need random IVs (in most block cipher modes). In other words, if you generate a new key using -pbkdf2 and without -nosalt for every encryption operation, you do not need to worry about the IV. OpenSSL will magically derive the IV in the same way as the key and there is no need to store it (but the salt will be stored, see above).

However, it is often desirable to reuse AES keys, even if only for performance reasons. For example, TLS/SSL would be incredibly slow if it had to exchange new AES keys for every data packet.

As soon as you use a symmetric key more than once, you need to add randomness (or at least statefulness depending on the block cipher mode, but randomness is usually the simpler approach), and with most symmetric ciphers, this happens through the IV.

Note that the salt only affects key derivation, so once you have derived a key and are going to reuse it, the salt does not matter anymore -- it still needs to be stored so that the same key can be derived again for decryption (see above).

Thus, the only way to securely reuse the derived key is to change the IV for each encryption operation. If you generate a random IV for each encryption operation, you need to store the IV alongside the ciphertext in the same way that OpenSSL stores the salt along with the ciphertext (but in that case, the IV is derived from the password and salt, so storing the salt is sufficient and the IV does not need to be stored).

markg85 commented 2 years ago

Thank you so much for that elaborate response! It helps a lot :)

I do now "kinda" get how i'd sort of create an Ed25519 master pair and X25519 as ephemeral ones. But... this gives a new challenge. Once i don't have "right now" because i'm skipping Ed25519 entirely and "kinda" use one set of X25519 as master pair. Just knowing the other party's public key is enough (in an online and offline environment) to encrypt data for the receiving side.

Imagine the case where there are 2 parties where one is offline, one is online. The TLS approach doesn't work in this situation because you can't do the "exchange" part. Yet i do want to encrypt data for the other party for them to use when they come online. I've seen a couple different approaches to this:

  1. Get the last known public X25519 key and multiply that by some random number. This information would have to be added to the encrypted data. The owner of that X25519 key would have to multiple the corresponding private key by the same value. This is supposed to give that party the ability to decrypt that data. Downside is that the receiver side would need to maintain a list of X25519 pairs to find the private key belonging to the last know public one. Not really ideal. I have no clue of this works, first time i read anything about that magic multiplication trick.
  2. My current approach (a X25519 as master pair per user). This still gives me unique secrets between each "my private, their public" pair. But perhaps not as secure as it could be.
  3. A bit like point 1 but more structured. Each side just pre-register a bunch of X25519 pairs where you'd just pick one and let that be known as prefixed data. If it's then agreed that each pair can only be used once then it's a matter of removing a pair once it's been used. You do still have the extra bookkeeping though.

Summed up, i'm looking for a way to do:

  1. Offline encryption
  2. 1 "master" key pair
  3. No bookkeeping besides the master pair

Any idea what best for that? I prefer to stick in the Ed25519 and X25519 ones here but open to alternatives if they provide what i need.

Lastly, how do you generate your public key in node when you read your private key from a PEM file? So:

const privateKey = crypto.createPrivateKey(`-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEILAkqrzZ8/bjcSlJ1gaFo3aBSS3BgUD/noXx8EriOMhf
-----END PRIVATE KEY-----`);

How do i get the public key from that? Before you ask, yes i have the public key and could pass it in the application as an argument (the private one is an argument too). But it would save arguments and is easier in code if i can just get the public key from the private one. Which should be possible as openssl can do that too.

I again just don't see an API option that allows for this with imported keys. I'm either missing api discover-ability (looking at this page: https://nodejs.org/api/crypto.html), or it's not documented or it all is there but just in ways i totally don't consider to be working for me..... It's probably the last case (as it has been a couple times already).

tniessen commented 2 years ago

Any idea what best for that? I prefer to stick in the Ed25519 and X25519 ones here but open to alternatives if they provide what i need.

If one party is offline, it's usually fine for that party to have a persistent X25519 key pair (optionally signed using Ed25519 or verified through some certificate chain / PKI) as long as the sender generates an ephemeral X25519 key pair. The party that is online could then send the ephemeral X25519 public key and the encrypted message to the offline receiver, again signing both parts if desired.

There are other possible algorithms and slightly different designs, but virtually all key encapsulation mechanisms boil down to the offline party having a persistent public key for the purpose of key exchange (X25519, ECDH, RSA-OAEP, etc.).

Note that this leaves it up to the sender to employ secure randomness and to not choose weak keys etc., which is usually not the case in online protocols where both sides equally affect key derivation.

Theoretically, you could use only one "master" key pair for both Ed25519 and X25519, but it's usually considered bad practice. You can, of course, use a single "master" password to encrypt both private keys.

Lastly, how do you generate your public key in node when you read your private key from a PEM file? So:

const privateKey = crypto.createPrivateKey(`-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEILAkqrzZ8/bjcSlJ1gaFo3aBSS3BgUD/noXx8EriOMhf
-----END PRIVATE KEY-----`);

How do i get the public key from that?

Like this:

crypto.createPublicKey(privateKey)
markg85 commented 2 years ago

Awesome!

Thank you so much for all that help! You've literally answered every question i had with not just an explanation but also a solution! Now I've got some fixing to do and get this project working :)

As for crypto.createPublicKey(privateKey) darn! After you said that i looked at the documentation again and noticed it can be used exactly how i needed it. Nice!

tniessen commented 2 years ago

You are welcome. I am going to close this as answered but feel free to ping me here or elsewhere if any other questions related to node:crypto come up.