Necesito firmar un mensaje con la función crypto.sign() en NodeJS para obtener un JWT válido. Tengo una clave privada (base 64) como esta:
Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==
Y traté de obtener una firma:
const getJWT = () => { const privateKey = "Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A=="; const payload = { iss: "test", aud: "test.com", iat: 1650101178, exp: 1650101278, sub: "12345678-1234-1234-1234-123456789123" }; const token = encode(payload, privateKey); return token }; const encode = (payload, key) => { const header = { typ: "JWT", alg: "EdDSA" }; const headerBase64URL = base64url(JSON.stringify(header)); const payloadBase64URL = base64url(JSON.stringify(payload)); const msg = Buffer.from(`${headerBase64URL}.${payloadBase64URL}`); const keyDecoded = Buffer.from(key, "base64"); const signature = crypto.sign("Ed25519", msg, keyDecoded); //Here is the problem const signatureBase64url = base64url(Buffer.from(signature)); return `${msg}.${signatureBase64url}`; };
Recibí este error:
internal/crypto/sig.js:142 return _signOneShot(keyData, keyFormat, keyType, keyPassphrase, data, ^ Error: error:0909006C:PEM routines:get_name:no start line library: 'PEM routines', function: 'get_name', reason: 'no start line', code: 'ERR_OSSL_PEM_NO_START_LINE'
¿Cómo puedo adaptar mi clave privada a un formato válido?
El método crypto.sign()
requiere para Ed25519 una clave privada en formato PKCS#8. Su clave es una clave sin procesar que consta de la concatenación de la clave privada sin procesar de 32 bytes y la clave pública sin procesar de 32 bytes, codificada en base64. Una clave PKCS#8 codificada por DER se puede derivar e importar de la siguiente manera:
302e020100300506032b657004220420
En consecuencia, la importación de clave en getJWT()
debe cambiarse de la siguiente manera:
const privateKey = toPkcs8der('Dm2xriMD6riJagld4WCA6zWqtuWh40UzT/ZKO0pZgtHATOt0pGw90jG8BQHCE3EOjiCkFR2/gaW6JWi+3nZp8A==');
con
const toPkcs8der = (rawB64) => { var rawPrivate = Buffer.from(rawB64, 'base64').subarray(0, 32); var prefixPrivateEd25519 = Buffer.from('302e020100300506032b657004220420','hex'); var der = Buffer.concat([prefixPrivateEd25519, rawPrivate]); return crypto.createPrivateKey({key: der, format: "der", type: "pkcs8"}) }
Además, en la función encode()
:
Elimina la línea const keyDecoded = Buffer.from(key, "base64")
Crear la firma con
const signature = crypto.sign(null, msg, key)
Tenga en cuenta que para Ed25519, se debe pasar un null
como primer parámetro en la llamada sign()
. El algoritmo proviene de la clave.
Con estos cambios, el código de NodeJS devuelve el siguiente JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJ0ZXN0IiwiYXVkIjoidGVzdC5jb20iLCJpYXQiOjE2NTAxMDExNzgsImV4cCI6MTY1MDEwMTI3OCwic3ViIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MTIzIn0.f7WG_02UKljrMeVVOTNNBAGxtLXJUT_8QAnujNhomV18Pn5cU-0lHRgVlmRttOlqI7Iol_fHut3C4AOXxDGnAQ
que coincide con el JWT esperado .