---
title: "Ed25519 private keys, encoding, and entropy"
title_en: "Ed25519 private keys, encoding, and entropy"
description: "A study note tracing the chain from 'why brute force is infeasible' down to 'what entropy actually is', using Ed25519 keys as the worked example."
sidebar_label: "Ed25519 keys & entropy"
---

# Ed25519 private keys, encoding, and entropy: a complete note

> A study note that follows the question from "why is brute-forcing infeasible?" all the way down to "what is entropy?"

---

## Table of contents

1. [256-bit random numbers and the infeasibility of brute force](#1-256-bit-random-numbers-and-the-infeasibility-of-brute-force)
2. [Why does a private key look like a pile of letters and digits?](#2-why-does-a-private-key-look-like-a-pile-of-letters-and-digits)
3. [What if the private key happens to be 0?](#3-what-if-the-private-key-happens-to-be-0)
4. [What does "enough entropy" actually mean?](#4-what-does-enough-entropy-actually-mean)
5. [One-sentence summary](#5-one-sentence-summary)

---

## 1. 256-bit random numbers and the infeasibility of brute force

A "256-bit random number" means the private key is essentially an integer drawn at random from `0` to `2²⁵⁶ − 1`. For an attacker to hit the private key by brute-force guessing is equivalent to guessing that one number out of the entire range.

The point is how large that space is:

- `2²⁵⁶ ≈ 1.16 × 10⁷⁷`
- This magnitude is close to "the total number of atoms in the observable universe".

### Getting a feel with concrete numbers

Suppose an attacker commands:

- 1 trillion machines worldwide (`10¹²`)
- Each able to try 1 trillion guesses per second (`10¹²`)
- A combined `10²⁴` guesses per second (far beyond any real compute)

Divide that into the key space:

```
2²⁵⁶ / 10²⁴ ≈ 1.16 × 10⁵³ seconds
```

The age of the universe is only about `4.35 × 10¹⁷` seconds. In other words, even with this absurd amount of compute, it would take on average "10³⁵ times the age of the universe" to hit the key.

> **"Mathematically infeasible" doesn't mean "very hard" — it means there isn't physically enough time.**

### "Online" brute-force guessing is even more infeasible

An "online attack" means repeatedly trying to log in against a real running server, where every guess must:

- Make a network round trip and wait for the server's response
- Face rate limiting, lockout after failures, and other protections

In practice this may be only a few thousand guesses per second, or fewer. If even local full-speed cracking (an offline attack) is already infeasible, online guessing is even more of a fantasy.

**The real risk has never been someone guessing the private key — it's the private key file leaking** (no passphrase protection, read off by malware, accidentally pushed to a repo, etc.).

---

## 2. Why does a private key look like a pile of letters and digits?

That pile of letters and digits is just that 256-bit number "written a different way". Underneath, it's still the same thing.

The core idea: **the same value can be written in different bases or encodings — the amount of information doesn't change at all.**

### A familiar analogy

We normally write the decimal `255`; the computer internally stores the binary `11111111`. They look completely different, yet are the same number.

### The private key's conversion chain

```
A 256-bit random number
   ↓ rewrite
Binary: 256 zeros/ones
   ↓ group every 4 bits
Hexadecimal (hex): 64 characters (0-9 a-f)
   ↓ convert every 3 bytes
Base64: the string of letters and digits you actually see (A-Z a-z 0-9 + /)
```

- **Hex**: each character represents 0–15 (16 states), which maps exactly to 4 bits. 256 bits → 64 characters.
- **Base64**: the encoding used by SSH private key files (`-----BEGIN OPENSSH PRIVATE KEY-----`), so the key can be stuffed into a plain-text file and copy-pasted without errors.

Each layer is just "a different appearance of the same number". What the attacker has to guess is always that bottom-layer value — which of the `2²⁵⁶` possibilities it is. Displaying it as letters and digits does not make guessing any easier.

> Note: an OpenSSH private key file also wraps in extra structure (the corresponding public key, a comment, and — if a passphrase is set — encryption parameters), so the file is somewhat longer than "just 64 characters". But the core secret is still that one 256-bit random number.

---

## 3. What if the private key happens to be 0?

This intuition pokes at a very real point in cryptography. Two layers to the answer.

### Layer one: an attacker "trying 0 first" is smart, but only helps if your key really is 0

**The key misconception**: an attacker trying 0 does not make "your key" become 0. Your key was fixed the moment it was generated.

If your key is some random value X buried deep in the `2²⁵⁶` space, then an attacker scanning up from 0, 1, 2, 3… still has to reach X to hit it — on average about `2²⁵⁵` values to scan. "Trying the small ones first" only pays off if the target happens to be a small number.

So the real question is: **what's the probability that my key happens to be exactly 0 (or some other easy-to-guess value)?** Under normal randomness, the answer is `1/2²⁵⁶` — exactly the same as the probability of it equaling any other specific value. 0 is no more likely to be your key than anything else.

### Layer two: this is why "randomness" carries the entire security burden

The whole guarantee collapses the moment randomness breaks — and these are real disasters that have actually happened:

| Case | What happened |
|------|---------------|
| **Debian OpenSSL bug (2008)** | The RNG was broken, leaving only ~32,000 possible keys; an attacker could try them all and break in |
| **Embedded / IoT devices** | Not enough entropy at boot, producing predictable — even identical across devices — keys |
| **Cryptocurrency brainwallets** | People used memorable strings like `password` or `123456` as the seed, drained within seconds |

In these cases the attacker really did "try 0 and the likely values" and succeed — but the reason for success wasn't that 0 has magic; it's that the key generation process had no real randomness at all.

### An Ed25519 design detail: even "seed = 0" doesn't degenerate

Ed25519 does not take the 256 bits directly as the scalar for its operations. It:

1. First feeds the seed into **SHA-512** and takes the first half
2. Then performs **clamping** (always clears the lowest 3 bits, always sets bit 254 to 1)

Verifying in practice (seed set to all zeros):

```python
import hashlib

seed = bytes(32)  # seed = all zeros
h = hashlib.sha512(seed).digest()
a = bytearray(h[:32])

# Ed25519 clamping
a[0]  &= 0xF8
a[31] &= 0x7F
a[31] |= 0x40

scalar = int.from_bytes(a, "little")
print("Is the scalar after clamping zero?", scalar == 0)
```

Output:

```
seed: 0000000000000000000000000000000000000000000000000000000000000000
Is the scalar after clamping zero? False
scalar (first 40 decimal digits): 3932564886698065279271500916921949606201 ...
corresponding public key (hex): 3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29
```

Even with an all-zero seed, the scalar after clamping is a large non-zero number, and the corresponding public key is fully valid and looks completely patternless. In other words, Ed25519 doesn't degenerate into a mathematical flaw even in the extreme "seed = 0" case; at most it's "a key whose seed you already happen to know" — which loops back to layer one: probability `1/2²⁵⁶`.

> Some other schemes (where the private key is used directly as the scalar) do have degenerate weak keys like 0 and 1 that make the public key the identity element, but a proper implementation checks for and rejects those specific values at key-generation time.

---

## 4. What does "enough entropy" actually mean?

**Entropy measures exactly one thing: how unpredictable the outcome of this generation process is.** It can be measured precisely, in bits.

The cleanest definition:

> **Entropy = the base-2 logarithm of the total number of possibilities, assuming every outcome is equally likely.**

| Scenario | Number of equally likely outcomes | Entropy |
|----------|-----------------------------------|---------|
| Flipping one fair coin | 2 | log₂(2) = **1 bit** |
| Flipping two coins | 4 | **2 bits** |
| A genuinely random 256-bit key | 2²⁵⁶ | **256 bits** |

So "a 256-bit random number" stated precisely should be "a number carrying 256 bits of entropy".

### Key point: key "length" ≠ key "entropy"

Imagine a buggy program: it claims to pick a "random" number from 1 to a trillion, but actually always picks 1 to 10, just padded out to width as `000000000007`.

- **Appearance**: looks like one-in-a-trillion difficulty
- **Real entropy**: only log₂(10) ≈ **3.3 bits** — broken in 10 tries

A private key is the same. The file has 64 hex characters and looks imposing, but if the random source only fed in 16 bits of real randomness, the effective strength is 16 bits (only 65,536 possibilities).

> **A key's real security equals its entropy, not its length.**
> The Debian bug was essentially an "entropy collapse": real entropy plummeted from 256 bits to a dozen or so bits.

### Where does a computer's entropy come from?

A computer is fundamentally a **deterministic** machine and cannot produce true randomness on its own. The operating system has to "harvest" unpredictability from the physical world and pool it into an entropy pool:

- The precise timing intervals of keystrokes and mouse movements (microsecond, nanosecond-level jitter)
- Tiny random fluctuations in the timing of disk reads/writes, interrupts, and network packet arrivals
- The CPU's built-in hardware random instructions (e.g. Intel `RDRAND`), TPM chips
- Thermal noise from the manufacturing process

Linux gathers these and supplies them, after cryptographic processing, through interfaces like `/dev/urandom`.

### A classic disaster: a freshly booted embedded device

A device fresh from the factory, powered on for the first time:

- No one is typing, no mouse
- Disk behavior is identical
- The entropy pool is nearly empty, and every device's boot state is highly similar

If it rushes to generate an SSH key at this moment, the result is predictable — even identical across several devices. This is exactly why large numbers of IoT devices have been found sharing keys.

### Practical advice

- Don't generate long-term keys right after boot, before the entropy has built up
- Make good use of modern CPUs' hardware random sources
- VM environments are physically noise-poor and especially prone to entropy starvation; when needed, add `haveged` or `rng-tools`, or have the hypervisor pass the host's entropy in

---

## 5. One-sentence summary

> **Brute force is infeasible not because "there are no weak keys", but because "under genuine randomness, the probability of your key landing on any guessable value is so low it effectively doesn't exist".**
>
> **Entropy is "the stock of unpredictability", measured in bits. A key can be as long as you like — without enough entropy it's a hollow shell. Whether a key can be trusted ultimately comes down to one question: at the moment it was generated, how much genuine randomness did the system have in hand?**
