An attacker with a copy of an age file can try passphrases on their own computer. No server limits the number of attempts. The time depends on the attempt rate and how soon the attacker tries the correct passphrase.
The scrypt post gave the cost of one calculation. This final post uses recorded measurements to calculate example attack times. The calculations are estimates under stated assumptions, not expiry dates for passwords.
What one attempt does
In this series, a guess means one candidate passphrase. A brute force attack tries each candidate in a defined set. A dictionary attack tries likely candidates, such as common passwords, first.
An attacker can use this procedure for an age passphrase file:
- Read the salt and work factor from the header.
- Derive a wrapping key with scrypt and a candidate passphrase.
- Try to decrypt the file key with that wrapping key.
- Test the authentication tag on the encrypted file key.
An incorrect candidate fails at the file key. The attacker does not have to decrypt the complete payload for each attempt. The cipher post shows this authentication check.
A generic scrypt benchmark measures the expensive part of the attempt. It does not measure the complete age decryption process. Support for the age format in a specified attack tool is a separate question.
The recorded laptop measurements
The initial measurements used an Apple M5 with 10 cores and work factor 18. A core is a processor unit that executes program instructions. A parallel test runs multiple operations at the same time.
The recorded rates were:
| Method | Recorded rate each second |
|---|---|
| age command, one decryption attempt at a time | 2.6 attempts |
| scrypt calculation, one core | 2.7 calculations |
| scrypt calculations, all 10 cores in parallel | 8.2 calculations |
These are the measurements recorded for the initial article. The series includes the code for the sequential scrypt measurement. It does not include the initial parallel test program or raw timing records.
The tables below use 8.2 attempts each second as an example rate based on the parallel scrypt result. They do not establish the performance of an optimized attack implementation.
The parallel rate was about three times the sequential rate in those results. That observation alone does not prove a memory bandwidth limit. Scheduling, memory access, and implementation details can affect the result.
What the memory requirement tells us
At the example parameters, the primary scrypt memory area is about 256 MiB for each attempt. Ten calculations at the same time use about 2.5 GiB, plus other program memory.
A graphics processing unit, or GPU, can perform many calculations in parallel. Its available memory still limits how many scrypt calculations fit at once.
For a hypothetical device with 24 GiB of usable memory:
24 GiB = 24576 MiB
24576 MiB / 256 MiB per calculation = 96 calculations
This memory capacity gives a maximum of 96 calculations at the same time, before other memory allocations. It is not a rate of 96 attempts each second. The time for each calculation is also necessary to calculate a rate.
The recorded measurements do not include a GPU benchmark. The memory calculation does not prove that a GPU provides no advantage. A faster implementation, more hardware, or different memory tradeoffs can change the result.
Calculate an exhaustive search time
An exhaustive search tries all combinations in a set. For a random string of a fixed length, the number of combinations is:
combinations = alphabet size raised to the power of string length
full search time = combinations / attempts per second
For example, an alphabet with 26 lowercase letters and a length of 6 gives 308,915,776 combinations. At 8.2 attempts each second, a full search takes about 1.2 years.
The tables use these assumptions:
- Each character comes from an independent random selection.
- Every character in the stated alphabet has the same probability.
- The attacker knows the alphabet and exact length.
- The attack rate stays constant, with no repeated candidates.
- One year is 365.25 days.
Each cell gives the time for the complete set. Under these assumptions, the average time to reach the correct candidate is approximately half that value. A successful first attempt is still possible.
At the example rate of 8.2 attempts each second:
| Alphabet | 6 characters | 8 characters | 10 characters | 12 characters |
|---|---|---|---|---|
| 26 lowercase letters | 1.2 years | 807 years | 545,526 years | 369 million years |
| 26 lowercase letters and 10 digits | 8.4 years | 10,902 years | 14.1 million years | 18.3 billion years |
| 52 uppercase and lowercase letters and 10 digits | 219 years | 843,754 years | 3.24 billion years | 12.5 trillion years |
A second table uses a hypothetical rate of 1,000 attempts each second. This rate is about 122 times faster. It is an assumption, not a measured age benchmark.
| Alphabet | 6 characters | 8 characters | 10 characters | 12 characters |
|---|---|---|---|---|
| 26 lowercase letters | 3.6 days | 6.6 years | 4,473 years | 3.02 million years |
| 26 lowercase letters and 10 digits | 25.2 days | 89.4 years | 115,857 years | 150 million years |
| 52 uppercase and lowercase letters and 10 digits | 1.8 years | 6,919 years | 26.6 million years | 102 billion years |
The Python code reproduces the calculations before rounding. It also calculates the random word examples in the next section.
seconds_per_year = 365.25 * 24 * 60 * 60
for rate in (8.2, 1000):
print(f"\nAttempts per second: {rate}")
for alphabet_size in (26, 36, 62):
for length in (6, 8, 10, 12):
combinations = alphabet_size ** length
seconds = combinations / rate
years = seconds / seconds_per_year
print(alphabet_size, length, f"{years:.3g} years")
for word_count in (4, 5, 6):
combinations = 7776 ** word_count
years = combinations / 8.2 / seconds_per_year
print(word_count, f"{combinations:.3g} combinations", f"{years:.3g} years")
Why a common password does not fit those tables
A person often chooses a name, a date, or a familiar word. An attacker can try those choices before unrelated strings of the same length. Thus, the full character set is the wrong model for that password.
At 8.2 attempts each second, a list of one million candidates takes about 33.9 hours to complete. If the correct candidate is near the start, the attack finishes much earlier.
Passwords such as password1 and summer2026 follow familiar patterns. Their lengths do not justify the random string estimates above. scrypt adds a cost to each attempt, but it does not change the order in which an attacker tries candidates.
Random words increase the number of combinations
A passphrase can use words from a list. For the calculation below, the list contains 7,776 words. Each word must come from an independent random selection, with equal probability for each word.
Four such words give 7776^4 combinations, or about 3.66 quadrillion. The estimate assumes that the attacker knows the list, the word count, and the separator.
| Number of random words | Combinations | Full search at 8.2 attempts each second |
|---|---|---|
| 4 | About 3.66 quadrillion | About 14.1 million years |
| 5 | About 2.84 × 10^19 | About 110 billion years |
| 6 | About 2.21 × 10^23 | About 854 trillion years |
In scientific notation, 2.84 × 10^19 means 2.84 multiplied by 10 raised to the power 19. The notation keeps very large numbers readable.
Words that form a favorite quotation or a sentence you invent do not meet the random selection assumption. Adding more words helps only according to the choices those words add. A short passphrase is not automatically stronger than a long password.
The EFF dice method gives a procedure for random word selection and recommends six words. The four word row above is a calculation example, not that recommendation.
Choose a method you can keep secure
The key pair post shows encryption without a human passphrase. In that path, the recipient must keep the secret key private and available for decryption.
For passphrase encryption, age can generate a random passphrase at its prompt. A password manager can store it. The reader does not have to memorize a predictable phrase to make decryption convenient.
graph TD
A["Who must encrypt the file?"] --> B{"A script or another person?"}
B -->|Yes| C["Consider a public key recipient"]
C --> D["Keep a secure backup of the secret key"]
B -->|No| E["A random passphrase is an option"]
E --> F["Store the passphrase securely"]
I trust a passphrase that I can measure more than one that I hope is strong. The useful measure is how the passphrase was selected, together with an explicit model of the attack. The tables cannot predict the lifetime of an individual password.

Loading comments...