How to Use Hexadecimal Ed25519 Public Keys in Node.js

Friday, Jan 28th 2022

When dealing with Ed25519 in most cryptographic packages, signing keys and verify keys are encoded in hexadecimal. But unlike most, Node uses DER-encoding, and does not support hexadecimal encoded keys. Node not supporting hex-encoded public keys is a big point of confusion for Keygen users licensing apps built on Node.

Today, we're going to learn how to use a hex Ed25519 key in Node.

Using your DER-encoded Ed25519 key

To save our users the headache, we've gone ahead and included a DER-encoded Ed25519 public key, available in your account settings. You can utilize this in Node, avoiding the pitfalls that the rest of this article attempts to work around.

But if you're curious, or you're using an Ed25519 key that isn't from Keygen, read on to learn how to convert a hexadecimal key to DER format.

Converting a hex key to DER format

The DER format is made up of a few parts. First, is the IOD, which is more or less a sequence of bytes identifying the cryptographic algorithm.

Ed25519 has the following OID:

06 03 2B 65 70

We can use this OID to manually convert our hex-encoded public key into a DER-encoded key. In addition, we'll need to do a bit more magic by creating a sequence of bytes that define the key's length, which is always 32 bytes for Ed25519 public keys.

function toDER(hex) {
const key = Buffer.from(hex, 'hex')
 
// Ed25519's OID
const oid = Buffer.from([0x06, 0x03, 0x2B, 0x65, 0x70])
 
// Create a byte sequence containing the OID and key
const elements = Buffer.concat([
Buffer.concat([
Buffer.from([0x30]), // Sequence tag
Buffer.from([oid.length]),
oid,
]),
Buffer.concat([
Buffer.from([0x03]), // Bit tag
Buffer.from([key.length + 1]),
Buffer.from([0x00]), // Zero bit
key,
]),
])
 
// Wrap up by creating a sequence of elements
const der = Buffer.concat([
Buffer.from([0x30]), // Sequence tag
Buffer.from([elements.length]),
elements,
])
 
return der
}

Now if we try out our toDER function, we should be able to convert our hex-encoded verify key to DER-encoding, and use it in Node's crypto.createPublicKey function. This allows us to use crypto.verify(...), for example.

const key = toDER('6d28cf8e17e4682fbe6285e72b21aa26f094d8dbd18f7828358f822b428d069f')
const verifyKey = crypto.createPublicKey({
format: 'der',
type: 'spki',
key,
})

But if we pass in a couple different public keys, we may notice a pattern.

toDER('6d28cf8e17e4682fbe6285e72b21aa26f094d8dbd18f7828358f822b428d069f')
// => <Buffer 30 2a 30 05 06 03 2b 65 70 03 21 00
// 6d 28 cf 8e 17 e4 68 2f be 62 85 e7
// 2b 21 aa 26 f0 94 d8 db d1 8f 78 28
// 35 8f 82 2b 42 8d 06 9f>
toDER('51e46ec0a8dac6b00b921e80d53dd90cd922a7a7bda8bef32edef4b112da1716')
// => <Buffer 30 2a 30 05 06 03 2b 65 70 03 21 00
// 51 e4 6e c0 a8 da c6 b0 0b 92 1e 80
// d5 3d d9 0c d9 22 a7 a7 bd a8 be f3
// 2e de f4 b1 12 da 17 16>
toDER('08058583b5a58f5c847090d36cea298907ccf0de37b65ba63e10ed862e2cdc3b')
// => <Buffer 30 2a 30 05 06 03 2b 65 70 03 21 00
// 08 05 85 83 b5 a5 8f 5c 84 70 90 d3
// 6c ea 29 89 07 cc f0 de 37 b6 5b a6
// 3e 10 ed 86 2e 2c dc 3b>

The pattern? They all start with the following byte sequence!

30 2a 30 05 06 03 2b 65 70 03 21 00

This is the Ed25519 OID, and our sequence of bytes indicating that the key is 32 bytes in length. Since this will always be the case for Ed25519 verify keys, that means we can simplify things, avoiding our toDER function altogether!

const key = Buffer.concat([
Buffer.from('302a300506032b6570032100', 'hex'), // Static value
Buffer.from('6d28cf8e17e4682fbe6285e72b21aa26f094d8dbd18f7828358f822b428d069f', 'hex'),
])
 
const verifyKey = crypto.createPublicKey({
format: 'der',
type: 'spki',
key,
})

To use the public key to verify a signature, you'd use it like so.

const ok = crypto.verify(null, dataBytes, verifyKey, signatureBytes)

Converting a DER key to hex format

Now what about the reverse? Thankfully, converting a DER-encoded key to hexadecimal is much, much more straight forward. Since our DER key is simply a Buffer, we can easily convert to hex, or even base64.

key.toString('hex').substring(24)
// => 6d28cf8e17e4682fbe6285e72b21aa26f094d8dbd18f7828358f822b428d069f

We removed the first 24 characters of the string, the DER prefix.

Conclusion

Today, we covered how your Keygen account comes with a hexadecimal Ed25519 key, as well as a DER-encoded Ed25519 verify key for use within Node. And we learned how to convert Ed25519 verify keys between hexadecimal and DER encoding.

We also learned that Node, unlike so many other programming languages, does not support hexadecimal Ed25519 public keys.

Until next time.


If you find any errors in my code, or if you can think of ways to improve things, ping me via Twitter.