Skip to content

Instantly share code, notes, and snippets.

@delorenj
Last active April 25, 2025 12:57
Show Gist options
  • Save delorenj/80ade52e3e315307b7b279806eeea96e to your computer and use it in GitHub Desktop.
Save delorenj/80ade52e3e315307b7b279806eeea96e to your computer and use it in GitHub Desktop.
Script to generate SSH keypair in PEM format
/**
* 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