Andre M
Andre M

Reputation: 7554

How do I determine the algorithm of a public key in nodejs?

Given a public key of the following form (invalidated in example), how would I find the algorithm used in Node.js, using the crypto package?

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhZPjbR5OWOjXeAT/MvrK
TeS5aKhSZJgdm6/Mq7DWgUWIQ3aIHqiXRpWd9WPG7n9/YSPmwcbmiJLUVETt3400
gSd9AoabzOMZsJYz6fsxC9+M54RV4x8Ef5Rej2cc6m9iTfzc9uRpJtDulYGNGWCF
crOLLbMVGgeKjDmqF57s2OWG2xSmy1+MpY9GqWgDBWvdEF7+pNrc1HcHMd2ELOKS
QKbUcE6w24ro71bLSW9FZexWwQNr6fYRqBxyrlobYserl9kKZNkbBsBdezFT8S8c
ZM8WUVzYpNR2mXdrs+fumKFljSG32dLfnI70eVeEX1oMSB/9bzvtPfS36KSZ+PPs
FQIDAQAB
-----END PUBLIC KEY-----

The main goal is to ensure that the provided key is valid on application initialisation and that it is using an algorithm that is supported by Node.js.

I did look at crypto.createVerify, but that requires knowing the algorithm before hand, hence looking for other approaches.

Upvotes: 0

Views: 95

Answers (2)

mikemaccana
mikemaccana

Reputation: 123500

Obligatory: you will always get more accurate answers on https://crypto.stackexchange.com/ than Stack Overflow.

StackOverflow often gives incorrect information for cryptography topics, including many of the comments on this question.

I had thought there was an algorithm indicator before the data.

There is. See https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7 :

  • Algorithm Identifier - eg RSA or ECDSA
  • Public Key

Optionally there is also sometimes -----BEGIN RSA PUBLIC KEY----- but the header spec just has BEGIN PUBLIC KEY

How to get the algorithm from the public key contents

You need to check the ASN1 Object IDs from the 'Subject Public Key Info' part of the key.

// Your RSA public key in PEM format
const rsaPublicKeyPem = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhZPjbR5OWOjXeAT/MvrK
TeS5aKhSZJgdm6/Mq7DWgUWIQ3aIHqiXRpWd9WPG7n9/YSPmwcbmiJLUVETt3400
gSd9AoabzOMZsJYz6fsxC9+M54RV4x8Ef5Rej2cc6m9iTfzc9uRpJtDulYGNGWCF
crOLLbMVGgeKjDmqF57s2OWG2xSmy1+MpY9GqWgDBWvdEF7+pNrc1HcHMd2ELOKS
QKbUcE6w24ro71bLSW9FZexWwQNr6fYRqBxyrlobYserl9kKZNkbBsBdezFT8S8c
ZM8WUVzYpNR2mXdrs+fumKFljSG32dLfnI70eVeEX1oMSB/9bzvtPfS36KSZ+PPs
FQIDAQAB
-----END PUBLIC KEY-----
`;

// Sample EC public key in PEM format (secp256r1/prime256v1 curve)
const ecdsaPublicKeyPem = `
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH5OBk5sFKQq9qjzD
FO2KwuG8sBKxGz5cEHs3/IM1GxcLEq4V0tDhxtXkdxFvxjXp5GkZbM8z8A==
-----END PUBLIC KEY-----
`;

// Define OID constants with their common names
const OID = {
  // Public Key Types
  rsaEncryption: "1.2.840.113549.1.1.1",
  ecPublicKey: "1.2.840.10045.2.1",

  // Hash Algorithms
  sha1: "1.3.14.3.2.26",
  sha256: "2.16.840.1.101.3.4.2.1",
  sha384: "2.16.840.1.101.3.4.2.2",
  sha512: "2.16.840.1.101.3.4.2.3",

  // Signature Algorithms
  sha1WithRSAEncryption: "1.2.840.113549.1.1.5",
  sha256WithRSAEncryption: "1.2.840.113549.1.1.11",
  sha384WithRSAEncryption: "1.2.840.113549.1.1.12",
  sha512WithRSAEncryption: "1.2.840.113549.1.1.13",

  // Elliptic Curves
  prime256v1: "1.2.840.10045.3.1.7", // secp256r1
  secp384r1: "1.3.132.0.34",
  secp521r1: "1.3.132.0.35",
} as const;

// Create reverse mapping for looking up names from OIDs
const OID_NAMES = Object.fromEntries(
  Object.entries(OID).map(([name, oid]) => [oid, name])
) as Record<string, string>;

// Algorithm and KeyAlgorithm are already
// used as part of the node stdlib.
type AlgorithmResult = "RSA" | "ECDSA" | "UNKNOWN";

function decodeOID(bytes: number[]): string {
  // First byte encodes the first two numbers: first = Math.floor(byte / 40), second = byte % 40
  const first = Math.floor(bytes[0] / 40);
  const second = bytes[0] % 40;

  const components = [first, second];
  let value = 0;

  // Process remaining bytes
  for (let i = 1; i < bytes.length; i++) {
    const byte = bytes[i];
    // For each byte, if MSB is 1, continue reading. If 0, write value.
    value = (value << 7) | (byte & 0x7f);
    if ((byte & 0x80) === 0) {
      components.push(value);
      value = 0;
    }
  }

  return components.join(".");
}

function parseAlgorithmIdentifier(der: Buffer): AlgorithmResult {
  // Skip the outer SEQUENCE
  let pos = 1; // Skip SEQUENCE tag
  const seqLength = der[pos++];
  if (seqLength & 0x80) {
    // Long form length
    const lenBytes = seqLength & 0x7f;
    pos += lenBytes;
  }

  // Now at the start of AlgorithmIdentifier SEQUENCE
  pos++; // Skip SEQUENCE tag
  const algLength = der[pos++];

  // Now at the OID
  pos++; // Skip OID tag (0x06)
  const oidLength = der[pos++];

  // Extract just the OID bytes
  const oid = der.slice(pos, pos + oidLength);

  // Convert OID bytes to string representation
  const oidString = decodeOID(Array.from(oid));

  // Determine key algorithm from OID
  let algorithm: AlgorithmResult = "UNKNOWN";
  if (oidString === OID.rsaEncryption) {
    algorithm = "RSA";
  } else if (oidString === OID.ecPublicKey) {
    algorithm = "ECDSA";
  }

  return algorithm;
}

function getKeyType(pem: string) {
  const base64Data = pem.replace(/-----.*-----/g, "").trim();
  const der = Buffer.from(base64Data, "base64");
  return parseAlgorithmIdentifier(der);
}

console.log("RSA key algorithm:", getKeyType(rsaPublicKeyPem));
console.log("EC key algorithm:", getKeyType(ecdsaPublicKeyPem));

// Remove or comment out the old test code
// const algorithm = parseAlgorithmIdentifier(der);
// console.log("Key Algorithm:", algorithm);

Run tsx runme.ts. This returns:

RSA key algorithm: RSA
EC key algorithm: ECDSA

Upvotes: 1

Umair Sarfraz
Umair Sarfraz

Reputation: 6079

createPublicKey can be used to reconstruct the object from a public key and asymmetricKeyType can be used to infer the algorithm used.

const { createPublicKey } = require('crypto');

const publicKeyPemRSA = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzC5RNOewXjMZY9lwE5qz
ivz4C6vc9cqO3byYby7od5gOBnJv5E16B1jsUhkH8LdfjFz9gqv0gtlCIfgF4ffr
V5zEyFyz9p4e7QBaZz3ADPNT6+58rKScuA0gOsVp+k8/xYmR35O7UPKhcfY1XzQF
bzFkbwBQYkEn91YZ/y7F5lcL7Xtv7jULu/Vu/7ZbOIrhr6Zbmcgy1aFA7+jVb6F5
5GonN7oZvErnCRG31rIVipA3WlrLVYVj83ctf/FlROc4yy1m0/U9XYQbh5Qv5XzL
0cmOq7Wn5+O6jBmqg3qaAO4l3l2QtjqIEcNO0DzBMQzKK8zFfv8zFscz8Wq9znfv
JwIDAQAB
-----END PUBLIC KEY-----`;

const publicKeyRSA = createPublicKey(publicKeyPemRSA);
console.log(publicKeyRSA.asymmetricKeyType); // logs: "rsa"

Upvotes: 1

Related Questions