Last active
April 25, 2025 12:57
-
-
Save delorenj/80ade52e3e315307b7b279806eeea96e to your computer and use it in GitHub Desktop.
Script to generate SSH keypair in PEM format
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* RSA Key Generator | |
* | |
* This script generates RSA key pairs in PEM format. | |
* Built for Deno runtime. | |
* | |
* Usage: | |
* deno run --allow-write --allow-read generate-keys.ts [options] | |
* | |
* Options: | |
* --name Key name prefix (default: "rsa") | |
* --size Key size in bits (default: 2048) | |
* --format Private key format: pkcs8 or pkcs1 (default: pkcs8) | |
* --output Output directory (default: current directory) | |
* | |
* Examples: | |
* deno run --allow-write --allow-read generate-keys.ts --name myapp --size 4096 | |
* deno run --allow-write --allow-read generate-keys.ts --output ./keys --format pkcs1 | |
*/ | |
interface KeyOptions { | |
name: string; | |
size: number; | |
format: "pkcs8" | "pkcs1"; | |
output: string; | |
} | |
async function generateRsaKeyPair(options: KeyOptions): Promise<void> { | |
try { | |
// Generate a key pair | |
const keyPair = await crypto.subtle.generateKey( | |
{ | |
name: "RSASSA-PKCS1-v1_5", | |
modulusLength: options.size, | |
publicExponent: new Uint8Array([1, 0, 1]), // 65537 | |
hash: "SHA-256" | |
}, | |
true, | |
["sign", "verify"] | |
); | |
// Export keys in correct format | |
const privateKeyFormatType = options.format === "pkcs1" ? "pkcs1" : "pkcs8"; | |
// Export private key | |
const privateKeyBuffer = await crypto.subtle.exportKey( | |
privateKeyFormatType === "pkcs1" ? "pkcs1" : "pkcs8", | |
keyPair.privateKey | |
); | |
// Export public key | |
const publicKeyBuffer = await crypto.subtle.exportKey( | |
"spki", | |
keyPair.publicKey | |
); | |
// Convert to PEM format | |
const privateKeyPem = formatAsPem(privateKeyBuffer, "PRIVATE KEY"); | |
const publicKeyPem = formatAsPem(publicKeyBuffer, "PUBLIC KEY"); | |
// Ensure output directory exists | |
try { | |
await Deno.mkdir(options.output, { recursive: true }); | |
console.log(`Created output directory: ${options.output}`); | |
} catch (error) { | |
if (!(error instanceof Deno.errors.AlreadyExists)) { | |
throw error; | |
} | |
} | |
// Create file paths | |
const privateKeyPath = `${options.output}/${options.name}-private.pem`; | |
const publicKeyPath = `${options.output}/${options.name}-public.pem`; | |
// Write the keys to files | |
await Deno.writeTextFile(privateKeyPath, privateKeyPem); | |
await Deno.writeTextFile(publicKeyPath, publicKeyPem); | |
// Try to set permissions (this might fail on some platforms) | |
try { | |
await Deno.chmod(privateKeyPath, 0o600); // Read/write for owner only | |
await Deno.chmod(publicKeyPath, 0o644); // Read for everyone, write for owner | |
console.log("Permissions set successfully"); | |
} catch (error) { | |
console.log( | |
"Note: Could not set file permissions (this is normal on Windows or with certain file systems)" | |
); | |
} | |
// Calculate key fingerprints | |
const privateKeyFingerprint = await calculateSha256(privateKeyPem); | |
const publicKeyFingerprint = await calculateSha256(publicKeyPem); | |
// Output results | |
console.log( | |
`\nGenerated RSA key pair (${ | |
options.size | |
} bits, ${options.format.toUpperCase()} format):` | |
); | |
console.log(` Name: ${options.name}`); | |
console.log(` Private key: ${privateKeyPath}`); | |
console.log(` Public key: ${publicKeyPath}`); | |
console.log(`\nKey fingerprints (SHA-256):`); | |
console.log( | |
` Private key: ${privateKeyFingerprint.substring( | |
0, | |
8 | |
)}...${privateKeyFingerprint.substring(privateKeyFingerprint.length - 8)}` | |
); | |
console.log( | |
` Public key: ${publicKeyFingerprint.substring( | |
0, | |
8 | |
)}...${publicKeyFingerprint.substring(publicKeyFingerprint.length - 8)}` | |
); | |
} catch (error) { | |
console.error(`Error generating keys: ${error.message}`); | |
Deno.exit(1); | |
} | |
} | |
function formatAsPem(buffer: ArrayBuffer, type: string): string { | |
const base64 = base64Encode(buffer); | |
const lines = []; | |
// Break the base64 string into lines of 64 characters | |
for (let i = 0; i < base64.length; i += 64) { | |
lines.push(base64.slice(i, i + 64)); | |
} | |
return `-----BEGIN ${type}-----\n${lines.join("\n")}\n-----END ${type}-----`; | |
} | |
function base64Encode(buffer: ArrayBuffer): string { | |
return btoa(String.fromCharCode(...new Uint8Array(buffer))); | |
} | |
async function calculateSha256(text: string): Promise<string> { | |
const msgUint8 = new TextEncoder().encode(text); | |
const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8); | |
const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); | |
} | |
// Parse command line arguments | |
function parseArgs(): KeyOptions { | |
const args = Deno.args; | |
const options: KeyOptions = { | |
name: "rsa", | |
size: 2048, | |
format: "pkcs8", | |
output: Deno.cwd() | |
}; | |
for (let i = 0; i < args.length; i++) { | |
if (args[i] === "--name" && i + 1 < args.length) { | |
options.name = args[i + 1]; | |
i++; | |
} else if (args[i] === "--size" && i + 1 < args.length) { | |
const size = parseInt(args[i + 1], 10); | |
if (!isNaN(size) && [1024, 2048, 3072, 4096].includes(size)) { | |
options.size = size; | |
} else { | |
console.error( | |
"Invalid key size. Must be one of: 1024, 2048, 3072, 4096" | |
); | |
Deno.exit(1); | |
} | |
i++; | |
} else if (args[i] === "--format" && i + 1 < args.length) { | |
const format = args[i + 1].toLowerCase(); | |
if (format === "pkcs8" || format === "pkcs1") { | |
options.format = format as "pkcs8" | "pkcs1"; | |
} else { | |
console.error("Invalid format. Must be one of: pkcs8, pkcs1"); | |
Deno.exit(1); | |
} | |
i++; | |
} else if (args[i] === "--output" && i + 1 < args.length) { | |
options.output = args[i + 1]; | |
i++; | |
} else if (args[i] === "--help" || args[i] === "-h") { | |
console.log(` | |
RSA Key Generator | |
Usage: | |
deno run --allow-write --allow-read generate-keys.ts [options] | |
Options: | |
--name Key name prefix (default: "rsa") | |
--size Key size in bits (default: 2048) | |
--format Private key format: pkcs8 or pkcs1 (default: pkcs8) | |
--output Output directory (default: current directory) | |
Examples: | |
deno run --allow-write --allow-read generate-keys.ts --name myapp --size 4096 | |
deno run --allow-write --allow-read generate-keys.ts --output ./keys --format pkcs1 | |
`); | |
Deno.exit(0); | |
} | |
} | |
return options; | |
} | |
// Main execution | |
if (import.meta.main) { | |
const options = parseArgs(); | |
await generateRsaKeyPair(options); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment