flexi-human-hash
v1.0.2
Published
Making hashes human readable, and giving the user control over how they look
Downloads
511
Maintainers
Keywords
Readme
flexi-human-hash
There are lots of packages that convert big random numbers to something readable or create random strings from words, but none are as flexible as I wanted. I created this to be a highly controllable version of the other human hash packages.
Note that this package is well tested and fairly stable, so don't expect to see many changes unless new GitHub issues are opened.
Usage
Install:
npm install flexi-human-hashUse
import { FlexiHumanHash } from "flexi-human-hash";
const fhh = new FlexiHumanHash("{{adjective}}-{{noun}}");
console.log(fhh.hash());
// Expected output: "betwixt-railways"Features:
- Multiple dictionaries: nouns, adjectives, verbs, first name, last name, city
- Full control over formatting: separators, spaces, additional words, upper case, lower case, numbers
- Random: You provide the source of randomness (hash, string, uuid, etc) or one will be provided for you
- Reversable hashes: hashes can be converted back to their random number
- Entropy reporting: understand how likely hash collisions are for your given format
- Low learning curve: good documentation and examples
- Extendable: add your own dictionaries and formatting transforms
- Dictionaries aren't loaded unless used, reduces bloat
- Command line: use the JavaScript API or use it from the command line!
API Examples:
Simple hash, you provide the random numbers
const fhh = new FlexiHumanHash("{{adjective}}-{{adjective}}-{{noun}}-{{decimal 4}}");
fhh.hash("edf63145-f6d3-48bf-a0b7-18e2eeb0a9dd");
// Expected output: "disagreeably-thankless-newsgirls-3149"Another format, random number provided for you
const fhh = new FlexiHumanHash("{{adjective}}, {{adjective}} {{noun}} {{hex 4}}");
fhh.hash();
// Expected output: "stalwart, dominant attire f214"Another format, md5 hash a string for random numbers, transform names to all caps
const fhh = new FlexiHumanHash("{{first-name caps}}-{{last-name caps}}-{{decimal 6}}");
fhh.hash("this is my password...", {hashAlg: "md5"});
// Expected output: "CHARITY-ESMERELDA-903817"Reverse a string back to the original random number
// Note: Use separators that don't appear in dictionary words to ensure unhashing works
// Some names contain hyphens (e.g., "Ann-Marie"), so "_" is safer than "-"
const fhh = new FlexiHumanHash("{{first-name lowercase}}_{{last-name lowercase}}_the_{{adjective}}_{{noun}}");
const randomArr = [57, 225, 104, 232, 109, 102, 74];
const str = fhh.hash(randomArr);
const ret = fhh.unhash(str);
// ret equals randomArrReport how much entropy is used for a format to help understand likelihood of collisions
const fhh = new FlexiHumanHash("{{first-name uppercase}}-{{last-name uppercase}}-{{decimal 6}}");
console.log(fhh.entropy);
// Expected output (note BigInt): "70368744177664n"
console.log("Number of combinations:", fhh.entropy.toLocaleString());
// Expected output: "Number of combinations: 70,368,744,177,664"
console.log(`Entropy: 2^${fhh.entropyBase2}`);
// Expected output: "Entropy: 2^46"
console.log(`Entropy: 10^${fhh.entropyBase10}`);
// Expected output: "Entropy: 10^14"Add a dictionary
const scientificTerms = [
"antigens",
"magnetron",
"nanoarchitectonics",
"spintronics",
"teflon",
"transistor",
/* ... */
];
function registerScientificTerms() {
return {
size: scientificTerms.length,
getEntry: function(n) {
return scientificTerms[n];
},
};
}
FlexiHumanHash.registerDictionary("science", registerScientificTerms);
const fhh = new FlexiHumanHash("{{adjective}}:{{science}}");
fhh.hash();
// Expected output: "archetypical:spintronics"Add a transform
function reverseString(str) {
return str.split("").reverse().join("");
}
FlexiHumanHash.registerTransform("reverse", reverseString);
const fhh = new FlexiHumanHash("{{adjective reverse}}-{{noun reverse}}");
fhh.hash();
// Expected output: "ydeewt-airalos"Formats
- noun
- 47,004 English nouns from categorized-words
- verb
- 31,232 English verbs from categorized-words
- adjective
- 14,903 English adjectives from categorized-words
- decimal
- A decimal number (zero through 10), defaults to four digits long but can be a specified number of digits long
- {{decimal}} = 2394
- {{decimal 8}} = 84258973
- {{decimal 1}} = 7
- hex
- A hexidecimal number (zero through f), defaults to four nibbles / characters long but can be a specified number of digits long
- {{hex}} = 3fa8
- {{hex 8}} = cb28f30d
- {{hex 1}} = e
- female-name
- 4,951 English capitalized female first names / given names from @stdlib
- male-name
- 3,898 English capitalized male first names / given names from @stdlib
- first-name
- 8,849 English capitalized first names / given names (female-name and male-name combined)
- last-name
- 21,985 last names / family names from uuid-readable
- city
- 138,398 city names from all-the-cities
Unhashing (Reversing Hashes)
The unhash() method converts a human-readable hash string back to the original random bytes. This is useful when you need to recover the original data from a hash.
Basic Usage
const fhh = new FlexiHumanHash("{{adjective}}_{{noun}}_{{decimal 4}}");
const randomArr = [0x12, 0x34, 0x56];
const str = fhh.hash(randomArr); // e.g., "fuzzy_cat_1234"
const recovered = fhh.unhash(str); // returns [0x12, 0x34, 0x56]Validation
Not all format strings can be unhashed. The library provides validation to catch problems early and give you clear error messages explaining what needs to be fixed.
Eager validation (recommended): Validate at construction time with validateUnhash: true. This catches problems immediately so you can fix your format string:
const fhh = new FlexiHumanHash("{{first-name}}-{{last-name}}", { validateUnhash: true });
// Throws immediately with a helpful message:
// Error: Format is not unhashable: dictionary word "Ann-Marie" contains separator "-"
//
// The error tells you exactly what's wrong: the name "Ann-Marie" in the first-name
// dictionary contains a hyphen, which is the same character you're using as a separator.
// Solution: use a different separator like "_" or ":"Lazy validation (default): Validation happens on the first unhash() call:
const fhh = new FlexiHumanHash("{{first-name}}-{{last-name}}");
fhh.hash("some-uuid"); // Works fine for hashing
fhh.unhash("John-Smith"); // Error thrown here on first unhash attemptCommon Validation Errors
The validation checks for several issues and provides specific error messages:
| Error Message | What It Means | How to Fix |
|--------------|---------------|------------|
| dictionary word "X" contains separator "Y" | A word in your dictionary contains characters you're using as a separator. This makes parsing ambiguous. | Use a different separator that doesn't appear in dictionary words. Try _, :, or \|. |
| no separator between words in format string | Your format has adjacent dictionary placeholders like {{noun}}{{verb}}. | Add a separator between placeholders: {{noun}}_{{verb}}. |
| empty separator between words | There's nothing between two dictionary placeholders. | Add a separator character between them. |
| transform "X" does not have an undo function | A custom transform doesn't support being reversed. | Add an undoFn when registering the transform. |
Runtime Errors
These errors occur when calling unhash() with a string that doesn't match the format:
| Error Message | What It Means | How to Fix |
|--------------|---------------|------------|
| Unable to parse string for unhashing: "X" | The input string doesn't match the expected format structure. | Verify the string was produced by hash() with the same format. Check that separators match. |
| word not found while unhashing: X | A word in the string isn't in the expected dictionary. | Verify the string hasn't been modified. Check that you're using the same dictionaries. |
Tips for Unhashable Formats
When designing formats that need to support unhashing:
Use uncommon separators: Characters like
_,:,|, or multi-character separators like::or--are less likely to appear in dictionary words.Test with validation enabled: Always use
{ validateUnhash: true }during development to catch issues early.Consider your dictionaries: Some dictionaries are safer than others:
adjective,noun,verb: Generally safe with most separatorsfirst-name,last-name: May contain hyphens (e.g., "Ann-Marie", "O'Brien")city: May contain spaces and hyphens (e.g., "New York", "Winston-Salem")
For custom dictionaries: Ensure your words don't contain your chosen separator character.
Entropy Considerations
The unhash operation only recovers the bits of entropy encoded in the format. If your input has more bits than the format's entropy, the extra bits are lost:
const fhh = new FlexiHumanHash("{{test}}"); // 3 bits of entropy
fhh.hash([0b11111111]); // Only uses first 3 bits
fhh.unhash("slug"); // Returns [0b11100000], last 5 bits are 0Transforms
Note: transforms with "=" in them must come last, because Handlebars. e.g. "{{noun uppercase max-length=4}}" works, but "{{noun max-length=4 uppercase}}" will throw a parsing error.
- uppercase
- Converts the first letter of a word to uppercase
- e.g. "{{noun uppercase}}" -> "Word"
- lowercase
- Converts an entire word to lowercase
- e.g. "{{noun lowercase}}" -> "word"
- caps
- Converts an entire word to uppercase
- e.g. "{{noun caps}}" -> "WORD"
- max-length=n
- Filters a dictionary to only include words 'n' letters long or less
- e.g. "{{noun max-length=4}}" => "cat" or "blob" (not "building")
- min-length=n
- Filters a dictionary to only include words 'n' letters long or more
- e.g. "{{noun min-length=5}}" => "cloud" or "building" (not "cat")
- exact-length=n
- Filters a dictionary to only include words 'n' letters long or less
- e.g. "{{noun exact-length=4}}" => "tree" or "bush" (not "cat", not "building")
API
- FlexiHumanHash
- Class, constructor takes a
formatstring and an options object - e.g.
new FlexiHumanHash(formatString, options) - Constructor options:
validateUnhash: (boolean, default: false) If true, validates at construction time that the format can be unhashed. Throws an error if the format contains ambiguities (e.g., dictionary words containing separator characters).
hash(randomness, options): uses randomness to create a string in the specified formatrandomnesscan be a: string, array of numbers, iterable of numbers, TypedArray, ArrayBuffer- options:
- hashAlg: a string passed to Node Crypto createHash. The algorithm must be acceptable by the local installation of OpenSSL ("sha256" is a good guess if you don't know better). If used,
randomnessmust be an argument acceptable to Node Crypto's hash.update()function. - hashSalt: Used in combination with
hashAlg-- is passed to the.update()method beforerandomnessto create a different output hash. Must be an argument acceptable to Node Crypto's hash.update()function.
- hashAlg: a string passed to Node Crypto createHash. The algorithm must be acceptable by the local installation of OpenSSL ("sha256" is a good guess if you don't know better). If used,
unhash(str): converts a human-readable hash string back to the original random bytes- Returns an array of numbers (bytes)
- Throws an error if:
- The format is not unhashable (ambiguous separators, etc.)
- The string cannot be parsed (words not found in dictionaries)
- See "Unhashing" section above for important considerations
entropy: (BigInt) Returns the number of possible combinationsentropyBase2: (number) Returns the entropy in bitsentropyBase10: (number) Returns the entropy as a power of 10
- Class, constructor takes a
- FlexiHumanHash.registerDictionary(name, registerFn)
nameof the dictionary that will be used in the format- e.g.
name= "foo" becomes "{{foo}}"
- e.g.
registerFnreturns an object with:- size: number of entries in the dictionary
- getEntry: function with one arg (n), that returns the Nth entry of the dictionary
- reverseLookup: (optional) function with one arg (str), returns the index of the word or -1. Required for unhashing support.
- FlexiHumanHash.registerTransform(name, transformFn, undoFn)
nameof the transform that will be used in the format- e.g.
name= "bar" becomes "{{noun bar}}"
- e.g.
transformFnis a function with one argument, the word that will be transformed. Returns the transformed word.undoFnis a function that reverses the transform. Required for unhashing support with custom transforms.
Similar packages
- Project Name Generator
- Codenamize JS
- UUID Readable
- GUID in Words
- Mnemonic ID
- Human Readable IDs
- Wordhash
- Human Readable
- Humanize Digest
- UUID API Key
Future ambitions
- Add more dictionaries!
- Add Chroma Hash and other non-word hashes
Issues and pull requests always welcome, even if you're just saying hi. :)
