Of course! Generating SSH keys in Python is a common task, especially for automating server setups, cloud deployments, or CI/CD pipelines. The best and most secure way to do this is by using Python's built-in paramiko library, which is a pure Python implementation of the SSHv2 protocol.

Here’s a complete guide, from the basics to a full, reusable function.
The Best Approach: Using the paramiko Library
paramiko is the de-facto standard for SSH in Python. It's robust, well-maintained, and avoids the security risks of calling external command-line tools like ssh-keygen via subprocess.
Step 1: Install paramiko
If you don't have it installed, open your terminal or command prompt and run:
pip install paramiko
Step 2: Generate the SSH Key Pair
The core of the process is using paramiko.RSAKey.generate(bits=2048). You can then save the public and private keys to files.

Here is a simple, complete script to generate a key pair:
import paramiko
import os
def generate_ssh_key_pair(key_type='rsa', key_size=2048, comment="python-keygen"):
"""
Generates a new SSH key pair and saves them to files.
Args:
key_type (str): The type of key to generate ('rsa', 'dsa', 'ecdsa', 'ed25519').
'rsa' is the most common and widely supported.
key_size (int): The size of the key in bits. 2048 is a good minimum for RSA.
comment (str): A comment to add to the public key.
Returns:
tuple: A tuple containing the paths to the private and public key files.
(private_key_path, public_key_path)
"""
# Generate the key object
if key_type.lower() == 'rsa':
key = paramiko.RSAKey.generate(bits=key_size)
elif key_type.lower() == 'dsa':
key = paramiko.DSSKey.generate(bits=key_size)
elif key_type.lower() == 'ecdsa':
# For ECDSA, key_size is the key size in bits (e.g., 256, 384, 521)
key = paramiko.ECDSAKey.generate(bits=key_size)
elif key_type.lower() == 'ed25519':
# Ed25519 uses a fixed key size
key = paramiko.Ed25519Key.generate()
else:
raise ValueError(f"Unsupported key type: {key_type}")
# Define filenames
private_key_path = f"id_{key_type}"
public_key_path = f"{private_key_path}.pub"
# --- Save the Private Key ---
# The private key is saved in the OpenSSH format
# The password argument can be used to encrypt the private key with a passphrase
key.write_private_key_file(private_key_path, password=None)
print(f"[*] Private key saved to: {os.path.abspath(private_key_path)}")
# --- Save the Public Key ---
# The public key is just the key's base64 representation plus the comment
public_key_line = f"{key.get_name()} {key.get_base64()} {comment}"
with open(public_key_path, 'w') as f:
f.write(public_key_line + '\n')
print(f"[*] Public key saved to: {os.path.abspath(public_key_path)}")
return private_key_path, public_key_path
# --- Example Usage ---
if __name__ == "__main__":
try:
# Generate a standard 2048-bit RSA key pair
private_key, public_key = generate_ssh_key_pair(key_type='rsa', key_size=2048)
# Print the public key to the console
print("\n--- Public Key ---")
with open(public_key, 'r') as f:
print(f.read())
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
How to Run the Script
- Save the code above as a Python file (e.g.,
create_keys.py). - Run it from your terminal:
python create_keys.py
You will see output similar to this:
[*] Private key saved to: /path/to/your/project/rsa
[*] Public key saved to: /path/to/your/project/rsa.pub
--- Public Key ---
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... (long string of characters) python-keygen
This will create two files in your current directory:
id_rsa: Your private key. Treat this like a password! Keep it secure and never share it.id_rsa.pub: Your public key. This is the one you can share and place on servers.
Advanced: Adding a Passphrase to the Private Key
For better security, you should encrypt your private key with a passphrase. You can do this easily by providing the password argument to write_private_key_file.

Here's how you would modify the example usage:
# Inside the generate_ssh_key_pair function, when saving the private key:
# key.write_private_key_file(private_key_path, password="your-super-secret-passphrase")
# --- Example Usage with Passphrase ---
if __name__ == "__main__":
try:
# Ask for a passphrase (ideally, use a more secure method for input in a real app)
passphrase = input("Enter a passphrase for your new private key (leave blank for no passphrase): ")
if not passphrase:
passphrase = None # No passphrase
private_key, public_key = generate_ssh_key_pair(key_type='rsa', key_size=2048)
# Modify the key saving part to include the passphrase
key.write_private_key_file(private_key_path, password=passphrase)
print(f"[*] Private key saved to: {os.path.abspath(private_key_path)} (with passphrase)")
# ... (rest of the script is the same)
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Now, when you try to use this private key with SSH or another tool, it will prompt you for the passphrase.
Alternative (Not Recommended): Using subprocess to Call ssh-keygen
You can use Python's subprocess module to call the system's ssh-keygen command. This is generally not recommended because:
- Portability: It assumes
ssh-keygenis installed on the system, which isn't always true (e.g., in some minimal Docker containers). - Security: You have to be very careful with how you handle arguments and file paths to avoid command injection vulnerabilities.
- Error Handling: It's harder to get detailed error information compared to a native library.
However, if you absolutely must, here's how it would look:
import subprocess
import os
def generate_key_with_subprocess():
"""Generates an SSH key pair by calling the ssh-keygen command."""
key_type = 'rsa'
key_size = 2048
private_key_path = f"id_{key_type}"
public_key_path = f"{private_key_path}.pub"
comment = "python-keygen-subprocess"
passphrase = None # Or a passphrase
# The -f flag specifies the output file name for the private key.
# The public key will be automatically created with a .pub extension.
command = [
'ssh-keygen',
'-t', key_type,
'-b', str(key_size),
'-C', comment,
'-f', private_key_path,
'-N', passphrase if passphrase else '' # -N for new passphrase, empty string for none
]
try:
print(f"[*] Running command: {' '.join(command)}")
# subprocess.run is the modern, safer way to call external commands
result = subprocess.run(command, check=True, capture_output=True, text=True)
# ssh-keygen prints to stderr on success, so we check that
if result.stderr:
print("[*] ssh-keygen output:")
print(result.stderr)
print(f"[*] Successfully generated keys using ssh-keygen.")
print(f"[*] Private key: {os.path.abspath(private_key_path)}")
print(f"[*] Public key: {os.path.abspath(public_key_path)}")
except FileNotFoundError:
print("Error: 'ssh-keygen' command not found. Is OpenSSH installed and in your PATH?")
except subprocess.CalledProcessError as e:
print(f"Error: ssh-keygen failed with return code {e.returncode}")
print(f"Stderr: {e.stderr}")
# --- Example Usage ---
if __name__ == "__main__":
generate_key_with_subprocess()
Summary: Which Method to Choose?
| Feature | paramiko (Recommended) |
subprocess (Not Recommended) |
|---|---|---|
| Portability | High. Pure Python, works anywhere Python runs. | Low. Requires ssh-keygen to be installed on the system. |
| Security | High. No risk of command injection. | Medium/Low. Risk of vulnerabilities if arguments are not sanitized. |
| Dependencies | Requires pip install paramiko. |
None, relies on system tools. |
| Control | Full. Can manipulate key objects directly in Python. | Limited. Can only interact via command-line arguments and output. |
| Error Handling | Excellent. Full Python exception handling. | Basic. Relies on exit codes and parsing text output. |
For any serious application, automation, or library, always use paramiko. It's the correct, secure, and professional way to handle SSH operations in Python.
