dchest / tweetnacl-js

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

Sign messages with 32-bytes box priv and verify it #194

Closed username1565 closed 4 years ago

username1565 commented 4 years ago

Add ability to sign messages by using 32-bytes secretKey, generated by nacl.box.keyPair() or nacl.box.keyPair.fromSecretKey(32bytesSecretKey) and verify by 32-bytes publicKey. This is Tox private key and public key in ToxID: https://tox.chat/download.html

Full changes: nacl-fast.js: Lines 1969 - 2021: rewrite function "crypto_sign". Add optional parameters "direct" (to sign with private key directly, without hashing) and "rnd", to add optional random, if this 64 bytes was been specified. Lines 2415 - 2527: Add commented code to show this difference in details. Just uncomment this and see console.log (F12 button). Lines 2535 - 2598: Add functions to sign and verify messages by using nacl.box keyPair (32 bytes for each). This is not nacl.sign.keyPair, where secretKey.length = 64 bytes, and another publicKey. Lines 2600 - 2627: Add nacl.sign32 and nacl.verify32 methods. Lines 2633 - 2658: Add commented test code to test signing and verify signature, by using 32-bytes nacl.box keyPair.

https://github.com/username1565/nanoboard-javascript-captcha/commit/1bcefd221d430e630e8f4b999ce0eadfaa6e7e85

dchest commented 4 years ago

Thanks for the PR, but unfortunately, I will not merge it:

  1. I'd like to keep the functionality of TweetNaCl.js the same as the original tweetnacl.c version, with a few extra bits for convenience that don't involve new protocols or primitives.

  2. Signing with Curve25519 keys as opposed to Ed25519 is mostly used for legacy systems that already managed Curve25519 keys and then decided to add signing. For new protocols, the community has settled on using Ed25519 keys for DH instead, as implemented by ed2curve, since it's easier, compatible with existing code, and doesn't require a different signature format that include a sign bit.

PS I've implemented a similar scheme that you've sent for https://github.com/wavesplatform/curve25519-js by ripping out parts of TweetNaCl.js.

username1565 commented 4 years ago

@dchest, yes, this code from axlsign.js, and as you can see there is full backward-compatibility with old code, and two parameters in "crypto_sign"-function was been added as an optional parameters. Demo - here: https://username1565.github.io/nanoboard-javascript-captcha/#/sign The 32-bytes secretKey and 32-bytes publicKey - this is base64-encoded Tox-keypair, which can be generated using nacl.box.keyPair() or nacl.box.keyPair.fromSecretKey(32bytesSecretKey), and no need to generate the separate nacl.sign.keyPair() or nacl.sign.keyPair.fromSeed() which contains 64-bytes secretKey and another 32-bytes publicKey, just to sign the messages.

So one static box-keyPair with 32-byte-keys can sign/verify, can encrypt/decrypt, and can be used for Elliptic-Curve Diffie Hellman-KeyExchange, without need to regenerate this. All this options allow to use one keyPair in pre-shared public key-scheme to prevent the MITM-attacks.

So, I think, this full backward-compatible methods, will be better to include inside tweetnacl.c, nacl.c, libsodium, and another standard-libraries, of course, after audit the source code, and optimize this.

Best regards.

dchest commented 4 years ago

As said above: the currently recommended way to use a single key pair for both DH and signing is to use Ed25519 key pair for signing and convert it to DH key pair (via ed2curve.js in our case) for encryption, not vice versa as in you solution. It preserves the same property of having just a single key pair for both, but also doesn’t change the format of signatures or any of the library code.

See this example: https://github.com/dchest/ed2curve-js#example

username1565 commented 4 years ago

@dchest, is this possible to convert this keys back? Or is there some way to generate this nacl.sign.keyPair by using specified nacl.box.keyPair, or get some seed from .box-keypair to generate this sign-keys?

I see ed2curve, allow to get nacl.box.keyPair() from nacl.sign.keyPair(), this is a different two 32-bytes keys, but it cann't convert it back...

The main reason to create my commit this is because Tox-clients using nacl.box.keyPair(), not nacl.sign.keyPair(), and uTox, for example, saving 32-byte privkey in the profile-file tox_save.tox, as raw-bytes, and publicKey, as hex there is the part of ToxID.

dchest commented 4 years ago

It’s partially possible - in fact, that’s what the code you submitted does - but the public key misses a sign bit, which is why it’s added to the signature, changing its format.

Indeed, the scheme I mentioned cannot be used with already existing box keys (if that’s what Tox uses). For this, you can use your fork, but I don’t want to include it into TweetNacl.js directly.

username1565 commented 4 years ago

Из всего этого вот, я понял следующее... ed25519-ключи - далее sign-ключи, сurve25519-ключи - далее box-ключи.

Ну так вот... Несовместимость между sign и box-ключами - это результат хэширования при генерации sign-ключей! здесь, и здесь, при генерации sign.keyPair, вызывается функция crypto_sign_keypair, и вот в этом её месте внутрь d, записываются некие 64 байта, при выводе которых - выдаётся значение, эквивалентное вот этой функции. Я так понимаю, это есть sha512-хэш.

Если это хэширование, внутри функции crypto_sign_keypair, опционально отключить (с сохранением обратной совместимости, разумеется), перед умножением, записав в d, вместо хэша - первые 32 байта sk, то можно будет, при указании в значении аргумента seeded, этой функции - 32-байтного .box-secretKey, получить, на выходе её, 64-байтный sign-secretKey, состоящий из двух компонент: указанный и неизменнный box.secretKey и вычисленный корректно из него - sign.publicKey. Этот 64-байтный sign-secretKey будет возвращать полу-корректную sign-keyPair - вот таким вот образом, так как в этой функции - никакое хэширование не используется вовсе.

Далее, этим ключём, можно подписывать сообщения, но опять же, если (опционально) - отключить хэширование в этом месте, как, впрочем и сделано, у меня, здесь.

Таким образом, становится возможной - следующая конвертация: Из сгенерированной sign-keyPair, при помощи ed2curve.js, возможно получить box-keyPair, но конвертировать box-keyPair в sign-keyPair - уже нельзя. Можно только прямо из box-secretKey, вычислить корректный и ему соответствующий - sign-publicKey, и оставить этот 32-байтный box-secretKey в составе 64-байтного sign-secretKey-я, получив sign-keypair из него, которой уже можно будет подписывать, и проверять подпись, просто пихая первую часть (32-байтный box-secretKey), опять же, опционально - directly, и без этого вот, долбанного хэширования...

В общем, загогулина c sign-bit при конвертации box-publicKey в sign-publicKey - разруливается вот таким вот образом, через генерацию пары sign-ключей при помощи box-secretKey. Но, вот эта пара, она какая-то полу-валидная, и получить корректный sign-secretKey из box-secretKey-я, может быть и можно, но сложно, из-за этого вот вписанного там - sha512-хэширования.

Вцелом, следовало бы добавить чуток больше опциональных параметров, и сделать либу более гибкой, однако использование прямой (directly) подписи ключём, без хэширования, да ещё и по дефолту - может сделать ключи, уязвимыми - к атакам, вроде вот этой, поэтому если надумаете, в будущем как-то обратно-несовместимо менять протоколы какие-то, то это следовало бы делать, только - после тщательного аудита всех исходных кодов. А так, парочку опциональных параметров и методов, можно было бы и натыкать туда, оставив при этом, всё максимально обратно-совместимым, и чтобы всё изначальное - было по дефолту, ведь много всяких софтин юзают эти либы.