Reputation: 7554
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
Reputation: 123500
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 :
Optionally there is also sometimes -----BEGIN RSA PUBLIC KEY-----
but the header spec just has BEGIN PUBLIC KEY
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
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