dchest / tweetnacl-js

Port of TweetNaCl cryptographic library to JavaScript
https://tweetnacl.js.org
The Unlicense
1.75k stars 292 forks source link

Deriving Private Keys from Public Keys? #205

Closed AlbertRenshaw closed 3 years ago

AlbertRenshaw commented 3 years ago

I might be misunderstanding how this works but it seems that many private keys produce the same public keys and that the public keys can be converted to any of these private keys at will?

Suppose I have a secret key which I am representing in hex, 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

If I convert this to a keypair using the following:

var secretKeyHex = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

var secretKey = Buffer.from(secretKeyHex, 'hex');

var keypair = nacl.sign.keyPair.fromSecretKey(secretKey);

Then the keypair's public key will be (in base64) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

If I start incrementing the secret key 000...0001, 000...0002, 000...0003, etc the public key increments as follows: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM=, etc.

With some simple work it seems I can convert any public key into the last half of the secretKey w/ a simple algorithm, the first half of the secret key seems to not matter as it will produce the same publicKey either way?

That is, the following secret keys all produce the same public keys: 000...000, 100...000, 200...000, 300...000, etc.

Am I missing something?

dchest commented 3 years ago

fromSecretKey doesn't generate a public key, it just copies the public key part of the NaCl signing secret key (which is a concatetation of 32-byte private and 32-byte public parts) into the { publicKey, secretKey } structure.

See README for both functions:

nacl.sign.keyPair.fromSecretKey(secretKey)

Returns a signing key pair with public key corresponding to the given 64-byte secret key. The secret key must have been generated by nacl.sign.keyPair or nacl.sign.keyPair.fromSeed.

Perhaps, you wanted nacl.sign.keyPair.fromSeed?

AlbertRenshaw commented 3 years ago

Okay I think I understand my confusion now. The secret key is actually a concatenation A+B where B is the public key.

B is derived from A so you can't just make A anything. I can't just generate a random hex string and use it as a secret key as it would be an invalid secret key since A and B won't be properly correlated.

So is there a way to check, then, if someone has forged a secret key that has the same public key portion as someone else (B) but the private portion of the secret key (A) is invalidly generated? (Right now I just check by simply trying to sign and then open a dummy utf8 message with that forged keypair and I catch an error, is there a built in way to check?).

dchest commented 3 years ago

Check out nacl.sign.keyPair.fromSeed — it generates the full key pair from 32 bytes.