Dfns supports the following asymmetric key algorithms for credentials:
ECDSA
EDDSA
RSA
Dfns supports many different curves. You can use any curve that is supported by Node JS' crypto library.
Dfns recommends the following curves / modulus lengths:
Examples
# Recommended: Generate RSA Private Keyopensslgenrsa-outrsa2048.pem2048# Generate the Public Keyopensslpkey-inrsa2048.pem-pubout-outrsa2048.public.pem#OR # Generate a ECDSA Private Key# NOTE: This is not the key for the blockchain, only for the API.opensslecparam-genkey-nameprime256v1-noout-outprime256v1.pem# Generate the Public Keyopensslpkey-inprime256v1.pem-pubout-outprime256v1.public.pem#OR # Generate a EDDSA Private Key# NOTE: This is not the key for the blockchain, only for the API.# NOTE: EDDSA keys do not work in Postman!opensslgenpkey-algorithmEd25519-outed25519key.pem# Generate the Public Keyopensslpkey-ined25519key.pem-pubout-outed25519key.public.pem
On MacOS you may need to update your openssl version to add support for EDDSA keys
// Code taken from https://github.com/mdn/dom-examples/blob/main/web-crypto/export-key/pkcs8.jsfunctionab2str(buf) {returnString.fromCharCode.apply(null,newUint8Array(buf));}asyncfunctionexportPrivateKey(key) {constexported=awaitwindow.crypto.subtle.exportKey('pkcs8', key)constexportedAsString=ab2str(exported)constexportedAsBase64=window.btoa(exportedAsString)return`-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`}asyncfunctionexportPublicKey(key, format) {constexported=awaitwindow.crypto.subtle.exportKey('spki', key)constexportedAsString=ab2str(exported)constexportedAsBase64=window.btoa(exportedAsString)return`-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`}// EDDSA Key/* EDDSA support in Web Crypto is experimental and may not be present in all browsers See: https://nodejs.org/api/webcrypto.html#ed25519ed448x25519x448-key-pairs*/consteddsaKey=awaitwindow.crypto.subtle.generateKey({ name:'Ed25519' },true, ['sign','verify'])consteddsaPublicKey=awaitexportPublicKey(eddsaKey)consteddsaPrivateKey=awaitexportPrivateKey(eddsaKey)// ECDSA Keyconst ecdsaKey = await window.crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify'])
constecdsaPublicKey=awaitexportPublicKey(ecdsaKey)constecdsaPrivateKey=awaitexportPrivateKey(ecdsaKey)// RSA KeyconstrsaKey=awaitwindow.crypto.subtle.generateKey( { name:'RSA-PSS', modulusLength:2048, publicExponent:newUint8Array([1,0,1]), hash:"SHA-256", },true, ['sign','verify'])constrsaPublicKey=awaitexportPublicKey(rsaKey)constrsaPrivateKey=awaitexportPrivateKey(rsaKey)
# Generate a ECDSA Private Keyawskmscreate-key--key-specECC_NIST_P256--key-usageSIGN_VERIFY# Get the Public Keyawskmsget-public-key--key-id<KEY_ID># Generate a RSA Private Keyawskmscreate-key--key-specRSA_2048--key-usageSIGN_VERIFY# Get the Public Keyawskmsget-public-key--key-id<KEY_ID>
Signature Format
An ECDSA (which includes EDDSA) signature consists of two values; a r and a s. Different crypto libraries use different formats for encoding these two values. The two most popular formats are ASN.1 / DER format and raw format.
Dfns APIs expects ECDSA/EDDSA signatures to use the ASN. / DER format.
Raw Signatures
With raw, the r and s values are directly concatenated together to form the signature. Each value must be exactly 32 bytes long, with 0s added to the beginning of the r/s if it is less then 32 bytes.
ASN.1 / DER
With ASN.1 / DER, the values are encoded using a deterministic format. The first byte is a magic value (0x30) that is used to identify the encoding. The second byte is the remaining length of the signature. The remaining bytes are the encoded r and s values. Each one is formated with its first byte being a magic value / separator (0x02). The second byte being the length of the value. And the remaining bytes being the value as a minimal-sized signed big-endian hex number. This means the number has all leading zeros removed, then the first byte must be a positive number (0x00-0x7F). If the minimized number starts with a negative value (0x80-0xFF) a zero is added to the beginning of the number. This means the final length of the value could be anywhere between 1 bytes and 33 bytes.
Converting from Raw to ASN.1
If your encryption library uses raw format you can convert it to ASN.1 DER using the following code:
functionminimizeBigInt(value) {if (value.length===0) {return value }constminValue= [0,...value]for (let i =0; i <minValue.length; ++i) {if (minValue[i] ===0) {continue }if (minValue[i] >0x7f) {returnminValue.slice(i-1) }returnminValue.slice(i) }returnnewUint8Array([0])}functionrawSignatureToAns1(rawSignature) {if (rawSignature.length!==64) {console.log(rawSignature.length)returnnewUint8Array([0]) }constr=rawSignature.slice(0,32)consts=rawSignature.slice(32)constminR=minimizeBigInt(r)constminS=minimizeBigInt(s)returnnewUint8Array([0x30,minR.length+minS.length+4,0x02,minR.length,...minR,0x02,minS.length,...minS ])}
Base64 and Base64Url Encoding
Base64 is a popular way of encoding large values into a string that contains only printable characters.
Base64Url is an extension of Base64 encoding, but replaces characters that are not safe in URLs with different characters. It also minimizes the string, removing the extra padding characters. The Dfns auth system leverages Base64Url encoding in many different places.
In nodeJS, Buffer has built-in support for Base64Url as an encoding type.
// Convert a string to a base64url stringBuffer.from('somerandomvalue').toString('base64url')// Convert a base64url string to a stringBuffer.from('c29tZXJhbmRvbXZhbHVl','base64url').toString()
For places where Buffer is not available, or an older Buffer implementation is used, this code can be used to convert a value to a Base64Url encoded string: