---
title: "Tales Vault System: random first, memorable second"
description: "Tales Vault System: CSPRNG passwords, mnemonic phrases from a fixed template, and recovery without picking letters or words. Local-first."
date: 2026-07-19
locale: en
url: https://escribano.dev/en/blog/011-tales-vault-system/
---
In the [previous post](/blog/010-password-entropy-length/) we saw that entropy only matters when randomness is real. A password manager solves 99% of cases. But for the master password, laptop boot, or the few passwords you need to memorize without a sticky note, the question remains: how do you generate entropy without falling into the bias of picking words that "sound good"?

**[Tales Vault System](https://github.com/escribanoruben/tale)** (the [`tale`](https://github.com/escribanoruben/tale) repository) inverts the usual flow. Password first (CSPRNG). Mnemonic story second, derived with fixed rules. You do not choose the letters; in interactive mode you only pick among valid word candidates if you want to customize the phrase.

## The problem a manager does not solve

A manager stores a hundred passwords. But someone has to remember the key that unlocks the manager. And sometimes you must type a password in an environment where pasting from the clipboard is not an option.

Common solutions fail for the same reason:

| Approach | Flow | Problem |
|----------|------|---------|
| **"My favorite phrase"** | Phrase → password | Human patterns, low entropy |
| **Leetspeak** | Word + substitutions | Modern cracking anticipates it |
| **xkcd without dice** | Four words you pick | Skewed distribution by initial letter |
| **Diceware** | Random words → phrase | Works; see [diceware.dmuth.org](https://diceware.dmuth.org/) |

Diceware is solid (and [diceware.dmuth.org](https://diceware.dmuth.org/) is an excellent implementation that even links the xkcd strip in its FAQ). Tales Vault targets a different balance: a short random letter sequence (more bits per typed character) with grammatical phrases to recover it.

## How it works

```
[CSPRNG] → password (20 chars, language alphabet)
              ↓
         blocks of 4: jnra | ngtb | ...
              ↓
    noun + adjective + verb + noun (per block)
              ↓
    password + phrases + recovery rules
```

Real example from `tale generate --language en`:

```
Password: pwtiscdobsonvdpnudwt

Mnemonic phrases:
  [pwti] people whole take information
  [scdo] state certain did order
  [bson] book such obtained new
  [vdpn] value different put number
  [udwt] use due was time

Entropy: ~85.0 bits
```

Each word contributes its normalized initial (`árbol` → `a` in Spanish profiles; `ñ` excluded from the Spanish alphabet). Concatenate initials per block and you recover the password.

## Design principles

| Principle | Detail |
|-----------|--------|
| **Randomness first** | The key comes from a cryptographic generator; the phrase is derived, never the reverse |
| **Restricted alphabet** | Top 20 most frequent word-initial letters per language, maximizes dictionary coverage |
| **Fixed template** | `noun → adjective → verb → noun` per 4-letter block |
| **No letter picking** | The user does not invent initials; the TUI only allows choosing among valid words |
| **Lowercase only** | No `Shift` or layer switching; faster on mobile and clearer when dictating |
| **Local-first** | Word lists embedded in the binary (`go:embed`); no network at runtime |
| **Single binary** | ~3 MB with curated EN + ES dictionaries (~13k words) |

The restricted alphabet slightly reduces per-character entropy versus 26 lowercase letters, but length compensates: 20 characters over ~20 letters yield ~85 bits, enough for a master password or a few critical memorized passwords.

In Spanish, the uneven distribution of words by initial letter (which we described as human bias in the previous post) is handled explicitly here: the alphabet is computed from the corpus, and `tale stats` shows real coverage per letter (`a`: 360 words, `q`: 48).

## Lowercase only

Tales Vault generates passwords in lowercase on purpose. It targets the scenarios where typing a memorized key hurts most.

In the [previous post](/blog/010-password-entropy-length/) we already covered the cost of `Shift`, symbols, and uppercase on a physical keyboard. Here the decision is product-level:

- **One fewer keystroke per character.** Each uppercase letter needs `Shift` or a layer change. Over twenty characters, avoiding uppercase removes up to twenty extra presses and the risk of hitting the wrong key.
- **Mobile.** On a touch screen, switching between upper- and lowercase slows entry a lot. An all-lowercase key stays on the default layer with no mode hopping.
- **Dictation.** If you say the password out loud (backup with someone you trust, rehearsal while memorizing) you do not need to clarify "that's uppercase" or spell with ambiguity. `jnrangtbtljnlbiqnhqm` is read letter by letter, always the same.

Entropy is not wasted: the alphabet has ~20 letters and the default length is 20 characters (~85 bits). More length, one case, a deliberate trade-off versus mixing uppercase, digits, and symbols that nobody actually types well in practice.

## The `tale` command

The repository ships a Go binary named `tale`. Minimal install:

```bash
git clone https://github.com/escribanoruben/tale
cd tale
make build    # or: go build -o tale ./cmd/tale
```

### Automatic generation

```bash
./tale generate --language es
./tale generate --language en --length 16
```

Full random mode: password, phrases, estimated entropy, and recovery rules on stdout. No secrets stored to disk by default.

### Interactive mode (TUI)

```bash
./tale tui --language es
./tale tui --language en --password abcdefghijklmnopqrst
```

Terminal UI ([Bubble Tea](https://github.com/charmbracelet/bubbletea)) with a roller-style picker: for each letter in a block, you choose among words whose initial matches. Useful if you want a more personal phrase without touching letter entropy, those are already fixed.

### Dictionary statistics

```bash
./tale stats
```

Shows alphabet, words per letter, and part of speech. A transparency tool: verify language coverage before trusting the generator.

### Languages

| Language | Code | Alphabet (20 letters) |
|----------|------|-------------------------|
| English | `en` | `abcdefghilmnoprstuvw` |
| Spanish | `es` | `abcdefghijlmnopqrstv` |

Custom languages under `~/.tale/` are also supported (`tale language new`, `tale language init`). Built-ins load embedded lists; custom ones require filling the corresponding TSV files.

## Local-first and [digital sovereignty](/blog/005-digital-sovereignty/)

Word lists travel inside the binary. No API calls, no "generate your password in the cloud." In contexts where sending secrets to third parties is unacceptable (or when you simply want to audit which words the system can propose) that matters.

In the [RPS framework](/blog/002-rps-principle/), Tales Vault is pragmatic stone: one binary, zero deployment ceremony, maximum transparency over the dictionary. It does not pretend to be an identity platform or a full manager.

## Honest limits

- Does not replace a password manager. It is meant for a few passwords you memorize yourself.
- **Slightly lower entropy** than 20 pure lowercase letters due to the trimmed alphabet, documented in the output.
- **Dictionary coverage.** Blocks without valid candidates are regenerated; in Spanish some letters have fewer words than others.
- **TUI mode and bias.** If you always pick the "prettiest" word among candidates, you introduce bias in memorability, not in the letters, but the phrase is no longer the one automatic scoring would have chosen.
- **Project license:** TBD in the repository; word list sources have their own licenses (see `internal/worddata/README.md`).

## Diceware and [diceware.dmuth.org](https://diceware.dmuth.org/)

Before talking about `tale`, Diceware done right deserves its place. [diceware.dmuth.org](https://diceware.dmuth.org/), by Douglas Muth, is the web reference most people know: virtual dice rolls, [EFF](https://www.eff.org/dice) word list, passphrase generated in the browser without sending anything to a server. Four random words ≈ 44 bits; six ≈ 66. The words *are* the password: you remember them and type them as-is.

Cases where Diceware shines (the site lists them): smart TVs, awkward keyboards, someone else's machine without a manager installed. Typing `correct horse battery staple` is slower than twenty letters in a row, but each word is a recognizable mental chunk.

Tales Vault System does not compete with that; it solves a different profile:

| | [diceware.dmuth.org](https://diceware.dmuth.org/) | Tales Vault (`tale`) |
|---|---------------------------------------------------|----------------------|
| **What you memorize** | The random words | Mnemonic phrases; the key is the initials |
| **What you type** | Full words with spaces (or without) | Lowercase only, no `Shift` or layer switching |
| **Flow** | Random words → passphrase | Random letters (CSPRNG) → derived phrase |
| **Languages** | EFF list in English | Embedded English and Spanish; alphabet per language |
| **Runtime** | Web (local JS) or source on GitHub | Offline Go binary (`go:embed`) |
| **Typical entropy** | ~44 bits (4 words) / ~66 (6) | ~85 bits (20 letters, ~20-letter alphabet) |

If you want a whole-word passphrase and Diceware's rhythm suits you, use Diceware (for real). If you need more entropy per keystroke, lowercase only, recovery via templated phrases, and native Spanish support, that is what `tale` is for.

## How it compares

| System | Who chooses | Entropy |
|--------|-------------|---------|
| **Tales Vault** | CSPRNG → derived phrase | High (~85 bits, uniform letters) |
| **Diceware** ([dmuth.org](https://diceware.dmuth.org/)) | Dice/PRNG → words | High (~44–66 bits by length) |
| **Memorable phrase** | You → initials | Low (skewed) |
| **Manager** | CSPRNG → stored | High (you do not memorize) |

## What's next

The repository has an [open design discussion](https://github.com/escribanoruben/tale/issues/1) on adding derived control digits at the end of the password, a checksum to detect errors when recovering the key from phrases (wrong block order, misremembered word).

Code, OpenSpec specs, and word lists live at [github.com/escribanoruben/tale](https://github.com/escribanoruben/tale). If you try `tale` and hit a block with no valid phrase in your language, open an issue, dictionary contributions are welcome.

## Closing

Random and memorable are not opposites if the order is right: entropy first, story second. The xkcd comic intuited it; Diceware proved it with dice. Tales Vault brings it to a reproducible, auditable, local flow, with a command you can build yourself.

For everything else, keep using your manager. For the few passwords you need in your head, at least let the letters be chosen by something other than your intuition.
