|
|
|
|
|
package wordkey
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
|
_ "embed"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io"
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
"math"
|
|
|
|
|
|
"math/big"
|
|
|
|
|
|
"sort"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// DefaultCount is the number of words Generate draws when the caller does
|
|
|
|
|
|
// not ask for more: 7 words of a list of 7776 are about 90 bits.
|
|
|
|
|
|
const DefaultCount = 7
|
|
|
|
|
|
|
|
|
|
|
|
// MinListSize is the fewest words of a list that Generate accepts (spec
|
|
|
|
|
|
// §38.1: at least 6 words of a list of 2048 or more).
|
|
|
|
|
|
const MinListSize = 2048
|
|
|
|
|
|
|
The English list: the large wordlist of the EFF, and -dic en by default
wordkey/lists/en.txt is the large wordlist for passphrases of the EFF,
7776 words, CC BY 4.0 under its copyright policy, without the dice number
of each line and in its order, so that the position of a word still gives
its dice, 11111 for the first. encrypt -new-words takes it by default, as
the author decided; -dic es takes the Spanish one. The alphabet of en is a
to z and the ASCII hyphen of its four compound words, such as t-shirt,
kept so that no word loses its dice. lists/README.md records its source,
the SHA-256 of the download, the change and the license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 hours ago
|
|
|
|
//go:embed lists/en.txt
|
|
|
|
|
|
var listEN string
|
|
|
|
|
|
|
|
|
|
|
|
//go:embed lists/es.txt
|
|
|
|
|
|
var listES string
|
|
|
|
|
|
|
|
|
|
|
|
// lists are the word lists built into the module, by language. Each is a
|
|
|
|
|
|
// plain UTF-8 file, one word per line; lists/README.md says where each comes
|
|
|
|
|
|
// from and its license.
|
|
|
|
|
|
var lists = map[string]string{
|
The English list: the large wordlist of the EFF, and -dic en by default
wordkey/lists/en.txt is the large wordlist for passphrases of the EFF,
7776 words, CC BY 4.0 under its copyright policy, without the dice number
of each line and in its order, so that the position of a word still gives
its dice, 11111 for the first. encrypt -new-words takes it by default, as
the author decided; -dic es takes the Spanish one. The alphabet of en is a
to z and the ASCII hyphen of its four compound words, such as t-shirt,
kept so that no word loses its dice. lists/README.md records its source,
the SHA-256 of the download, the change and the license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 hours ago
|
|
|
|
"en": listEN,
|
|
|
|
|
|
"es": listES,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
// alphabets are the letters that a word of a list of each language may hold,
|
|
|
|
|
|
// as the list writes it: lower case, in NFC. A word with a letter of another
|
|
|
|
|
|
// script that looks like one of these, such as the Cyrillic U+0430 for the
|
|
|
|
|
|
// Latin a, would be written down and typed again with the letter of the
|
|
|
|
|
|
// keyboard, and the capsule would not open. The alphabet of a list comes from
|
The English list: the large wordlist of the EFF, and -dic en by default
wordkey/lists/en.txt is the large wordlist for passphrases of the EFF,
7776 words, CC BY 4.0 under its copyright policy, without the dice number
of each line and in its order, so that the position of a word still gives
its dice, 11111 for the first. encrypt -new-words takes it by default, as
the author decided; -dic es takes the Spanish one. The alphabet of en is a
to z and the ASCII hyphen of its four compound words, such as t-shirt,
kept so that no word loses its dice. lists/README.md records its source,
the SHA-256 of the download, the change and the license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 hours ago
|
|
|
|
// here, never from the list. The English one has the ASCII hyphen of the four
|
|
|
|
|
|
// compound words of the list of the EFF, such as t-shirt, kept so that every
|
|
|
|
|
|
// word keeps its number of dice.
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
var alphabets = map[string]string{
|
The English list: the large wordlist of the EFF, and -dic en by default
wordkey/lists/en.txt is the large wordlist for passphrases of the EFF,
7776 words, CC BY 4.0 under its copyright policy, without the dice number
of each line and in its order, so that the position of a word still gives
its dice, 11111 for the first. encrypt -new-words takes it by default, as
the author decided; -dic es takes the Spanish one. The alphabet of en is a
to z and the ASCII hyphen of its four compound words, such as t-shirt,
kept so that no word loses its dice. lists/README.md records its source,
the SHA-256 of the download, the change and the license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 hours ago
|
|
|
|
"en": "abcdefghijklmnopqrstuvwxyz-",
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
"es": "abcdefghijklmnopqrstuvwxyzáéíóúüñ",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Languages returns the languages of the built-in word lists, sorted.
|
|
|
|
|
|
func Languages() []string {
|
|
|
|
|
|
var l []string
|
|
|
|
|
|
for k := range lists {
|
|
|
|
|
|
l = append(l, k)
|
|
|
|
|
|
}
|
|
|
|
|
|
sort.Strings(l)
|
|
|
|
|
|
return l
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// List returns the built-in word list of lang, after checking it with
|
|
|
|
|
|
// CheckList.
|
|
|
|
|
|
func List(lang string) ([]string, error) {
|
|
|
|
|
|
text, ok := lists[lang]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return nil, fmt.Errorf("wordkey: no word list for %q; the lists are %s", lang, strings.Join(Languages(), ", "))
|
|
|
|
|
|
}
|
|
|
|
|
|
words := strings.Split(strings.TrimSuffix(text, "\n"), "\n")
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
if err := CheckList(lang, words); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("wordkey: the list %q: %w", lang, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return words, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
// CheckList reports why words cannot be a list of the language lang for
|
|
|
|
|
|
// Generate: a language without an alphabet in this package; fewer than
|
|
|
|
|
|
// MinListSize words; a word that is not one word of MinLetters characters or
|
|
|
|
|
|
// more once normalized, that holds a character Check refuses or one that is
|
|
|
|
|
|
// not a letter of the alphabet of lang; or a word that is the same as
|
|
|
|
|
|
// another once normalized. Two words such as «papa» and «papá» would be one
|
|
|
|
|
|
// word with less entropy than the list promises.
|
|
|
|
|
|
func CheckList(lang string, words []string) error {
|
|
|
|
|
|
alphabet, ok := alphabets[lang]
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
return fmt.Errorf("no alphabet for the language %q", lang)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(words) < MinListSize {
|
|
|
|
|
|
return fmt.Errorf("%d words, fewer than %d", len(words), MinListSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
seen := make(map[string]string, len(words))
|
|
|
|
|
|
for i, w := range words {
|
|
|
|
|
|
n := Normalize(w)
|
|
|
|
|
|
if len(n) != 1 || n[0] != strings.TrimSpace(n[0]) || utf8.RuneCountInString(n[0]) < MinLetters {
|
|
|
|
|
|
return fmt.Errorf("line %d, %q, is not one word of %d or more letters", i+1, w, MinLetters)
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := checkRunes(n[0]); err != nil {
|
|
|
|
|
|
return fmt.Errorf("line %d: %w", i+1, err)
|
|
|
|
|
|
}
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
for _, r := range w {
|
|
|
|
|
|
if !strings.ContainsRune(alphabet, r) {
|
|
|
|
|
|
return fmt.Errorf("line %d, %q, holds U+%04X, which is not in the alphabet of %q", i+1, w, r, lang)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if prev, ok := seen[n[0]]; ok {
|
|
|
|
|
|
return fmt.Errorf("line %d, %q, is the same word as %q once normalized", i+1, w, prev)
|
|
|
|
|
|
}
|
|
|
|
|
|
seen[n[0]] = w
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Generate draws n different words of list, uniformly, with random, which
|
|
|
|
|
|
// is crypto/rand.Reader when nil. Each word adds log2(len(list)) bits, a
|
|
|
|
|
|
// little less for each word already drawn. n must be MinWords or more.
|
|
|
|
|
|
func Generate(list []string, n int, random io.Reader) ([]string, error) {
|
|
|
|
|
|
if n < MinWords {
|
|
|
|
|
|
return nil, fmt.Errorf("wordkey: a key of words needs at least %d words, not %d", MinWords, n)
|
|
|
|
|
|
}
|
|
|
|
|
|
if n > len(list)/2 {
|
|
|
|
|
|
return nil, fmt.Errorf("wordkey: %d words of a list of %d", n, len(list))
|
|
|
|
|
|
}
|
|
|
|
|
|
if random == nil {
|
|
|
|
|
|
random = rand.Reader
|
|
|
|
|
|
}
|
|
|
|
|
|
picked := make(map[int]bool, n)
|
|
|
|
|
|
words := make([]string, 0, n)
|
|
|
|
|
|
size := big.NewInt(int64(len(list)))
|
|
|
|
|
|
for len(words) < n {
|
|
|
|
|
|
i, err := rand.Int(random, size)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("wordkey: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
if picked[int(i.Int64())] {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
picked[int(i.Int64())] = true
|
|
|
|
|
|
words = append(words, list[i.Int64()])
|
|
|
|
|
|
}
|
|
|
|
|
|
return words, nil
|
|
|
|
|
|
}
|
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
|
|
|
|
|
|
|
|
|
|
// Bits is the strength of count words that Generate draws from a list of
|
|
|
|
|
|
// size words: log2 of the number of draws in order, size·(size−1)·…, which
|
|
|
|
|
|
// whoever knows the list must search, before the rounds of PBKDF2.
|
|
|
|
|
|
func Bits(size, count int) float64 {
|
|
|
|
|
|
b := 0.0
|
|
|
|
|
|
for i := range count {
|
|
|
|
|
|
b += math.Log2(float64(size - i))
|
|
|
|
|
|
}
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|