javascriptnode.jscryptographyed25519tweet-nacl

Discrepancy between nodejs crypto module and tweetnacl.js output


For example, I have this code in nodejs:

import crypto from 'crypto';

const pkey = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJC52rh4PVHgA/4p20mFhiaQ/iKtmr/XJWMtqAmdTaFw
-----END PRIVATE KEY-----`; // dummy key

function sign_msg(msg, key) {
    key = crypto.createPrivateKey(key);
    msg = crypto.sign(null, Buffer.from(msg), key).toString('base64');
    return msg;
}

console.log(sign_msg('hello', pkey)); // output: kNMsAGw9ssJ3lcsybuXHmhJp8REI5OKA8twcd24/NfyXDdbCyccpqNtXtWiWQuZqQBRnS8thWJTBewXnEfFrBw==

and same for browser (using tweetnacl.js) :

let pkey = 'MC4CAQAwBQYDK2VwBCIEIJC52rh4PVHgA/4p20mFhiaQ/iKtmr/XJWMtqAmdTaFw';

function sign_msg(msg, key) {
  TE = new TextEncoder();
  msg = TE.encode(msg);
  key = TE.encode(key);
  return btoa(String.fromCharCode.apply(null, nacl.sign.detached(msg, key)));
}

console.log(sign_msg('hello', pkey)); // output: wMqQdH+SBmSdlUMfgEr1MMKisZjqcqExzX1JbKfl3G6xwouKCKZL6/S8aYpNXadL4nYZrAaBxu9cWtdrl/vpAQ==

As you can see, both outputs different results. Both are using same Ed25519 key (generated using openssl) and the service I'm using it for accepts the nodejs crypto output, and not the tweetnacl. Not sure what I am doing wrong here.

Any help will be greatly appreciated. Thanks :)


Solution

  • Both codes use different key formats. The crypto-code uses a PEM encoded private key in PKCS#8 format, while the tweetnacl-code requires a raw Ed25519 secret key. This consists of the concatenation of a 32 bytes seed and the 32 bytes public key.
    The seed can be extracted from the PEM key, namely the last 32 bytes of the Base64 decoded body. From this, nacl.sign.keyPair.fromSeed() can be used to derive the corresponding key pair, which consists of the secret and public key.
    If the secret key derived in this way is used for signing, the result is the same signature that the crypto-code generates:

    var msg = nacl.util.decodeUTF8('hello')
    var pem = nacl.util.decodeBase64('MC4CAQAwBQYDK2VwBCIEIJC52rh4PVHgA/4p20mFhiaQ/iKtmr/XJWMtqAmdTaFw')
    var seed = pem.slice(-32)
    var keypair = nacl.sign.keyPair.fromSeed(seed)
    var secretKey = keypair.secretKey
    var signature = nacl.sign.detached(msg, secretKey)
    console.log(nacl.util.encodeBase64(signature)) // kNMsAGw9ssJ3lcsybuXHmhJp8REI5OKA8twcd24/NfyXDdbCyccpqNtXtWiWQuZqQBRnS8thWJTBewXnEfFrBw==
    <script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script>