Cryptography
Encryption vs Encoding vs Hashing: What Each One Does
Separate encoding, encryption and hashing with Base64, SHA-256 and OpenSSL labs. Learn why Base64 is not protection and hashes are one-way.
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.
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.
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.
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.
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:
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.
The sequence for a signed release looks like this:
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.
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.
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pemBoth commands print nothing when they succeed. Check the results:
ls -l priv.pem pub.pemOn 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.
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out msg.sig
ls -l msg.sigThe -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.
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigOutput:
Signature Verified SuccessfullyThe -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.
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigOutput:
Signature Verification FailureThe 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.
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:
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:
identicalA file without the final newline fails. echo adds a newline, so this file is one byte shorter:
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sigOutput:
Signature Verification Failuremsg.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:
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.sigOutput:
Signature Verification FailureThe file and the signature are both correct. The public key belongs to a different pair, so the check fails.
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.
The standards and manual pages above are the primary references for this article:
openssl-genpkeyopenssl-pkeyutlCryptography
Separate encoding, encryption and hashing with Base64, SHA-256 and OpenSSL labs. Learn why Base64 is not protection and hashes are one-way.
Cryptography
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.
Cryptography
Cryptography from zero: encryption, keys, hashes, signatures, and why a Caesar cipher teaches ideas but protects nothing.