Crypto on the Command Line, Part 3: Key Pairs
A key pair is two numbers tied by a mathematical relation: what one half does, only the other undoes. This post
makes Bob's key pair from the
Security Primitives
tool with openssl:
- making the private key;
- looking at the numbers inside it, and checking their relation;
- computing the public half from it;
- naming a public key by its fingerprint;
- locking the private key with a passphrase.
Parts 4 and 5 use the pair made here.
The tools on Ubuntu 26.04
The commands are shown as a terminal session:
$starts a command: type what follows it;>marks a line that continues the command above it, for a command too long for one line;- a line with neither is output. A command with no lines under it prints nothing.
openssl (3.5 on 26.04); python3 for one check, installed on every desktop. If either is missing:
1$ sudo apt update
2$ sudo apt install openssl python3
3$ mkdir -p ~/crypto-lab && cd ~/crypto-lab
Making the private key
genpkey makes a key of the algorithm named; 2048 bits is openssl's default size for RSA:
1$ openssl genpkey -quiet -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out bob.key
2$ ls -l bob.key
3-rw------- 1 yuval yuval 1700 Oct 12 14:17 bob.key
4$ head -3 bob.key
5-----BEGIN PRIVATE KEY-----
6MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDEIE+iLG2UeRTV
7QfRhipGiTfjFFvq7R44QqLdsFP4+nlRWbTq7dTRV8BFskYvcG+jxkJK3Ki5BEqdK
- Only its owner can read it (
-rw-------): openssl writes a private key that way. - The file is PEM: the key's binary form (DER), in base64, between two marker lines.
- Without
-quiet, genpkey prints dots and pluses while it searches for two large primes.
The numbers inside the key
-text prints the key's numbers in hex. Their names alone:
1$ openssl pkey -in bob.key -text -noout | grep -v '^ '
2Private-Key: (2048 bit, 2 primes)
3modulus:
4publicExponent: 65537 (0x10001)
5privateExponent:
6prime1:
7prime2:
8exponent1:
9exponent2:
10coefficient:
- prime1 and prime2 (p and q) are two random primes of 1024 bits each.
- modulus (n) is their product, 2048 bits: the key's size.
- publicExponent (e) is 65537 in almost every RSA key.
- privateExponent (d) is computed from e, p and q, so that raising a number to the power e and then d (modulo n) gives the number back.
- exponent1, exponent2 and coefficient are d split up by p and q: a shortcut that makes the private key's work about four times faster.
A short Python program reads the numbers back and checks the relation:
1$ openssl pkey -in bob.key -text -noout > bob.txt
2$ python3 - bob.txt <<'EOF'
3> import re, sys
4> text = open(sys.argv[1]).read()
5> def num(name):
6> hexdigits = re.search(name + r":\n((?: .*\n)+)", text).group(1)
7> return int(re.sub(r"[\s:]", "", hexdigits), 16)
8> n, e, d = num("modulus"), 65537, num("privateExponent")
9> p, q = num("prime1"), num("prime2")
10> print("p x q == n:", p * q == n)
11> print("digits in n:", len(str(n)))
12> c = pow(42, e, n)
13> print("42 locked with e:", str(c)[:20] + "...")
14> print("unlocked with d:", pow(c, d, n))
15> EOF
16p x q == n: True
17digits in n: 617
1842 locked with e: 20571338879645799541...
19unlocked with d: 42
20$ rm bob.txt
- Computing d needs p and q. Anyone holding n alone would have to factor a 617-digit number to find them, and that is out of reach.
- RSA used this way, on a bare number, is only the arithmetic. Real encryption and signing first pad the number, as Parts 4 and 5 show.
The public half
The public key is n and e, copied out of the private key:
1$ openssl pkey -in bob.key -pubout -out bob.pub
2$ cat bob.pub
3-----BEGIN PUBLIC KEY-----
4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCBPoixtlHkU1UH0YYqR
5ok34xRb6u0eOEKi3bBT+Pp5UVm06u3U0VfARbJGL3Bvo8ZCStyouQRKnSlWi8zEO
6w6JUAK6ayJtXkMFG3gFqFailpcPvnAde4toadxWIuTlGOv/aNMuXNvrqlP7IgTel
7pP6wnSuEZxysJYCP3VzI9od0YSkH0HsMj2e9Mo/w3PkQLTy5h+DYguuti8x+IB1b
8XKgf7583tw0KIYAA2x6GHQBohIlVyeOWbkFaR3xQueo/Xpdf9VFasNsICZel1uTs
9lKWgh/LGUteULRL33OkCGYgtQShMMvztTYm3BFNWmxG8gvpNmnVWB4czRPQczLva
10JQIDAQAB
11-----END PUBLIC KEY-----
12$ openssl pkey -pubin -in bob.pub -text -noout | grep -v '^ '
13Public-Key: (2048 bit)
14Modulus:
15Exponent: 65537 (0x10001)
- Computing it again gives the same bytes:
openssl pkey -in bob.key -pubout | cmp - bob.pubprints nothing. Nothing random went in; it is a copy of two numbers. - Going back is out of reach: the public file holds neither prime.
bob.pubis the file Bob publishes. Alice, Eve and Mallory all get a copy.
Checking that two halves belong together
Both files hold the same modulus, and comparing it is the usual check that a public key matches a private one:
1$ openssl rsa -in bob.key -noout -modulus | sha256sum
2bbed2911adac8926d54171e89abaa6fe865b7692230ef003fdba7acd071d0432 -
3$ openssl rsa -pubin -in bob.pub -noout -modulus | sha256sum
4bbed2911adac8926d54171e89abaa6fe865b7692230ef003fdba7acd071d0432 -
The two digests are equal. A web server that refuses to start with "key values mismatch" failed this check between its certificate and its key.
A public key's fingerprint
A public key is 294 bytes in its binary form, too long to compare by eye. Its SHA-256 digest ( Part 1 ) names it in 32:
1$ openssl pkey -pubin -in bob.pub -outform DER | sha256sum
2239dcc89b331c570fe15d10efe031877ed6b2ccd090b9f8d06bb50108259b93c -
- SSH's "key fingerprint" and a certificate's key pinning are digests like this one.
- Reading the fingerprint to Alice over the phone is one way for her to know
bob.pubis Bob's and not Mallory's.
Locking the private key with a passphrase
A private key in a plain file is read by anyone who copies the file. genpkey can encrypt it with AES, the
key coming from a passphrase:
1$ openssl genpkey -quiet -algorithm RSA -aes-256-cbc -out locked.key
2Enter PEM pass phrase:
3Verifying - Enter PEM pass phrase:
4$ head -1 locked.key
5-----BEGIN ENCRYPTED PRIVATE KEY-----
- Every later use asks for the passphrase.
- A wrong passphrase fails with
bad decrypt: the AES padding check from Part 2 .
Other kinds of key pair
| Algorithm | genpkey -algorithm | Used for | Key size |
|---|---|---|---|
| RSA | RSA | encryption and signatures | 2048–4096 bits |
| Ed25519 | ED25519 | signatures only | 32 bytes |
| X25519 | X25519 | key agreement only | 32 bytes |
Ed25519 and X25519 are built on an elliptic curve, not on primes, and are much smaller and faster than RSA. Part 7 uses X25519.