Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I sign a message and verify it using ED25519 keys? #2203

Open
Dubz opened this issue Sep 25, 2019 · 18 comments
Open

How can I sign a message and verify it using ED25519 keys? #2203

Dubz opened this issue Sep 25, 2019 · 18 comments

Comments

@Dubz
Copy link

Dubz commented Sep 25, 2019

  • Node.js Version: 12.8.0
  • OS: Windows 10 (deployment will be on CentOS 7)
  • Scope (install, code, runtime, meta, other?): Code?
  • Module (and version) (if relevant): crypto

I am trying to have servers connect to a centralized server. To validate their identity, I am opting to use encryption/signing keys. I would prefer to use ED25519 if possible, but if I'm mistaking their ability for this, please let me know.

Also, please note that this is not SSL/SSH. This is just general data that will be passed along an application that also accepts clients (hence the verification necessity). The keys will serve to prove the server really is a server, and give it the necessary rights/access for more methods.

I've tried RSA and I can get it partially working, but I cannot get it to load the public key properly.

Here's what I've got so far. Only dependencies are the keys, which anyone can generate. I am using ed25519 and RSA 4096-bit for this test.

"use strict"

const fs = require("fs"),
    crypto = require("crypto");

// Let's set up some key files that can be easily rotated for testing
const keys = [
    {algorithm: 'ed25519', name: 'ed25519', passphrase: 'password', pem: false, ppk: false, pub: false, public: false, private: false},
    {algorithm: 'ed25519', name: 'ed25519_2', passphrase: 'password', pem: false, ppk: false, pub: false, public: false, private: false},
    {algorithm: 'rsa', name: 'rsa', passphrase: 'password', pem: false, ppk: false, pub: false, public: false, private: false},
];

// Select our key from the list above
const key = keys[2];
// Set a message to sign
const message = 'Hello world!';

// Load the files (that exist)
key.pem = (fs.existsSync("./" + key.name + ".pem")) ? fs.readFileSync("./" + key.name + ".pem") : false;
key.ppk = (fs.existsSync("./" + key.name + ".ppk")) ? fs.readFileSync("./" + key.name + ".ppk") : false;
key.pub = (fs.existsSync("./" + key.name + ".pub")) ? fs.readFileSync("./" + key.name + ".pub") : false;

// Verify what was loaded
console.log(key.pem.toString(), "\n\n", key.ppk.toString(), "\n\n", key.pub.toString(), "\n\n");


// At this point, we have our key data and messages. Let's continue

// Create the public/private key objecs for sign/verify
key.private = crypto.createPrivateKey({key: key.pem, passphrase: key.passphrase});
// key.public = crypto.createPublicKey(key.private);
key.public = crypto.createPublicKey({key: key.pub});


// Sign message with private key
const sign = crypto.createSign('RSA-SHA512');
sign.update(message);
const sig = sign.sign(key.private);

console.log('Signed!');
console.log(sig);

// Verify signature with public key
console.log('Verifying signature...');

const verify = crypto.createVerify('RSA-SHA512');
verify.update(message);
const verified = verify.verify(key.public, sig);

// Results?
console.log('Match:', verified);

If I use the private key object to create the public key object, it works. If I use the public key to ry and create the public key object, it fails. Obviously I need to be able to have the public key on a separate server to verify without the private key, so that's where I'm stumped. This is for RSA. ED25519, can't even get the private key object.

RSA public key object error:

internal/crypto/keys.js:321
  handle.init(data, format, type);
         ^

Error: error:0909006C:PEM routines:get_name:no start line
    at Object.createPublicKey (internal/crypto/keys.js:321:10)
    at Object.<anonymous> (C:\Users\...\Desktop\test_signing\exec.js:24:21)
    at Module._compile (internal/modules/cjs/loader.js:868:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:879:10)
    at Module.load (internal/modules/cjs/loader.js:731:32)
    at Function.Module._load (internal/modules/cjs/loader.js:644:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:931:10)
    at internal/main/run_main_module.js:17:11 {
  library: 'PEM routines',
  function: 'get_name',
  reason: 'no start line',
  code: 'ERR_OSSL_PEM_NO_START_LINE'
}

ED25519 private key object error:

internal/crypto/keys.js:329
  handle.init(data, format, type, passphrase);
         ^

Error: error:0909006C:PEM routines:get_name:no start line
    at Object.createPrivateKey (internal/crypto/keys.js:329:10)
    at Object.<anonymous> (C:\Users\...\Desktop\test_signing\exec.js:30:22)
    at Module._compile (internal/modules/cjs/loader.js:868:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:879:10)
    at Module.load (internal/modules/cjs/loader.js:731:32)
    at Function.Module._load (internal/modules/cjs/loader.js:644:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:931:10)
    at internal/main/run_main_module.js:17:11 {
  library: 'PEM routines',
  function: 'get_name',
  reason: 'no start line',
  code: 'ERR_OSSL_PEM_NO_START_LINE'
}

They error with the same reason, but I have no idea what that is. Any help would be greatly appreciated!!!

@bnoordhuis
Copy link
Member

Check the documentation: crypto.createPrivateKey() and crypto.createPublicKey() expect PEM as the input unless format is 'der' (and in that case you must also pass in type.)

@Dubz
Copy link
Author

Dubz commented Sep 26, 2019

Check the documentation: crypto.createPrivateKey() and crypto.createPublicKey() expect PEM as the input unless format is 'der' (and in that case you must also pass in type.)

Already did that and many other tweaks. I ended up creating keys in NodeJS with crypto.generateKeyPairSync() and going back to RSA. Not satisfied, but it'll have to do.

@bnoordhuis
Copy link
Member

If you have reasonable cause to believe it's an issue with node.js, please open a bug over at nodejs/node and I'll take a look - but please include a full reproducer, keys and all. Thanks.

@Dubz
Copy link
Author

Dubz commented Oct 1, 2019

If you have reasonable cause to believe it's an issue with node.js, please open a bug over at nodejs/node and I'll take a look - but please include a full reproducer, keys and all. Thanks.

I just used the script above. The keys were generated with PuTTYgen and named accordingly (ed25519, ed25519_2, rsa; .pub, .pem, .ppk) (for keys array, key is set to choose which pair to use).

@tniessen
Copy link
Member

As @bnoordhuis said, we would need the exact input files to reproduce this and tell you where the problem is.

@anders94
Copy link

Not your core key parsing issue but in the case of ed25519 you can't sign.update(message); because streaming isn't supported - you have to do that in a single shot instead. See mscdex/io.js@7d0e50d where crypto.sign(algorithm, data, key) has been added. Documentation is here: https://nodejs.org/api/crypto.html#crypto_crypto_sign_algorithm_data_key

@tniessen
Copy link
Member

Closing due to lack of information from @Dubz.

@blelump
Copy link

blelump commented Mar 22, 2020

Got to the same issue, but probably for different reason (ed25519 key pair generated via lib sodium). More explanation can be found here panva/paseto#8 . The decoder http://lapo.it/asn1js/ also helps for inspecting the key format.

@Dubz
Copy link
Author

Dubz commented Mar 23, 2020

Not sure why @tniessen closed this honestly. Still likely an issue, but I'm no longer in need of this so not my problem anymore.
I didn't realize it was that complicated to generate keys with PuTTYgen and that they actually needed me to do that for them too.

To anyone else trying to do something along the lines of this, good luck to you. Might as well just use RSA keys for now since they don't seem to want to update it to support ED25519.

@anders94
Copy link

Seems to work for me. (culled your code down a bit)

const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');

const message = 'Hello world!';
console.log(message);

const signature = crypto.sign(null, Buffer.from(message), privateKey);
console.log(signature);

const verified = crypto.verify(null, Buffer.from(message), publicKey, signature)
console.log('Match:', verified);

@tniessen
Copy link
Member

Not sure why @tniessen closed this honestly.

@Dubz

  1. We asked you for more information five months ago, and you never replied.
  2. This repository is not for reporting bugs or problems with Node.js. If you believe there is a problem in Node.js, go to the main repository. This repository is for help with Node.js that is not related to problems in Node.js itself.

But since you clearly disagree, I will reopen your issue.


@Dubz wrote:

To anyone else trying to do something along the lines of this, good luck to you. Might as well just use RSA keys for now since they don't seem to want to update it to support ED25519.

To anyone else trying to do something along the lines of this, communicate with us. Don't just vanish and expect us to magically solve your problems.

Node.js is an open-source project, @Dubz. If you want something implemented/fixed, either do it yourself and create a PR, or talk to us. We don't work for you.

@tniessen tniessen reopened this Mar 23, 2020
@Dubz
Copy link
Author

Dubz commented Mar 24, 2020

Seems to work for me. (culled your code down a bit)

const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');

const message = 'Hello world!';
console.log(message);

const signature = crypto.sign(null, Buffer.from(message), privateKey);
console.log(signature);

const verified = crypto.verify(null, Buffer.from(message), publicKey, signature)
console.log('Match:', verified);

As I stated in the OP,

If I use the private key object to create the public key object, it works. If I use the public key to try and create the public key object, it fails. Obviously I need to be able to have the public key on a separate server to verify without the private key, so that's where I'm stumped. This is for RSA. ED25519, can't even get the private key object.

Not sure why @tniessen closed this honestly.

@Dubz

1. We asked you for more information five months ago, and you never replied.

2. This repository is not for reporting bugs or problems with Node.js. If you believe there is a problem in Node.js, go to the main repository. This repository is for help with Node.js that is not related to problems in Node.js itself.

But since you clearly disagree, I will reopen your issue.

@Dubz wrote:

To anyone else trying to do something along the lines of this, good luck to you. Might as well just use RSA keys for now since they don't seem to want to update it to support ED25519.

To anyone else trying to do something along the lines of this, communicate with us. Don't just vanish and expect us to magically solve your problems.

Node.js is an open-source project, @Dubz. If you want something implemented/fixed, either do it yourself and create a PR, or talk to us. We don't work for you.

  1. You asked me to use PuTTYgen for you since apparently it's too difficult to launch, tick a radial button, wiggle your mouse, and save each file. I purposefully left these out because I feel its bad practice to provide public/private keys to the public web, even if it's for (and explicitly advertised as) use with debugging scripts. You can state it's not a security issue all you want, and I'll agree, but there's bound to be one new coder to come around and use them because they can't figure out how to wiggle their mouse around either.
  2. I was originally asking for help and if I was doing it correctly, hoping someone here has had experience in any way with utilizing encryption and signing algorithms to be able to quickly look over it and either adjust as necessary, or inform me of it being incapable at this time.

To be clear, I never expected anything. Like I said, I was just asking for assistance to see if I was misunderstanding the module.
I also never claimed you worked for me, and was in fact attempting to talk with you. I just figured someone who is capable of assisting/maintaining an open source project like this could handle generating a simple key, rather than expect to be spoon fed. It's just as useful as not responding to the thread at all.

Like I said, I'm no longer building the project that I wanted this for, so it doesn't really matter to me what you do with this thread. At this point, it'd just be a POC for someone with more experience with this module to confirm my original theory that ED25519 keys are not supported.

@tniessen
Copy link
Member

tniessen commented Apr 3, 2020

@Dubz I apologize if it seemed to you like we don't want Ed25519 to work, that is not the case. I implemented and tested a big part of it myself.

You asked me to use PuTTYgen for you since apparently it's too difficult to launch, tick a radial button, wiggle your mouse, and save each file.

The reason I asked you to do this is because I tested ED25519 with various input files, and couldn't reproduce your issue. Since I wasn't working on Windows at the time, I didn't have PuTTY installed.

I downloaded PuTTYgen, started it, generated a key, and exported it. It seems that none of the formats provided by the PuTTYgen GUI are standard key formats. That makes sense, given that PuTTYgen is not a general purpose key management tool, but only designed for SSH. Node.js is not an SSH tool, and won't accept non-standard key formats.

I know that you have moved on, but how did you convert these SSH files to actual keys before passing them to Node.js? Just so I can reproduce the issue by following in your steps.

@Dubz
Copy link
Author

Dubz commented Apr 4, 2020

@Dubz I apologize if it seemed to you like we don't want Ed25519 to work, that is not the case. I implemented and tested a big part of it myself.

You asked me to use PuTTYgen for you since apparently it's too difficult to launch, tick a radial button, wiggle your mouse, and save each file.

The reason I asked you to do this is because I tested ED25519 with various input files, and couldn't reproduce your issue. Since I wasn't working on Windows at the time, I didn't have PuTTY installed.

That is a fair enough reason for this and I can understand. I apologize for being short about it before, just had a trend of various "nobody is getting anything done around here" going on and my patience was running rather thin at the time for simple things.

I downloaded PuTTYgen, started it, generated a key, and exported it. It seems that none of the formats provided by the PuTTYgen GUI are standard key formats. That makes sense, given that PuTTYgen is not a general purpose key management tool, but only designed for SSH. Node.js is not an SSH tool, and won't accept non-standard key formats.

That would make sense, and I was slightly aware of that, but wasn't fully sure if that would be an actual issue or not. I think I just had different expectations at the time.

I know that you have moved on, but how did you convert these SSH files to actual keys before passing them to Node.js? Just so I can reproduce the issue by following in your steps.

Referencing the lines here, I just loaded them as text:

// Load the files (that exist)
key.pem = (fs.existsSync("./" + key.name + ".pem")) ? fs.readFileSync("./" + key.name + ".pem") : false;
key.ppk = (fs.existsSync("./" + key.name + ".ppk")) ? fs.readFileSync("./" + key.name + ".ppk") : false;
key.pub = (fs.existsSync("./" + key.name + ".pub")) ? fs.readFileSync("./" + key.name + ".pub") : false;

Later loading the contents to the object, with the given password at the top of the script:

key.private = crypto.createPrivateKey({key: key.pem, passphrase: key.passphrase});

So in other words, the value for the first item key was the contents of the file saved from PuTTYgen, and the password was what was set in PuTTYgen before saving.

IIRC, the end goal was to have a private key on each server to "identify" itself, and a public key on the receiving ends to verify its signature. Basically similar to how known_hosts works via SSH, but across Node.JS connections. Wasn't sure of the best route to implement this at that time. Even if this isn't the best method, it would still be good to learn as to why that wasn't working for me. The process on the signing side would be, load the private key file, sign data, send it over. Receiving server would use that servers public key to then verify the signed data's authenticity.
The main issue still being making that public key object from the public key file:

key.public = crypto.createPublicKey({key: key.pub});

(again, the text contents of the key file)

It's been so long I'm not really sure what was working and what wasn't, but I do remember testing both RSA and ED25519 versions, as well as using the private key object to generate the public key object (not ideal, obviously), and using the public key file to generate the public key object.

@tniessen
Copy link
Member

tniessen commented Apr 5, 2020

@Dubz Thanks for your patience and the explanation :)

The problem: The files produced by PuTTYgen are not really keys, so Node.js won't recognize them. They are really just meant to be used with SSH tools. If you open them in a text editor, you'll notice that they begin with

---- BEGIN SSH2 PUBLIC KEY ----

or

-----BEGIN OPENSSH PRIVATE KEY-----

The solution: What you wanted to do is perfectly reasonable and possible. You could either somehow convert the SSH keys into real keys, or you could use tools such as openssl to produce the keys:

openssl genpkey -algorithm ed25519 -outform PEM -out private.pem
openssl pkey -in private.pem -pubout -out public.pem

(Or generate the keys from within Node.js, of course.)

These files are actual keys, so they begin with the usual

-----BEGIN PUBLIC KEY-----

and

-----BEGIN PRIVATE KEY-----

@alesmenzel
Copy link

This still doesn't work on node 14.15.1 with keys generated with ssh-keygen -t ed25519, it looks to me nodejs should support those.

Repro:

const crypto = require('crypto')
const fs = require('fs')

const publicKey = fs.readFileSync('id_node_test_ed25519', { encoding: 'utf-8' })
const privateKey = fs.readFileSync('id_node_test_ed25519.pub', {
	encoding: 'utf-8',
})

const message = 'Hello world!'
console.log(message)

const signature = crypto.sign(null, Buffer.from(message), privateKey)
console.log(signature)

const verified = crypto.verify(null, Buffer.from(message), publicKey, signature)
console.log('Match:', verified)

test private key:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACB5fUJwP/RSBUfbkEmL/9VEPu38KxHLwcngUdEBI//ILQAAAJiZVyEDmVch
AwAAAAtzc2gtZWQyNTUxOQAAACB5fUJwP/RSBUfbkEmL/9VEPu38KxHLwcngUdEBI//ILQ
AAAEDIn37veTsezaiUpPrlfjRdLt/MGvdtn674olN1xVeC9nl9QnA/9FIFR9uQSYv/1UQ+
7fwrEcvByeBR0QEj/8gtAAAAE2FsZXMubWVuemVsQG1lbnplbGEBAg==
-----END OPENSSH PRIVATE KEY-----

test public key:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHl9QnA/9FIFR9uQSYv/1UQ+7fwrEcvByeBR0QEj/8gt xxx

@tniessen
Copy link
Member

@alesmenzel Please read #2203 (comment) (the comment right above yours). The private key and public key files produced by ssh-keygen are specific to OpenSSH and not supported by other tools and libraries, including Node.js.

Copy link

It seems there has been no activity on this issue for a while, and it is being closed in 30 days. If you believe this issue should remain open, please leave a comment.
If you need further assistance or have questions, you can also search for similar issues on Stack Overflow.
Make sure to look at the README file for the most updated links.

@github-actions github-actions bot added the stale label Oct 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants