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

It is awkward to instantiate a SigningKey from its value. #419

Open
dmerejkowsky opened this issue Mar 27, 2018 · 4 comments
Open

It is awkward to instantiate a SigningKey from its value. #419

dmerejkowsky opened this issue Mar 27, 2018 · 4 comments

Comments

@dmerejkowsky
Copy link

Use case:

We have a signing keypair that has been generated by an other program in a different language, and we need to sign and verify messages with them.

The naive way does not work:

message = b'this is a message'
raw_signing_key = b'....' # a bunch of bytes of length 64
signing_key = SigningKey(raw_signing_key, encoder=RawEncoder)
signing_key.sign(message)

That's because the SigningKey constructor expects a seed, not the value:

# in src/nacl/signing.py
class SigningKey(...):

    def __init__(self, seed, encoder=encoding.RawEncoder):
        ...
        public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)

We have a workaround, though:

We assing the _signing_key variable directly:

message = b'this is a message'
raw_signing_key = b'....' # a bunch of bytes of length 64
fake_seed = b"\0" * 32
signing_key = nacl.signing.SigningKey(fake_seed)
signing_key._signing_key = raw_signing_key
signing_key.sign(message)

And then we can verify the signature by instantiating a VerifyKey directly:

raw_verify_key = b'....' # bunch of bytes of length 32
verify_key = nacl.signing.VerifyKey(public_key, encoder=RawEncoder)

Of course, this is a bad idea because if you use signing_key.verify_key you get the wrong key ...

Proposed fix

Maybe we could have something like this instead ?

class SigningKey:
    @classmethod
    def from_value(signing_key):
           res = cls()
           res._signing_key = signing_key
           res.verify_key = VerifyKey(bindings.crypto_sign_ed25519_sk_to_pk(signing_key))

And then:

signing_key = SigningKey.from_value(raw_signing_key)

Thoughts ?

@lmctv
Copy link
Contributor

lmctv commented Mar 27, 2018

@dmerejkowsky would you mind giving some more details about the raw keypairs you are dealing with, and in particular the environment in which they have been generated in the first place? Are the secret and the public part both available, or only the secret? Thank you.

@dmerejkowsky
Copy link
Author

The keys have been generated in Javascript (using libsodium-wrapper by the way).

The public and private keys are both available.

Since I opened the bug we thought more about the problem a bit more and discovered we could just use the lower-lever API in nacl.bindings directly:

from nacl.bindings.crypto_sign import crypto_sign, crypot_sign_open, crypto_sign_BYTES

# generate a detached signature:
combined = crypto_sign(message, raw_signing_key)
return combined[:crypto_sign_BYTES]

# check the signature is correct
combined = signature + message
crypto_sign_open(combined, raw_verify_key)

So, feel free to close this issue :)

@lmctv
Copy link
Contributor

lmctv commented Apr 1, 2018

@dmerejkowsky Thank you very much for following up! The reason for asking whether you were in possession of the verification key or not was to understand if an API requiring both would be enough for your needs.

@HerrMuellerluedenscheid

As I was stuck on the same problem (Seed != SecretKey) when trying to sign and validate using a private/secret keypair generated in another language I'd like to add my solution hoping that it serves others:

PRIVATE_KEY and PUBLIC_KEY are environment variables that hold the keys as base64 encoded strings.

import base64
import os

from nacl.encoding import Base64Encoder
from nacl import bindings
from nacl.signing import VerifyKey, SigningKey

# load the private key
private_key = os.getenv("PRIVATE_KEY")
hex_key = base64.b64decode(private_key)
seed = bindings.crypto_sign_ed25519_sk_to_seed(hex_key)
signing_key = SigningKey(seed)

# load the public key
public_key = os.getenv("PUBLIC_KEY")
verify_key = VerifyKey(public_key, Base64Encoder)

# test both keys:
message = b"ASDF"
signature = signing_key.sign(message).signature
verify_key.verify(message, signature)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants