LoupVaillant / Monocypher

An easy to use, easy to deploy crypto library
https://monocypher.org
Other
614 stars 80 forks source link

Using hmac sha 512 for ed25519 #175

Closed kingsleyh closed 4 years ago

kingsleyh commented 4 years ago

Hi

I'm trying to both understand and convert this code to monocypher:

const ED25519_CURVE = 'ed25519 seed';
const hmac = createHmac('sha512', ED25519_CURVE);
const I = hmac.update(Buffer.from(seed, 'hex')).digest();

I see in the ed25519.h there is this:

// HMAC SHA 512
// ------------
void crypto_hmac_sha512_init(crypto_hmac_sha512_ctx *ctx,
                             const uint8_t *key, size_t key_size);
void crypto_hmac_sha512_update(crypto_hmac_sha512_ctx *ctx,
                               const uint8_t *message, size_t  message_size);
void crypto_hmac_sha512_final(crypto_hmac_sha512_ctx *ctx, uint8_t hmac[64]);
void crypto_hmac_sha512(uint8_t hmac[64],
                        const uint8_t *key    , size_t key_size,
                        const uint8_t *message, size_t message_size);

Are these the correct functions that correspond to my javascript example? And is there an example anywhere of how to use them. Do I need to use the init,update,final functions to replicate the javascript?

Any help greatly appreciated

kingsleyh commented 4 years ago

I found some more info in the manual: https://monocypher.org/manual/optional/hmac

I'll try to figure it out from that

fscoto commented 4 years ago

My understanding of the node.js code is that it's doing HMAC-SHA512(key="ED25519_CURVE", seed), where seed comes from somewhere out of scope for this.

The equivalent of that in Monocypher with C would be with mac being the result of the operation:

const char szKey[] = "ed25519 seed";
const uint8_t bKey[12]; /* technically required because of strict aliasing */
uint8_t mac[64];

for (size_t i = 0; i < sizeof(bKey); ++i)
    bKey[i] = szKey[i];

crypto_hmac_sha512(mac,
                        bKey, sizeof(bKey),
                        seed, seed_len);

Note that crypto_hmac_sha512(3monocypher) is just a convenience function that performs crypto_hmac_sha512_init(), crypto_hmac_sha512_update() and crypto_hmac_sha512_final() all in one go so that you don't need to micromanage a context if you just need the HMAC of one buffer.

I hope that helps.


That said, I'm curious what you're trying to do there. The string “ed25519 seed” definitely sounds like you're doing something somewhat out of the ordinary and I'm curious as to what it is you're doing. Just in case you're doing something subtly bad.

kingsleyh commented 4 years ago

ok thanks so I think you are saying:


key = "ed25519 seed"
message = seed

void crypto_hmac_sha512(hash_out, key, key_len, message, message_len);

It would make more sense to me if it was:

key = seed
message = "ed25519 seed"
fscoto commented 4 years ago

In the code from your original post, the parameters map as such:

I just translated your code 1:1. The JavaScript code you've given does HMAC with 'ed25519 seed' as the key argument. So if you've been having issues because you're getting wrong results: That's probably the actual cause.

LoupVaillant commented 4 years ago

Hi @kingsleyh

Note that we're not exactly JavaScript specialists. I myself know very little, and the code you're trying to understand… I personally don't know what it does, so I cannot translate it. I can make guesses, but they won't be reliable.

You need to understand the JavaScript code yourself, so you can tell us what it does. We'll be happy to help you translate it then.

Right now, all I can say about this code is that something's fishy. Why is this code is talking about Ed25519 and HMAC? Even if you hash the message before signing the hash (instead of signing the message directly), you don't need HMAC, SHA-512 is enough. And what's the "seed" for? HMAC generally uses a key, and EdDSA has no need for a random seed —it's deterministic.

(Note: I have no idea how much you know about cryptography, but if you haven't followed a formal introduction yet, I strongly suggest you do. It's tedious, but unfortunately necessary. I currently recommend Crypto101 (PDF) or this Stanford course (videos).)

kingsleyh commented 4 years ago

Hi

Ok here is some more background:

I'm trying to implement this: https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-1-for-ed25519

and specifically the first part is Master Key Generation: https://github.com/satoshilabs/slips/blob/master/slip-0010.md#master-key-generation

The main aim is that given the reference test vectors that the output from the code should match the expected results specified in the test vectors 1 and 2.

There are 2 existing implementations I'm looking at for reference:

  1. javascript: https://github.com/alepop/ed25519-hd-key/blob/master/src/index.ts
  2. dart: https://github.com/alepop/dart-ed25519-hd-key/blob/master/lib/src/base.dart

both the dart and the javascript one when using the hmac sha512 produce the same result. But when using OpenSSL and Monocypher I get a different result. (At least OpenSSL and Monocypher agree with each other though)

so given this key and message:

  key = "ed25519 seed"
  message = "000102030405060708090a0b0c0d0e0f"

The digest should be:

2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e790046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb

for example some js: (https://npm.runkit.com/create-hmac)

var createHmac = require('create-hmac')
var hmac = createHmac('sha512', 'ed25519 seed')
const I = hmac.update(Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex')).digest()
const result = I.slice(0,64).toString('hex')

however with monocypher and openSSL I'm getting:

2f415ca1560fa1350d9ab22b3de4fec73bed2a6346601e9b17436f961b48fbccb1267fc885f16703526a8039a4605ca07fa53b91dbc1a9f1b67795cf5bae6ac8

I would have hoped that Dart and Javascript hmac sha512 would have been compatible with OpenSSL and Monocypher but either they are not - or I'm doing something wrong!

LoupVaillant commented 4 years ago

An incompatibility is extremely unlikely. You must be using the JavaScript API and Monocypher/OpenSSL differently. I'd first verify you didn't swapped hexadecimal and ASCII encodings.

Moreover, Have you tried the official test vectors from the RFC?

Can you show your C code, so we can compare?

fscoto commented 4 years ago

The following snippet of code generates and prints the expected value of 2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e790046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb:

#include "monocypher.h"
#include "monocypher-ed25519.h"
#include <stdio.h>

int
main(void)
{
    const char szKey[] = "ed25519 seed";
    uint8_t bKey[12];

    for (size_t i = 0; i < 12; ++i)
        bKey[i] = szKey[i];

    uint8_t msg[16] = { /* <-- message in binary */
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
    };
    uint8_t mac[64];

    crypto_hmac_sha512(mac, bKey, 12, msg, 16);
    for (size_t i = 0; i < 64; ++i)
        printf("%02x", mac[i]);
    puts("");
}

I didn't manage to reverse engineer the code that didn't work though. Hard to tell at this point.

kingsleyh commented 4 years ago

Thanks very much for confirming - I must be doing something wrong. I'm not a C programmer and I'm also not using the C version of Monocypher - just Crystal lang bindings so my code may not make any sense to you:

def go
  # fun hmac_sha512 = crypto_hmac_sha512(hmac : Uint8T[64], key : Uint8T*, key_size : SizeT, message : Uint8T*, message_size : SizeT)
  digest = uninitialized UInt8[64]
  key = "ed25519 seed"
  message = "000102030405060708090a0b0c0d0e0f"
  LibMonocypher.hmac_sha512(digest, key, key.bytesize, message, message.bytesize)
  digest.to_slice.hexstring
end

and the OpenSSL version:

puts OpenSSL::HMAC.hexdigest(:sha512, "ed25519 seed", "000102030405060708090a0b0c0d0e0f")
kingsleyh commented 4 years ago

I've got it working now thanks for the help:

def go
  # fun hmac_sha512 = crypto_hmac_sha512(hmac : Uint8T[64], key : Uint8T*, key_size : SizeT, message : Uint8T*, message_size : SizeT)
  digest = uninitialized UInt8[64]
  key = "ed25519 seed"
  message = "000102030405060708090a0b0c0d0e0f".hexbytes
  LibMonocypher.hmac_sha512(digest, key, key.bytesize, message, message.bytesize)
  digest.to_slice.hexstring
end

I was passing bytes instead of hexbytes for the message - obvious really!! the more I looked the less I saw! lol