From: Loup Vaillant Date: Fri, 2 Dec 2022 22:45:45 +0000 (+0100) Subject: Safer interface for EdDSA X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=da7b5407d20329f21a53ea993f516fb55e2f5e26;p=Monocypher.git Safer interface for EdDSA Now the private key is 64 bytes, and is the concatenation of the seed and the public key just like Libsodium. The idea is to make sure users never sign messages with the wrong public key, which can leak the secret scalar and allow forgeries. Users who can't afford the overhead of storing 32 additional bytes for the secret key (say they need to burn the key into expensive fuses), they can always only store the first 32 bytes, and re-derive the entire key pair when they need it. TODO: update the manual. Fixes #240 --- diff --git a/src/monocypher.c b/src/monocypher.c index 14b9d72..f248df2 100644 --- a/src/monocypher.c +++ b/src/monocypher.c @@ -2234,12 +2234,17 @@ int crypto_eddsa_r_check(u8 r_check[32], const u8 public_key[32], return 0; } -void crypto_sign_public_key(u8 public_key[32], const u8 secret_key[32]) +void crypto_eddsa_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32]) { u8 a[64]; - crypto_blake2b(a, secret_key, 32); - crypto_eddsa_trim_scalar(a, a); - crypto_eddsa_scalarbase(public_key, a); + COPY(a, seed, 32); // a[ 0..31] = seed + crypto_wipe(seed, 32); + COPY(secret_key, a, 32); // secret key = seed + crypto_blake2b(a, a, 32); // a[ 0..31] = scalar + crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar + crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B + COPY(secret_key + 32, public_key, 32); // secret key includes public half + WIPE_BUFFER(a); } static void hash_reduce(u8 h[32], @@ -2257,46 +2262,96 @@ static void hash_reduce(u8 h[32], crypto_eddsa_reduce(h, hash); } -void crypto_sign(u8 signature[64], - const u8 secret_key[32], - const u8 public_key[32], - const u8 *message, size_t message_size) -{ - u8 a[64]; +// Digital signature of a message with from a secret key. +// +// The secret key comprises two parts: +// - The seed that generates the key (secret_key[ 0..31]) +// - The public key (secret_key[32..63]) +// +// The seed and the public key are bundled together to make sure users +// don't use mismatched seeds and public keys, which would instantly +// leak the secret scalar and allow forgeries (allowing this to happen +// has resulted in critical vulnerabilities in the wild). +// +// The seed is hashed to derive the secret scalar and a secret prefix. +// The sole purpose of the prefix is to generate a secret random nonce. +// The properties of that nonce must be as follows: +// - Unique: we need a different one for each message. +// - Secret: third parties must not be able to predict it. +// - Random: any detectable bias would break all security. +// +// There are two ways to achieve these properties. The obvious one is +// to simply generate a random number. Here that would be a parameter +// (Monocypher doesn't have an RNG). It works, but then users may reuse +// the nonce by accident, which _also_ leaks the secret scalar and +// allows forgeries. This has happened in the wild too. +// +// This is no good, so instead we generate that nonce deterministically +// by reducing modulo L a hash of the secret prefix and the message. +// The secret prefix makes the nonce unpredictable, the message makes it +// unique, and the hash/reduce removes all bias. +// +// The cost of that safety is hashing the message twice. If that cost +// is unacceptable, there are two alternatives: +// +// - Signing a hash of the message instead of the message itself. This +// is fine as long as the hash is collision resistant. It is not +// compatible with existing "pure" signatures, but at least it's safe. +// +// - Using a random nonce. Please exercise **EXTREME CAUTION** if you +// ever do that. It is absolutely **critical** that the nonce is +// really an unbiased random number between 0 and L-1, never reused, +// and wiped immediately. +// +// To lower the likelihood of complete catastrophe if the RNG is +// either flawed or misused, you can hash the RNG output together with +// the secret prefix and the beginning of the message, and use the +// reduction of that hash instead of the RNG output itself. It's not +// foolproof (you'd need to hash the whole message) but it helps. +// +// Signing a message involves the following operations: +// +// scalar, prefix = HASH(secret_key) +// r = HASH(prefix || message) % L +// R = [r]B +// h = HASH(R || public_key || message) % L +// S = ((h * a) + r) % L +// signature = R || S +void crypto_eddsa_sign(u8 signature [64], const u8 secret_key[32], + const u8 *message, size_t message_size) +{ + u8 a[64]; // secret scalar and prefix + u8 r[32]; // secret deterministic "random" nonce + u8 h[32]; // publically verifiable hash of the message (not wiped) + u8 R[32]; // first half of the signature (allows overlapping inputs) + crypto_blake2b(a, secret_key, 32); crypto_eddsa_trim_scalar(a, a); - u8 pk[32]; // not secret, not wiped - if (public_key == 0) { - crypto_eddsa_scalarbase(pk, a); - } else { - COPY(pk, public_key, 32); - } - - // Deterministic part of EdDSA: Construct a nonce by hashing the message - // instead of generating a random number. - // An actual random number would work just fine, and would save us - // the trouble of hashing the message twice. If we did that - // however, the user could fuck it up and reuse the nonce. - u8 r[32]; hash_reduce(r, a + 32, 32, message, message_size, 0, 0); - - // First half of the signature R = [r]B - u8 R[32]; // Not secret, not wiped crypto_eddsa_scalarbase(R, r); - - // Second half of the signature - u8 h_ram[32]; - hash_reduce(h_ram, R, 32, pk, 32, message, message_size); + hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size); COPY(signature, R, 32); - crypto_eddsa_mul_add(signature + 32, h_ram, a, r); // s = h_ram * a + r + crypto_eddsa_mul_add(signature + 32, h, a, r); WIPE_BUFFER(a); WIPE_BUFFER(r); - WIPE_BUFFER(h_ram); } -int crypto_check(const u8 signature[64], const u8 public_key[32], - const u8 *message, size_t message_size) +// To verify a signature, there are 3 steps: +// +// S, R = signature +// h = HASH(R || public_key || message) % L +// R' = [s]B - [h]public_key +// +// For the signature to be valid, **TWO** conditions must hold: +// +// - Computing R' must succeed. +// - R and R' must be identical (duh). +// +// Don't you **ever** forget to check the return value of +// `crypto_eddsa_r_check()`. +int crypto_eddsa_check(const u8 signature[64], const u8 public_key[32], + const u8 *message, size_t message_size) { u8 h_ram [32]; u8 r_check[32]; diff --git a/src/monocypher.h b/src/monocypher.h index 1fa9bce..2bfee26 100644 --- a/src/monocypher.h +++ b/src/monocypher.h @@ -183,19 +183,15 @@ void crypto_key_exchange(uint8_t shared_key [32], // Signatures (EdDSA with curve25519 + BLAKE2b) // -------------------------------------------- - -// Generate public key -void crypto_sign_public_key(uint8_t public_key[32], - const uint8_t secret_key[32]); - -// Direct interface -void crypto_sign(uint8_t signature [64], - const uint8_t secret_key[32], - const uint8_t public_key[32], // optional, may be 0 - const uint8_t *message, size_t message_size); -int crypto_check(const uint8_t signature [64], - const uint8_t public_key[32], - const uint8_t *message, size_t message_size); +void crypto_eddsa_key_pair(uint8_t secret_key[64], + uint8_t public_key[32], + uint8_t seed[32]); +void crypto_eddsa_sign(uint8_t signature [64], + const uint8_t secret_key[32], + const uint8_t *message, size_t message_size); +int crypto_eddsa_check(const uint8_t signature [64], + const uint8_t public_key[32], + const uint8_t *message, size_t message_size); //////////////////////////// /// Low level primitives /// diff --git a/src/optional/monocypher-ed25519.c b/src/optional/monocypher-ed25519.c index 7230e47..ec9d441 100644 --- a/src/optional/monocypher-ed25519.c +++ b/src/optional/monocypher-ed25519.c @@ -338,12 +338,17 @@ void crypto_hmac_sha512(u8 hmac[64], const u8 *key, size_t key_size, /////////////// /// Ed25519 /// /////////////// -void crypto_ed25519_public_key(u8 public_key[32], const u8 secret_key[32]) +void crypto_ed25519_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32]) { u8 a[64]; - crypto_sha512(a, secret_key, 32); - crypto_eddsa_trim_scalar(a, a); - crypto_eddsa_scalarbase(public_key, a); + COPY(a, seed, 32); // a[ 0..31] = seed + crypto_wipe(seed, 32); + COPY(secret_key, a, 32); // secret key = seed + crypto_sha512(a, a, 32); // a[ 0..31] = scalar + crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar + crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B + COPY(secret_key + 32, public_key, 32); // secret key includes public half + WIPE_BUFFER(a); } static void hash_reduce(u8 h[32], @@ -361,46 +366,27 @@ static void hash_reduce(u8 h[32], crypto_eddsa_reduce(h, hash); } -void crypto_ed25519_sign(u8 signature [64], - const u8 secret_key[32], - const u8 public_key[32], +void crypto_ed25519_sign(u8 signature [64], const u8 secret_key[32], const u8 *message, size_t message_size) { - u8 a[64]; + u8 a[64]; // secret scalar and prefix + u8 r[32]; // secret deterministic "random" nonce + u8 h[32]; // publically verifiable hash of the message (not wiped) + u8 R[32]; // first half of the signature (allows overlapping inputs) + crypto_sha512(a, secret_key, 32); crypto_eddsa_trim_scalar(a, a); - u8 pk[32]; // not secret, not wiped - if (public_key == 0) { - crypto_eddsa_scalarbase(pk, a); - } else { - COPY(pk, public_key, 32); - } - - // Deterministic part of EdDSA: Construct a nonce by hashing the message - // instead of generating a random number. - // An actual random number would work just fine, and would save us - // the trouble of hashing the message twice. If we did that - // however, the user could fuck it up and reuse the nonce. - u8 r[32]; hash_reduce(r, a + 32, 32, message, message_size, 0, 0); - - // First half of the signature R = [r]B - u8 R[32]; // Not secret, not wiped crypto_eddsa_scalarbase(R, r); - - // Second half of the signature - u8 h_ram[32]; - hash_reduce(h_ram, R, 32, pk, 32, message, message_size); + hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size); COPY(signature, R, 32); - crypto_eddsa_mul_add(signature + 32, h_ram, a, r); // s = h_ram * a + r + crypto_eddsa_mul_add(signature + 32, h, a, r); WIPE_BUFFER(a); WIPE_BUFFER(r); - WIPE_BUFFER(h_ram); } -int crypto_ed25519_check(const u8 signature [64], - const u8 public_key[32], +int crypto_ed25519_check(const u8 signature[64], const u8 public_key[32], const u8 *message, size_t message_size) { u8 h_ram [32]; diff --git a/src/optional/monocypher-ed25519.h b/src/optional/monocypher-ed25519.h index 8ee14e1..5caf2c4 100644 --- a/src/optional/monocypher-ed25519.h +++ b/src/optional/monocypher-ed25519.h @@ -105,13 +105,13 @@ void crypto_hmac_sha512(uint8_t hmac[64], // Ed25519 // ------- - -void crypto_ed25519_public_key(uint8_t public_key[32], - const uint8_t secret_key[32]); - +// Signatures (EdDSA with curve25519 + BLAKE2b) +// -------------------------------------------- +void crypto_ed25519_key_pair(uint8_t secret_key[64], + uint8_t public_key[32], + uint8_t seed[32]); void crypto_ed25519_sign(uint8_t signature [64], const uint8_t secret_key[32], - const uint8_t public_key[32], // optional, may be 0 const uint8_t *message, size_t message_size); int crypto_ed25519_check(const uint8_t signature [64], const uint8_t public_key[32], diff --git a/tests/test.c b/tests/test.c index 4606c88..0304e8b 100644 --- a/tests/test.c +++ b/tests/test.c @@ -214,24 +214,36 @@ static void edDSA(vector_reader *reader) vector public_k = next_input(reader); vector msg = next_input(reader); vector out = next_output(reader); - u8 out2[64]; - - // Sign with cached public key, then by reconstructing the key - crypto_sign(out.buf, secret_k.buf, public_k.buf, msg.buf, msg.size); - crypto_sign(out2 , secret_k.buf, 0 , msg.buf, msg.size); - // Compare signatures (must be the same) - if (memcmp(out.buf, out2, out.size)) { - printf("FAILURE: reconstructing public key" - " yields different signature\n"); - exit(1); - } + u8 fat_secret_key[64]; + memcpy(fat_secret_key , secret_k.buf, 32); + memcpy(fat_secret_key + 32, public_k.buf, 32); + crypto_eddsa_sign(out.buf, fat_secret_key, msg.buf, msg.size); } static void edDSA_pk(vector_reader *reader) { vector in = next_input(reader); vector out = next_output(reader); - crypto_sign_public_key(out.buf, in.buf); + u8 seed [32]; + u8 secret_key[64]; + u8 public_key[32]; + memcpy(seed, in.buf, 32); + crypto_eddsa_key_pair(secret_key, public_key, seed); + memcpy(out.buf, public_key, 32); + + u8 zeroes[32] = {0}; + if (memcmp(seed, zeroes, 32)) { + printf("FAILURE: seed has not been wiped\n"); + exit(1); + } + if (memcmp(secret_key, in.buf, 32)) { + printf("FAILURE: first half of secret key is not the seed\n"); + exit(1); + } + if (memcmp(secret_key + 32, public_key, 32)) { + printf("FAILURE: second half of secret key is not the public key\n"); + exit(1); + } } static void ed_25519(vector_reader *reader) @@ -240,24 +252,36 @@ static void ed_25519(vector_reader *reader) vector public_k = next_input(reader); vector msg = next_input(reader); vector out = next_output(reader); - u8 out2[64]; - - // Sign with cached public key, then by reconstructing the key - crypto_ed25519_sign(out.buf, secret_k.buf, public_k.buf, msg.buf, msg.size); - crypto_ed25519_sign(out2 , secret_k.buf, 0 , msg.buf, msg.size); - // Compare signatures (must be the same) - if (memcmp(out.buf, out2, out.size)) { - printf("FAILURE: reconstructing public key" - " yields different signature\n"); - exit(1); - } + u8 fat_secret_key[64]; + memcpy(fat_secret_key , secret_k.buf, 32); + memcpy(fat_secret_key + 32, public_k.buf, 32); + crypto_ed25519_sign(out.buf, fat_secret_key, msg.buf, msg.size); } static void ed_25519_pk(vector_reader *reader) { vector in = next_input(reader); vector out = next_output(reader); - crypto_ed25519_public_key(out.buf, in.buf); + u8 seed [32]; + u8 secret_key[64]; + u8 public_key[32]; + memcpy(seed, in.buf, 32); + crypto_ed25519_key_pair(secret_key, public_key, seed); + memcpy(out.buf, public_key, 32); + + u8 zeroes[32] = {0}; + if (memcmp(seed, zeroes, 32)) { + printf("FAILURE: seed has not been wiped\n"); + exit(1); + } + if (memcmp(secret_key, in.buf, 32)) { + printf("FAILURE: first half of secret key is not the seed\n"); + exit(1); + } + if (memcmp(secret_key + 32, public_key, 32)) { + printf("FAILURE: second half of secret key is not the public key\n"); + exit(1); + } } static void ed_25519_check(vector_reader *reader) @@ -748,16 +772,23 @@ static int p_eddsa_roundtrip() int status = 0; FOR (i, 0, MESSAGE_SIZE) { RANDOM_INPUT(message, MESSAGE_SIZE); - RANDOM_INPUT(sk, 32); - u8 pk [32]; crypto_sign_public_key(pk, sk); - u8 signature[64]; crypto_sign(signature, sk, pk, message, i); - status |= crypto_check(signature, pk, message, i); + RANDOM_INPUT(seed, 32); + u8 sk [64]; + u8 pk [32]; + u8 signature[64]; + crypto_eddsa_key_pair(sk, pk, seed); + crypto_eddsa_sign(signature, sk, message, i); + status |= crypto_eddsa_check(signature, pk, message, i); // reject forgeries u8 zero [64] = {0}; - u8 forgery[64]; FOR (j, 0, 64) { forgery[j] = signature[j] + 1; } - status |= !crypto_check(zero , pk, message, i); - status |= !crypto_check(forgery, pk, message, i); + status |= !crypto_eddsa_check(zero , pk, message, i); + FOR (j, 0, 64) { + u8 forgery[64]; + memcpy(forgery, signature, 64); + forgery[j] = signature[j] + 1; + status |= !crypto_eddsa_check(forgery, pk, message, i); + } } printf("%s: EdDSA (roundtrip)\n", status != 0 ? "FAILED" : "OK"); return status; @@ -773,7 +804,7 @@ static int p_eddsa_random() RANDOM_INPUT(message, MESSAGE_SIZE); RANDOM_INPUT(pk, 32); RANDOM_INPUT(signature , 64); - status |= ~crypto_check(signature, pk, message, MESSAGE_SIZE); + status |= ~crypto_eddsa_check(signature, pk, message, MESSAGE_SIZE); } // Testing S == L (for code coverage) RANDOM_INPUT(message, MESSAGE_SIZE); @@ -787,7 +818,7 @@ static int p_eddsa_random() 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, }; - status |= ~crypto_check(signature, pk, message, MESSAGE_SIZE); + status |= ~crypto_eddsa_check(signature, pk, message, MESSAGE_SIZE); printf("%s: EdDSA (random)\n", status != 0 ? "FAILED" : "OK"); return status; @@ -801,11 +832,13 @@ static int p_eddsa_overlap() #undef INPUT_SIZE #define INPUT_SIZE (MESSAGE_SIZE + (2 * 64)) // total input size RANDOM_INPUT(input, INPUT_SIZE); - RANDOM_INPUT(sk , 32 ); - u8 pk [32]; crypto_sign_public_key(pk, sk); + RANDOM_INPUT(seed, 32); + u8 sk [64]; + u8 pk [32]; u8 signature[64]; - crypto_sign(signature, sk, pk, input + 64, MESSAGE_SIZE); - crypto_sign(input+i , sk, pk, input + 64, MESSAGE_SIZE); + crypto_eddsa_key_pair(sk, pk, seed); + crypto_eddsa_sign(signature, sk, input + 64, MESSAGE_SIZE); + crypto_eddsa_sign(input+i , sk, input + 64, MESSAGE_SIZE); status |= memcmp(signature, input + i, 64); } printf("%s: EdDSA (overlap)\n", status != 0 ? "FAILED" : "OK"); @@ -1061,11 +1094,12 @@ static int p_from_eddsa() { int status = 0; FOR (i, 0, 32) { - RANDOM_INPUT(ed_private, 32); - u8 ed_public[32]; crypto_sign_public_key (ed_public, ed_private); - u8 x_private[32]; crypto_from_eddsa_private(x_private, ed_private); - u8 x_public1[32]; crypto_from_eddsa_public (x_public1, ed_public); - u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private); + RANDOM_INPUT(ed_seed, 32); + u8 secret [64]; + u8 public [32]; crypto_eddsa_key_pair(secret, public, ed_seed); + u8 x_private[32]; crypto_from_eddsa_private(x_private, secret); + u8 x_public1[32]; crypto_from_eddsa_public (x_public1, public); + u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private); status |= memcmp(x_public1, x_public2, 32); } printf("%s: from_eddsa\n", status != 0 ? "FAILED" : "OK"); @@ -1076,11 +1110,12 @@ static int p_from_ed25519() { int status = 0; FOR (i, 0, 32) { - RANDOM_INPUT(ed_private, 32); - u8 ed_public[32]; crypto_ed25519_public_key (ed_public, ed_private); - u8 x_private[32]; crypto_from_ed25519_private(x_private, ed_private); - u8 x_public1[32]; crypto_from_ed25519_public (x_public1, ed_public); - u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private); + RANDOM_INPUT(ed_seed, 32); + u8 secret [64]; + u8 public [32]; crypto_ed25519_key_pair(secret, public, ed_seed); + u8 x_private[32]; crypto_from_ed25519_private(x_private, secret); + u8 x_public1[32]; crypto_from_ed25519_public (x_public1, public); + u8 x_public2[32]; crypto_x25519_public_key (x_public2, x_private); status |= memcmp(x_public1, x_public2, 32); } printf("%s: from_ed25519\n", status != 0 ? "FAILED" : "OK");