Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

Public and Private Keys Explained: Signatures You Can Verify

Public and private keys from zero: what a signature proves, why the private key stays secret, and an Ed25519 lab where a changed file fails.

Beginner9 min readBy NotHoly

Public-key cryptography solves a problem that shared secrets cannot. Two parties who have never met can agree on a secret over a network that anyone can read. Someone can also prove that they wrote a file, without sharing any secret with the people who check it. Both things work because a key pair has two halves that are mathematically linked. The public half can be shared freely. The private half stays with its owner.

If you have used TLS or SSH, you have already relied on this idea. The padlock in your browser and the fingerprint prompt in your SSH client both depend on public keys. This article explains the idea underneath those tools. You will generate an Ed25519 key pair with OpenSSL, sign a file, verify the signature, and then change one line of the file and watch verification fail. Signed software releases use this same mechanism, and it is the foundation for the SSH keys covered in the next article.

What a key pair is

A key pair is two values generated together. The private key is secret. The public key can be copied into a file, sent by email, or pasted into a server's configuration. The two are linked by math that works in one direction. Deriving the public key from the private key is easy. Recovering the private key from the public key is designed to be infeasible.

Cryptographers call this asymmetric cryptography because the two parties use different keys. Symmetric encryption, such as AES, uses one secret for both directions. An asymmetric scheme splits the work: one key performs an operation, and the other key reverses it or checks it.

A rough picture helps. Think of the public key as a mail slot. Anyone can drop a letter through it, and only the owner holds the key that opens the box. For signatures, the roles reverse. The owner stamps a document with a seal that only that owner can make, and anyone with the public reference can check the seal. The picture is imperfect, because keys are numbers and not brass. The direction of each operation is the part worth remembering.

Encryption and signatures do opposite jobs

People mix these up all the time, so keep them apart.

Encryption to a public key lets anyone with your public key create a message that only your private key can open. The goal is confidentiality. Anyone can write to you, and only you can read what they wrote.

A signature is a value the private key holder computes over some data. Anyone with the public key can check it. The goal is authenticity and integrity: the data came from the key holder, and nobody changed it after signing. A signature does not hide anything. A signed file stays fully readable by whoever has it.

Ed25519, the algorithm in this lab, is a signature scheme only. It cannot encrypt a message. Public-key encryption uses other constructions, such as RSA-OAEP, or an elliptic-curve key agreement followed by a symmetric cipher. That is why "public key" names a form of key rather than one job. Learn the job first, then the algorithm.

TLS uses both jobs. In a TLS 1.3 handshake, the server signs data with the private key that matches the public key in its certificate, which shows that it controls that key. A separate key agreement then derives symmetric keys that encrypt the session. SSH follows the same split. A host key signs part of the handshake, and a user key signs an authentication challenge. Once you separate the two jobs, the protocols become easier to read.

Why the private key must never leave its owner

The private key is the proof. Whoever holds it can produce signatures that verify as that identity, and can decrypt messages encrypted to it. A verifier does not see your face or your name. It checks a signature against the public key it trusts. If someone copies your private key, every verifier that trusts your public key now accepts the copy as you.

Four practical rules follow from this:

  • Generate the private key on the machine that will use it, or on a hardware token built for that purpose. Do not download a private key from a website or receive one in a chat.
  • Restrict file permissions. Protect the key with a passphrase when the tool supports it, or keep it in a hardware-backed store.
  • Keep it out of Git, tickets, screenshots and support requests. Secret scanners look for PEM headers. Once a key reaches a repository's history, treat it as exposed, even after you delete the file.
  • If you suspect exposure, retire that key. Create a new pair, publish the new public key through a channel your verifiers trust, and revoke the old one. Deleting the local file does not remove copies that already left your machine.

Sharing the public key is safe by design. The harder problem runs in the other direction. A verifier must know that a public key really belongs to the person or project it names. Certificate authorities, pinned release keys and SSH host fingerprints exist to solve exactly that problem. A valid signature proves that the key holder signed those bytes. It does not prove that the key holder is who you expected.

How signing and verification work, step by step

The sequence for a signed release looks like this:

  1. The publisher generates a key pair once and keeps the private key secret.
  2. The publisher shares the public key through a channel the verifier trusts, such as a page the verifier already trusts, a fingerprint published in a second place, or a key verified earlier.
  3. For each release, the publisher signs the exact bytes of the file with the private key. An Ed25519 signature is 64 bytes.
  4. The publisher distributes the file and its signature together.
  5. The verifier runs the check using the file bytes, the signature and the trusted public key. The answer is valid or not valid.

Two details matter more than they seem to. First, a signature covers exact bytes. One added space, a different line ending, or a re-packed archive is a different message, and verification fails. Second, Ed25519 signing in RFC 8032 is deterministic. The same key and the same message always produce the same signature, so signing does not need a fresh random number each time. That removes one common source of failure. FIPS 186-5 lists EdDSA with ECDSA and RSA as approved digital signature algorithms.

Hands-on lab: sign a file and watch verification fail

You need OpenSSL 3.x. The output in this lab comes from Windows 11 with Git Bash and OpenSSL 3.2.4. Check your version with openssl version first. Older builds may not accept the same options.

Use Git Bash, a Linux shell or Terminal on macOS. Windows PowerShell 5.1 writes UTF-16 when you redirect with >, which changes the bytes you sign. Run the lab in an empty folder on your own machine. It contacts no server.

Generate the key pair

bash
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pem

Both commands print nothing when they succeed. Check the results:

bash
ls -l priv.pem pub.pem

On our machine, priv.pem was 122 bytes and pub.pem was 116 bytes. Open pub.pem and you will see the -----BEGIN PUBLIC KEY----- block. Do not print or paste priv.pem. In this lab it is the secret half of the identity, and getting used to never showing it is a habit you will need for SSH keys too.

Sign the file

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out msg.sig
ls -l msg.sig

The -rawin option tells OpenSSL to sign the file contents directly. Ed25519 hashes its input inside the algorithm, so you do not hash the file first. The signing command prints nothing. Our msg.sig was 64 bytes.

Verify with the public key

bash
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

Output:

text
Signature Verified Successfully

The -pubin flag says the key file holds only the public half. A verifier needs nothing else, so this check can run on another machine that never saw priv.pem.

Change one line and verify again

bash
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sig

Output:

text
Signature Verification Failure

The command exits with status 1. The signature in msg.sig was made over release v1.0, so it cannot vouch for release v1.1. OpenSSL does not say what changed. It reports only that the bytes and the signature disagree. A verifier should treat that result as a hard stop, not as a warning to click past.

Check the edges

These checks show exactly what the signature covers. We ran each one on the same machine, in the same folder.

Signing the same file twice gives the same bytes:

bash
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out a.sig
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out b.sig
cmp a.sig b.sig && echo "identical"

Output:

text
identical

A file without the final newline fails. echo adds a newline, so this file is one byte shorter:

bash
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sig

Output:

text
Signature Verification Failure

msg.txt is 13 bytes and nonl.txt is 12 bytes. The signature is over the 13-byte version, so the shorter file fails.

A different key pair fails too, even on the original file:

bash
openssl genpkey -algorithm ed25519 -out other.pem
openssl pkey -in other.pem -pubout -out other.pub
openssl pkeyutl -verify -pubin -inkey other.pub -rawin -in msg.txt -sigfile a.sig

Output:

text
Signature Verification Failure

The file and the signature are both correct. The public key belongs to a different pair, so the check fails.

Common mistakes

  • Treating a signature as encryption. A signed file is still readable by anyone. If you need secrecy, you need encryption, and that is a separate construction.
  • Trusting any public key you find. A valid signature proves the key holder signed the bytes. It does not prove who the key holder is. Compare fingerprints through a second channel.
  • Verifying different bytes than the ones signed. A pipeline that re-encodes, re-compresses or changes line endings breaks verification. Sign the final artifact and verify that same artifact.
  • Signing in a shell that rewrites bytes. Windows PowerShell 5.1 redirection is one example. Editors that save with a different encoding or line ending are another.
  • Reusing one key for everything. Separate keys for release signing, SSH login and encryption limit the damage when one key leaks.
  • Assuming a deleted key is gone. Copies in backups, sync folders, shell history or chat logs survive a local delete.
  • Picking primitives by name. "RSA" or "ECC" does not say which padding, curve or mode is in use. Read the parameters in the standard, not just the label.

How to go further

The next article, What Is SSH and How Do SSH Keys Work?, uses this same signing idea for logins. Your id_ed25519 files, if you have them, are a pair of exactly this kind. Read the password hashing article too, since passwords and keys solve different parts of authentication, and it helps to keep them apart.

For depth, read RFC 8032 to see how Ed25519 structures its signatures and why it is deterministic. Read FIPS 186-5 to see where EdDSA sits among approved signature algorithms. Read the OpenSSL genpkey and pkeyutl man pages to see the other options, such as digest selection for algorithms that need one. Then verify a release from a project you already use, with the key the project publishes on its own site. Only run files you have downloaded from the official source, and only after you have checked them.

Sources

The standards and manual pages above are the primary references for this article:

  • RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA)
  • FIPS 186-5, Digital Signature Standard (DSS)
  • OpenSSL documentation, openssl-genpkey
  • OpenSSL documentation, openssl-pkeyutl

Sources

  1. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
  2. FIPS 186-5: Digital Signature Standard (DSS)
  3. OpenSSL documentation: openssl-genpkey
  4. OpenSSL documentation: openssl-pkeyutl

More in Cryptography

Cryptography

Passwords and Hashing with Argon2id

Why fast hashes fail for passwords, what salts and work factors do, and how to hash and verify with Argon2id in Python using argon2-cffi.

Intermediate10 min read