├── src ├── index.js ├── fuerte-lib.js ├── helpers.php ├── Fuerte.php ├── Generator.php ├── fuerte.js └── eff_short_wordlist_1.txt ├── CNAME ├── assets ├── og.jpg └── wordpress-password-reset.png ├── .gitignore ├── .npmignore ├── webpack.mix.js ├── CHANGELOG.md ├── composer.json ├── package.json ├── LICENSE.md ├── fuerte.php ├── index.html ├── README.md └── dist ├── fuerte.js └── fuerte-lib.js /src/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | makepass.dev -------------------------------------------------------------------------------- /assets/og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collegeman/fuerte/HEAD/assets/og.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | /vendor 5 | mix-manifest.json 6 | composer.lock -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | /vendor 5 | mix-manifest.json 6 | *.php 7 | composer.* -------------------------------------------------------------------------------- /assets/wordpress-password-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collegeman/fuerte/HEAD/assets/wordpress-password-reset.png -------------------------------------------------------------------------------- /src/fuerte-lib.js: -------------------------------------------------------------------------------- 1 | import { Fuerte } from './fuerte' 2 | import fuerte from './fuerte' 3 | 4 | window.Fuerte = Fuerte 5 | window.fuerte = fuerte -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | ", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "laravel-mix": "^6.0.3", 14 | "postcss": "^8.1", 15 | "raw-loader": "^4.0.2" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/collegeman/fuerte/issues" 19 | }, 20 | "homepage": "https://github.com/collegeman/fuerte#readme", 21 | "scripts": { 22 | "test": "echo \"Error: no test specified\" && exit 1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Fuerte.php: -------------------------------------------------------------------------------- 1 | {$name}(...$args); 22 | } 23 | 24 | protected static function instance() 25 | { 26 | return self::$instance ? self::$instance : (self::$instance = new Generator); 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Aaron Collegeman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /fuerte.php: -------------------------------------------------------------------------------- 1 | memorable(), $length, $special_chars, $extra_special_chars); 25 | if ($generator) { 26 | $password = $generator->make(); 27 | } 28 | } 29 | return $password; 30 | }, 10, 4); 31 | 32 | add_filter('password_hint', function () { 33 | return __("Hint: Use a long password composed of words that are meaningful to you: that way it's easy for you to remember but hard for a computer to guess."); 34 | }); 35 | 36 | $enqueued = true; 37 | } 38 | } 39 | } 40 | 41 | add_action('login_enqueue_scripts', 'enqueue_fuerte_password_generator'); 42 | 43 | add_action('login_head', function() { 44 | if (apply_filters('fuerte_style_passwords', true)) { 45 | ?> 46 | 74 | 81 | 82 | 103 | 2 | 3 | 4 | 5 | 6 | Fuerte: a strong password generator with no depenencies 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
26 |
27 |
28 | 29 | 37 |
38 |
39 | 45 | 54 |
55 |
56 | 62 | 75 |
76 |
77 | 83 | 90 |
91 |
92 |
93 | 99 | 105 |
106 |
107 | 113 | 119 |
120 |
121 | 127 | 133 |
134 |
135 | 141 |
142 |
143 |
144 |
145 |
146 | 149 | 150 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | 8, 32 | self::TYPE_MEMORABLE => 4, 33 | self::TYPE_PIN => 3, 34 | ]; 35 | 36 | protected static $maxSize = [ 37 | self::TYPE_RANDOM => 100, 38 | self::TYPE_MEMORABLE => 15, 39 | self::TYPE_PIN => 12, 40 | ]; 41 | 42 | protected $type = self::TYPE_MEMORABLE; 43 | protected $symbols = false; 44 | protected $capitalize = false; 45 | protected $numbers = false; 46 | protected $separator = self::SEPARATOR_SPACES; 47 | protected $size; 48 | 49 | public function __construct() 50 | { 51 | $this->size = self::$minSize[$this->type]; 52 | } 53 | 54 | public function word() 55 | { 56 | if (empty(self::$bank)) { 57 | if (empty(self::$words)) { 58 | self::$words = file_get_contents(__DIR__.'/eff_short_wordlist_1.txt'); 59 | } 60 | self::$bank = explode("\n", self::$words); 61 | } 62 | $i = self::arrayRand(self::$bank); 63 | $word = self::$bank[$i]; 64 | unset(self::$bank[$i]); 65 | return $word; 66 | } 67 | 68 | public function type(string $type) 69 | { 70 | $this->type = $type; 71 | $this->size(0); // uses default min 72 | return $this; 73 | } 74 | 75 | public function random() 76 | { 77 | return $this->type(self::TYPE_RANDOM); 78 | } 79 | 80 | public function pin() 81 | { 82 | return $this->type(self::TYPE_PIN); 83 | } 84 | 85 | public function memorable() 86 | { 87 | return $this->type(self::TYPE_MEMORABLE); 88 | } 89 | 90 | public function symbols($symbols = null) 91 | { 92 | $this->symbols = is_null($symbols) ? true : (bool) $symbols; 93 | return $this; 94 | } 95 | 96 | public function numbers($numbers = null) 97 | { 98 | $this->numbers = is_null($numbers) ? true : (bool) $numbers; 99 | return $this; 100 | } 101 | 102 | public function separator($separator) 103 | { 104 | $this->separator = $separator; 105 | return $this; 106 | } 107 | 108 | public function capitalize($capitalize = null) 109 | { 110 | $this->capitalize = is_null($capitalize) ? true : (bool) $capitalize; 111 | return $this; 112 | } 113 | 114 | public function size(int $size) 115 | { 116 | $this->size = max(self::$minSize[$this->type], min($size, self::$maxSize[$this->type])); 117 | return $this; 118 | } 119 | 120 | /** 121 | * @param array $elements 122 | * @return mixed 123 | * @throws \Exception 124 | */ 125 | public static function arrayRand(array $elements) 126 | { 127 | $keys = array_keys($elements); 128 | $min = 0; 129 | $max = count($keys) - 1; 130 | $rand = random_int($min, $max); 131 | return $keys[$rand]; 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function make() 138 | { 139 | $password = null; 140 | 141 | // Make a pin-type password 142 | if ($this->type === self::TYPE_PIN) { 143 | $password = self::randomChars(self::DIGITS, $this->size); 144 | 145 | // Make a memorable-type password 146 | } else if ($this->type === self::TYPE_MEMORABLE) { 147 | $words = []; 148 | for ($i = 0; $i < $this->size; $i++) { 149 | $words[] = $this->word(); 150 | } 151 | if ($this->capitalize) { 152 | $which = self::arrayRand($words); 153 | $words[$which] = strtoupper($words[$which]); 154 | } 155 | $separatorBanks = []; 156 | $separator = $this->separator; 157 | if ($this->separator === self::SEPARATOR_DIGITS) { 158 | $separator = self::DIGITS; 159 | $separatorBanks[] = self::DIGITS; 160 | } 161 | if ($this->separator === self::SEPARATOR_DIGITS_AND_SYMBOLS) { 162 | $separator = self::DIGITS . self::SYMBOLS; 163 | $separatorBanks[] = self::DIGITS; 164 | $separatorBanks[] = self::SYMBOLS; 165 | } 166 | do { 167 | $password = ''; 168 | foreach ($words as $i => $word) { 169 | $password .= $word; 170 | if ($i < count($words) - 1) { 171 | $password .= self::randomChars($separator, 1); 172 | } 173 | } 174 | } while (!self::containsChars($password, $separatorBanks)); 175 | 176 | // Make a random password 177 | } else { 178 | $banks = []; 179 | $banks[] = self::UPPER; 180 | $banks[] = self::LOWER; 181 | if ($this->symbols) { 182 | $banks[] = self::SYMBOLS; 183 | } 184 | if ($this->numbers) { 185 | $banks[] = self::DIGITS; 186 | } 187 | do { 188 | $password = self::randomChars(implode('', $banks), $this->size); 189 | } while (!self::containsChars($password, $banks)); 190 | } 191 | 192 | return $password; 193 | } 194 | 195 | public function __toString() 196 | { 197 | return $this->make(); 198 | } 199 | 200 | private static function containsChars(string $string, array $banks) 201 | { 202 | if (count($banks) < 1) { 203 | return true; 204 | } 205 | 206 | foreach($banks as $bank) { 207 | $chars = str_split($bank); 208 | $contains = false; 209 | foreach($chars as $char) { 210 | if (stripos($string, $char) !== false) { 211 | $contains = true; 212 | break; 213 | } 214 | } 215 | if (!$contains) { 216 | return false; 217 | } 218 | } 219 | 220 | return true; 221 | } 222 | 223 | private static function randomChars(string $from, int $length) 224 | { 225 | if (strlen($from) < 1) { 226 | return null; 227 | } 228 | if (!$length) { 229 | $length = 1; 230 | } 231 | $bank = []; 232 | $random = []; 233 | while (count($random) < $length) { 234 | if (count($bank) < 1) { 235 | $bank = str_split($from); 236 | } 237 | $i = self::arrayRand($bank); 238 | array_push($random, $bank[$i]); 239 | unset($bank[$i]); 240 | } 241 | return implode('', $random); 242 | } 243 | 244 | } -------------------------------------------------------------------------------- /src/fuerte.js: -------------------------------------------------------------------------------- 1 | import words from './eff_short_wordlist_1.txt' 2 | 3 | let bank = [] 4 | 5 | const Fuerte = function() { 6 | this._listeners = [] 7 | this._symbols = false 8 | this._capitalize = false 9 | this._numbers = false 10 | this._separator = Fuerte.SEPARATOR_SPACES 11 | this.type(Fuerte.TYPE_MEMORABLE) 12 | } 13 | 14 | Fuerte.DIGITS = '0123456789' 15 | Fuerte.SYMBOLS = '!@#_-.*%' 16 | Fuerte.LOWER = 'abcdefghijklmnopqrstuvwxyz' 17 | Fuerte.UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 18 | 19 | Fuerte.TYPE_RANDOM = 'random' 20 | Fuerte.TYPE_MEMORABLE = 'memorable' 21 | Fuerte.TYPE_PIN = 'pin' 22 | 23 | Fuerte.SEPARATOR_SPACES = ' ' 24 | Fuerte.SEPARATOR_HYPHENS = '-' 25 | Fuerte.SEPARATOR_PERIODS = '.' 26 | Fuerte.SEPARATOR_COMMAS = ',' 27 | Fuerte.SEPARATOR_UNDERSCORES = '_' 28 | Fuerte.SEPARATOR_DIGITS = '0' 29 | Fuerte.SEPARATOR_DIGITS_AND_SYMBOLS = '0_' 30 | 31 | const minSize = [] 32 | minSize[Fuerte.TYPE_RANDOM] = 8 33 | minSize[Fuerte.TYPE_MEMORABLE] = 4 34 | minSize[Fuerte.TYPE_PIN] = 3 35 | 36 | const maxSize = [] 37 | maxSize[Fuerte.TYPE_RANDOM] = 100 38 | maxSize[Fuerte.TYPE_MEMORABLE] = 15 39 | maxSize[Fuerte.TYPE_PIN] = 12 40 | 41 | const rand = (elements) => { 42 | var random = new Uint32Array(1); 43 | window.crypto.getRandomValues(random) 44 | return parseFloat('0.' + random[0].toString()) 45 | } 46 | 47 | const containsChars = (string, banks) => { 48 | if (banks.length < 1) { 49 | return true 50 | } 51 | 52 | for(let bank of banks) { 53 | let chars = bank.split('') 54 | let contains = false 55 | for(let char of chars) { 56 | if (string.indexOf(char) > -1) { 57 | contains = true 58 | break 59 | } 60 | } 61 | if (!contains) { 62 | return false 63 | } 64 | } 65 | 66 | return true 67 | } 68 | 69 | const first = (elements) => { 70 | return elements.length ? elements[0] : null 71 | } 72 | 73 | const randomChars = (from, length) => { 74 | if (from.length < 1) { 75 | return null 76 | } 77 | if (!length) { 78 | length = 1 79 | } 80 | let bank = [] 81 | let random = [] 82 | while (random.length < length) { 83 | if (bank.length < 1) { 84 | bank = from.split('') 85 | } 86 | let i = Math.round(rand() * (bank.length-1)) 87 | random.push(bank[i]) 88 | bank.splice(i, 1) 89 | } 90 | return random.join('') 91 | } 92 | 93 | const shuffle = (string) => { 94 | var a = string.split(""), 95 | n = a.length; 96 | 97 | for(var i = n - 1; i > 0; i--) { 98 | var j = Math.floor(rand() * (i + 1)); 99 | var tmp = a[i]; 100 | a[i] = a[j]; 101 | a[j] = tmp; 102 | } 103 | return string.join(""); 104 | } 105 | 106 | Fuerte.prototype.addEventListener = function(listener) { 107 | this._listeners.push(listener) 108 | return this 109 | } 110 | 111 | Fuerte.prototype.removeEventListener = function(listener) { 112 | for (let i in this._listeners) { 113 | if (this._listeners[i] === listener) { 114 | this._listeners.splice(i, 1) 115 | break; 116 | } 117 | } 118 | return this 119 | } 120 | 121 | Fuerte.prototype.fire = function(event, data) { 122 | for (let listener of this._listeners) { 123 | listener(event, data) 124 | } 125 | return this 126 | } 127 | 128 | Fuerte.prototype.fireChangeEvent = function() { 129 | return this.fire('change', { 130 | type: this._type, 131 | size: this._size, 132 | capitalize: this._capitalize, 133 | numbers: this._numbers, 134 | symbols: this._symbols, 135 | size_min: minSize[this._type], 136 | size_max: maxSize[this._type], 137 | }) 138 | } 139 | 140 | Fuerte.prototype.type = function(type) { 141 | this._type = type 142 | this.fireChangeEvent() 143 | return this.size(0) // uses default min 144 | } 145 | 146 | Fuerte.prototype.random = function() { 147 | return this.type(Fuerte.TYPE_RANDOM) 148 | } 149 | 150 | Fuerte.prototype.memorable = function() { 151 | return this.type(Fuerte.TYPE_MEMORABLE) 152 | } 153 | 154 | Fuerte.prototype.pin = function() { 155 | return this.type(Fuerte.TYPE_PIN) 156 | } 157 | 158 | Fuerte.prototype.symbols = function(bool) { 159 | this._symbols = bool === undefined ? true : !!bool 160 | this.fireChangeEvent() 161 | return this 162 | } 163 | 164 | Fuerte.prototype.numbers = function(bool) { 165 | this._numbers = bool === undefined ? true : !!bool 166 | this.fireChangeEvent() 167 | return this 168 | } 169 | 170 | Fuerte.prototype.separator = function(separator) { 171 | this._separator = separator 172 | this.fireChangeEvent() 173 | return this 174 | } 175 | 176 | Fuerte.prototype.capitalize = function(bool) { 177 | this._capitalize = bool === undefined ? true : !!bool 178 | this.fireChangeEvent() 179 | return this 180 | } 181 | 182 | Fuerte.prototype.size = function(number) { 183 | let n = parseInt(number) 184 | this._size = Math.max(minSize[this._type], Math.min(n, maxSize[this._type])) 185 | this.fireChangeEvent() 186 | return this 187 | } 188 | 189 | Fuerte.prototype.word = function() { 190 | if (bank.length < 1) { 191 | bank = words.split("\n") 192 | } 193 | let i = Math.round(rand() * (bank.length-1)) 194 | let word = bank[i] 195 | bank.splice(i, 1) 196 | return word 197 | } 198 | 199 | Fuerte.prototype.toString = function() { 200 | return this.make() 201 | } 202 | 203 | Fuerte.prototype.make = function() { 204 | let password = null 205 | 206 | // Make a pin-type password 207 | if (this._type === Fuerte.TYPE_PIN) { 208 | password = randomChars(Fuerte.DIGITS, this._size) 209 | 210 | // Make a memorable-type password 211 | } else if (this._type === Fuerte.TYPE_MEMORABLE) { 212 | let words = [] 213 | for (let i = 0; i < this._size; i++) { 214 | words.push(this.word()) 215 | } 216 | if (this._capitalize) { 217 | let which = Math.round(rand() * (words.length-1)) 218 | words[which] = words[which].toUpperCase() 219 | } 220 | let separatorBanks = [] 221 | let separator = this._separator 222 | if (this._separator === Fuerte.SEPARATOR_DIGITS) { 223 | separator = Fuerte.DIGITS 224 | separatorBanks.push(Fuerte.DIGITS) 225 | } else if (this._separator === Fuerte.SEPARATOR_DIGITS_AND_SYMBOLS) { 226 | separator = Fuerte.DIGITS + Fuerte.SYMBOLS 227 | separatorBanks.push(Fuerte.DIGITS) 228 | separatorBanks.push(Fuerte.SYMBOLS) 229 | } 230 | do { 231 | password = '' 232 | for (let i in words) { 233 | password += words[i] 234 | if (i < words.length - 1) { 235 | password += randomChars(separator, 1) 236 | } 237 | } 238 | } while (!containsChars(password, separatorBanks)) 239 | 240 | // Make a random password 241 | } else { 242 | let banks = [] 243 | banks.push(Fuerte.UPPER) 244 | banks.push(Fuerte.LOWER) 245 | if (this._symbols) { 246 | banks.push(Fuerte.SYMBOLS) 247 | } 248 | if (this._numbers) { 249 | banks.push(Fuerte.DIGITS) 250 | } 251 | do { 252 | password = randomChars(banks.join(''), this._size) 253 | } while (!containsChars(password, banks)) 254 | } 255 | 256 | this.fire('make', { password }) 257 | return password 258 | } 259 | 260 | Fuerte.prototype.form = function(el) { 261 | let instance = this 262 | 263 | let password = first(el.querySelectorAll(':scope [data-fuerte="password"]')) 264 | 265 | let btnGenerate = first(el.querySelectorAll(':scope [data-fuerte="generate"]')) 266 | if (btnGenerate) { 267 | btnGenerate.addEventListener('click', (e) => { 268 | instance.make() 269 | e.preventDefault() 270 | }) 271 | } 272 | 273 | let selectType = first(el.querySelectorAll(':scope [data-fuerte="type"]')) 274 | if (selectType) { 275 | selectType.addEventListener('change', (e) => { 276 | instance.type(selectType.value) 277 | instance.make() 278 | }) 279 | } 280 | 281 | let selectSeparator = first(el.querySelectorAll(':scope [data-fuerte="separator"]')) 282 | if (selectSeparator) { 283 | selectSeparator.addEventListener('change', (e) => { 284 | instance.separator(selectSeparator.value) 285 | instance.make() 286 | }) 287 | } 288 | 289 | let checkCapitalize = first(el.querySelectorAll(':scope [data-fuerte="capitalize"]')) 290 | if (checkCapitalize) { 291 | checkCapitalize.addEventListener('change', (e) => { 292 | instance.capitalize(checkCapitalize.checked) 293 | instance.make() 294 | }) 295 | } 296 | 297 | let checkNumbers = first(el.querySelectorAll(':scope [data-fuerte="numbers"]')) 298 | if (checkNumbers) { 299 | checkNumbers.addEventListener('change', (e) => { 300 | instance.numbers(checkNumbers.checked) 301 | instance.make() 302 | }) 303 | } 304 | 305 | let checkSymbols = first(el.querySelectorAll(':scope [data-fuerte="symbols"]')) 306 | if (checkSymbols) { 307 | checkSymbols.addEventListener('change', (e) => { 308 | instance.symbols(checkSymbols.checked) 309 | instance.make() 310 | }) 311 | } 312 | 313 | let rangeSize = first(el.querySelectorAll(':scope [data-fuerte="size"]')) 314 | let rangeSizeLabel = null 315 | if (rangeSize) { 316 | rangeSize.addEventListener('change', (e) => { 317 | instance.size(rangeSize.value) 318 | instance.make() 319 | }) 320 | if (rangeSize.id) { 321 | rangeSizeLabel = first(document.querySelectorAll('label[for="' + rangeSize.id + '"]')) 322 | } 323 | } 324 | 325 | instance.addEventListener((event, data) => { 326 | if (event === 'make') { 327 | if (password) { 328 | password.value = data.password 329 | } 330 | } 331 | 332 | if (event === 'change') { 333 | if (checkCapitalize) { 334 | checkCapitalize.value = data.capitalize 335 | checkCapitalize.disabled = data.type !== Fuerte.TYPE_MEMORABLE 336 | } 337 | if (checkNumbers) { 338 | checkNumbers.value = data.numbers 339 | checkNumbers.disabled = data.type !== Fuerte.TYPE_RANDOM 340 | } 341 | if (checkSymbols) { 342 | checkSymbols.value = data.symbols 343 | checkSymbols.disabled = data.type !== Fuerte.TYPE_RANDOM 344 | } 345 | if (selectSeparator) { 346 | selectSeparator.disabled = data.type !== Fuerte.TYPE_MEMORABLE 347 | } 348 | if (rangeSize) { 349 | rangeSize.min = data.size_min 350 | rangeSize.max = data.size_max 351 | rangeSize.value = data.size 352 | rangeSize.step = 1 353 | if (rangeSizeLabel) { 354 | let label = data.size 355 | if (data.type === Fuerte.TYPE_MEMORABLE) { 356 | label += ' words' 357 | } else if (data.type === Fuerte.TYPE_PIN) { 358 | label += ' digits' 359 | } else { 360 | label += ' characters' 361 | } 362 | rangeSizeLabel.innerHTML = label 363 | } 364 | } 365 | } 366 | }) 367 | 368 | instance.fireChangeEvent() 369 | 370 | instance.make() 371 | } 372 | 373 | export { Fuerte } 374 | 375 | export default function(el) { 376 | let instance = new Fuerte() 377 | if (el) { 378 | instance.form(el) 379 | } 380 | return instance 381 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuerte is a strong password generator with no dependencies 2 | 3 | "Through 20 years of effort, we've successfully trained everyone 4 | to use passwords that are hard for humans to remember, but easy 5 | for computers to guess." — [XKCD](https://xkcd.com/936/) 6 | 7 | ![To anyone who understands information theory and security and is in an infuriating argument with someone who does not (possibly involving mixed case), I sincerely apologize.](https://imgs.xkcd.com/comics/password_strength.png) 8 | 9 | Fuerte is a simple, strong password generator. 10 | 11 | It can create strong 12 | passwords that you should be using, because they're long and 13 | easy to remember, like `correct horse battery staple`, 14 | but it can also create the sorts of strong passwords that your bank 15 | and your insurance company want you to use, because they're 16 | complicated, like `Tr0ub4dor&3`. 17 | 18 | In a pinch, it can even blend the two methods to create something 19 | like `correct!HORSE3battery%staple`, which, while difficult to 20 | memorize, is at least equally difficult for a computer to guess. 21 | 22 | The library is available for JavaScript and PHP projects, and if 23 | you're using PHP, it installs easily into Laravel and WordPress 24 | codebases. 25 | 26 | As of version `1.0.7`, both libraries use cryptographically secure 27 | random number generators. 28 | 29 | ## Need a new password right now? 30 | 31 | You can experience Fuerte in action on [makepass.dev](https://makepass.dev). Enjoy! 32 | 33 | ## Quickstart Guide 34 | 35 | There are two versions of Fuerte: one for JavaScript and one for PHP. 36 | Both are a part of this codebase. 37 | 38 | You can also use the PHP library as a WordPress plugin or as a Laravel 39 | package. 40 | 41 | You only need one of these solutions for your project, but they 42 | can also be used together. 43 | 44 | ### Using the JavaScript library 45 | 46 | The Fuerte package is available via NPM: 47 | 48 | ```bash 49 | npm install @collegeman/fuerte 50 | ``` 51 | 52 | You can also just download this repository, place the uncompressed files 53 | among your project's assets, and then load the the library: 54 | 55 | ```html 56 | 57 | 60 | ``` 61 | 62 | You can also load the library via jsdeliver: 63 | 64 | ```html 65 | 66 | 69 | ``` 70 | 71 | Once added to your project, you can import Fuerte's API like this: 72 | 73 | ```js 74 | import fuerte from '@collegeman/fuerte' 75 | let password = fuerte().make() // a random, memorable password! 76 | ```` 77 | 78 | ### Using the PHP library 79 | 80 | The Fuerte package can be installed using Composer: 81 | 82 | ```bash 83 | composer require collegeman/fuerte 84 | ``` 85 | 86 | Assuming there are no other functions in your codebase named `fuerte`, you can 87 | use Fuerte like this: 88 | 89 | ```php 90 | make(); // a random, memorable password! 93 | ``` 94 | 95 | If a conflict exists preventing the helper function from being created, you 96 | can access Fuerte via its facade: 97 | 98 | ```php 99 | memorable()->separator('.')->capitalize()->size(6)->make(); 142 | // e.g., "snout.exit.SCUBA.watch.silly.hash" 143 | ``` 144 | 145 | Because the API is consistent across platforms, all remaining examples use the JavaScript version. 146 | 147 | ### Password type 148 | 149 | Fuerte can generate three types of strong passwords: **memorable** (like `correct horse battery staple`), 150 | **random** (like `Tr0ub4dor&3`), and **PIN** (like `1234`). 151 | 152 | You can tell Fuerte what type of password you want just by 153 | invoking the type name: 154 | 155 | ```js 156 | fuerte().memorable().make() // e.g., "film rhyme stunt coat" 157 | fuerte().random().make() // e.g., "TlCxbiKd" 158 | fuerte().pin().make() // e.g., "476" 159 | ``` 160 | 161 | ### Length/size 162 | 163 | Password length matters: password length not complexity is what makes a password 164 | hard for a computer to crack (see XKCD comic above). 165 | 166 | Each type of password has a minimum length. When you tell Fuerte what type of 167 | password you want to generate, it will assume you want to use the minimum safe 168 | length for that password. You can then tell Fuerte how long you want the password 169 | to be using the `size()` method: 170 | 171 | ```js 172 | fuerte().memorable().size(5).make() // use 5 words: "polo blush dug cola lance" 173 | fuerte().random().size(32).make() // use 32 characters: "mSyDqEQwZkgdUINljHiJnLsaYcWbrOuK" 174 | fuerte().pin().size(6).make() // use 6 digits: 386419 175 | ``` 176 | 177 | ### Separators 178 | 179 | By default, the **memorable** password type uses spaces to separate the words 180 | in the password. You can tell it what you want the separator to be using 181 | the `separator(string)` method: 182 | 183 | ```js 184 | fuerte().memorable().separator('-').make() // "stark-jog-copy-lilac" 185 | fuerte().memorable().separator('.').make() // "avoid.sleet.gas.view" 186 | ``` 187 | 188 | See *Symbols and Digits* below for more options. 189 | 190 | ### Symbols and Digits 191 | 192 | Sometimes a system will require passwords to contain symbols or digits. 193 | 194 | The **random** password type has two additional flags you can set: `symbols()` 195 | and `numbers()`. These flags will ensure that the passwords generated 196 | contain at least 1 of each special character: 197 | 198 | ```js 199 | fuerte().random().numbers().make() // e.g., "1A2oPvxR" 200 | fuerte().random().symbols().make() // e.g., "kQm#_xiA" 201 | fuerte().random().numbers().symbols().make() // e.g., "ZCJhGO5%" 202 | ``` 203 | 204 | The **memorable** password type supports Symbols and Digits using the 205 | `separator()` method. If you need a memorable password that contains 206 | Digits, do this: 207 | 208 | ```js 209 | fuerte().memorable().separator('0').make() // e.g., "chump9vixen6good9bud" 210 | ``` 211 | 212 | If you need a memorable password that contains Symbols *and* Digits, do this: 213 | 214 | ```js 215 | fuerte().memorable().separator('0_').make() // e.g., "cramp2dill#grant1coach" 216 | ``` 217 | 218 | ### Capitalization 219 | 220 | If you need a memorable password that contains capital letters, use the 221 | `capitalize()` method: 222 | 223 | ```js 224 | fuerte().memorable().capitalize().make() // e.g., "slept taco START prior" 225 | ``` 226 | 227 | You can use `capitalize()` in combination with the `separator(string)` method 228 | to create an almost-memorable password that also satisfies the goofy requirements 229 | for using complex characters: 230 | 231 | ```js 232 | let password = fuerte().memorable().capitalize().separator('0_').make() 233 | // e.g., "tusk9query*cross9DRAB" 234 | ``` 235 | 236 | ## WordPress plugin configuration 237 | 238 | By default, the WordPress plugin will have WordPress use Fuerte when it needs 239 | to generate and display a suggestion for a new password, like on the password 240 | reset screen: 241 | 242 | ![](assets/wordpress-password-reset.png) 243 | 244 | Just click the reload button (either in the UI on the page or on the browser) 245 | to get another suggestion. 246 | 247 | If you don't want Fuerte to add these features to WordPress, just add the 248 | following code to your theme's `functions.php` or to a must-use plugin file: 249 | 250 | ```php 251 | random()->size(12)->symbols()->numbers(); 262 | // ...but why would you want to make the passwords suck again? 263 | }); 264 | ``` 265 | 266 | ## Support 267 | 268 | If you find a problem with Fuerte, please post an [issue](https://github.com/collegeman/fuerte/issues) on GitHub. 269 | 270 | ## Credits 271 | 272 | The inspiration for a cross-platform password generator with a consistent API 273 | comes from the XKCD comic above as well as requirements from several of 274 | the projects I'm working on at the moment. The image has been used without 275 | explicit permission. 276 | 277 | The features of the generator were inspired and constrained by the 278 | minimal design of the password generator built into [1Password](https://1password.com/). 279 | If you don't use a password manager, I highly recommend you give 280 | 1Password a try. 281 | 282 | The word list used by Fuerte for generating memorable passwords belongs 283 | to the [EFF](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases). 284 | It was used without permission. 285 | 286 | The photo of padlocks is by DynamicWang on Unsplash. 287 | 288 | ## License 289 | 290 | Copyright 2020 Aaron Collegeman 291 | 292 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 293 | 294 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 295 | 296 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dist/fuerte.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";function n(n,t){var a;if("undefined"==typeof Symbol||null==n[Symbol.iterator]){if(Array.isArray(n)||(a=function(n,t){if(!n)return;if("string"==typeof n)return e(n,t);var a=Object.prototype.toString.call(n).slice(8,-1);"Object"===a&&n.constructor&&(a=n.constructor.name);if("Map"===a||"Set"===a)return Array.from(n);if("Arguments"===a||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(a))return e(n,t)}(n))||t&&n&&"number"==typeof n.length){a&&(n=a);var r=0,s=function(){};return{s,n:function(){return r>=n.length?{done:!0}:{done:!1,value:n[r++]}},e:function(n){throw n},f:s}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,l=!1;return{s:function(){a=n[Symbol.iterator]()},n:function(){var n=a.next();return i=n.done,n},e:function(n){l=!0,o=n},f:function(){try{i||null==a.return||a.return()}finally{if(l)throw o}}}}function e(n,e){(null==e||e>n.length)&&(e=n.length);for(var t=0,a=new Array(e);t-1){i=!0;break}}}catch(n){l.e(n)}finally{l.f()}if(!i)return!1}}catch(n){r.e(n)}finally{r.f()}return!0},l=function(n){return n.length?n[0]:null},u=function(n,e){if(n.length<1)return null;e||(e=1);for(var t=[],a=[];a.length{"use strict";var n,e={315:(n,e,t)=>{t.d(e,{v:()=>s,Z:()=>d});function a(n,e){var t;if("undefined"==typeof Symbol||null==n[Symbol.iterator]){if(Array.isArray(n)||(t=function(n,e){if(!n)return;if("string"==typeof n)return r(n,e);var t=Object.prototype.toString.call(n).slice(8,-1);"Object"===t&&n.constructor&&(t=n.constructor.name);if("Map"===t||"Set"===t)return Array.from(n);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return r(n,e)}(n))||e&&n&&"number"==typeof n.length){t&&(n=t);var a=0,o=function(){};return{s:o,n:function(){return a>=n.length?{done:!0}:{done:!1,value:n[a++]}},e:function(n){throw n},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var s,i=!0,l=!1;return{s:function(){t=n[Symbol.iterator]()},n:function(){var n=t.next();return i=n.done,n},e:function(n){l=!0,s=n},f:function(){try{i||null==t.return||t.return()}finally{if(l)throw s}}}}function r(n,e){(null==e||e>n.length)&&(e=n.length);for(var t=0,a=new Array(e);t-1){i=!0;break}}}catch(n){l.e(n)}finally{l.f()}if(!i)return!1}}catch(n){r.e(n)}finally{r.f()}return!0},p=function(n){return n.length?n[0]:null},h=function(n,e){if(n.length<1)return null;e||(e=1);for(var t=[],a=[];a.length{for(var t in e)a.o(e,t)&&!a.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:e[t]})},a.o=(n,e)=>Object.prototype.hasOwnProperty.call(n,e),n=a(315),window.Fuerte=n.v,window.fuerte=n.Z})(); -------------------------------------------------------------------------------- /src/eff_short_wordlist_1.txt: -------------------------------------------------------------------------------- 1 | acid 2 | acorn 3 | acre 4 | acts 5 | afar 6 | affix 7 | aged 8 | agent 9 | agile 10 | aging 11 | agony 12 | ahead 13 | aide 14 | aids 15 | aim 16 | ajar 17 | alarm 18 | alias 19 | alibi 20 | alien 21 | alike 22 | alive 23 | aloe 24 | aloft 25 | aloha 26 | alone 27 | amend 28 | amino 29 | ample 30 | amuse 31 | angel 32 | anger 33 | angle 34 | ankle 35 | apple 36 | april 37 | apron 38 | aqua 39 | area 40 | arena 41 | argue 42 | arise 43 | armed 44 | armor 45 | army 46 | aroma 47 | array 48 | arson 49 | art 50 | ashen 51 | ashes 52 | atlas 53 | atom 54 | attic 55 | audio 56 | avert 57 | avoid 58 | awake 59 | award 60 | awoke 61 | axis 62 | bacon 63 | badge 64 | bagel 65 | baggy 66 | baked 67 | baker 68 | balmy 69 | banjo 70 | barge 71 | barn 72 | bash 73 | basil 74 | bask 75 | batch 76 | bath 77 | baton 78 | bats 79 | blade 80 | blank 81 | blast 82 | blaze 83 | bleak 84 | blend 85 | bless 86 | blimp 87 | blink 88 | bloat 89 | blob 90 | blog 91 | blot 92 | blunt 93 | blurt 94 | blush 95 | boast 96 | boat 97 | body 98 | boil 99 | bok 100 | bolt 101 | boned 102 | boney 103 | bonus 104 | bony 105 | book 106 | booth 107 | boots 108 | boss 109 | botch 110 | both 111 | boxer 112 | breed 113 | bribe 114 | brick 115 | bride 116 | brim 117 | bring 118 | brink 119 | brisk 120 | broad 121 | broil 122 | broke 123 | brook 124 | broom 125 | brush 126 | buck 127 | bud 128 | buggy 129 | bulge 130 | bulk 131 | bully 132 | bunch 133 | bunny 134 | bunt 135 | bush 136 | bust 137 | busy 138 | buzz 139 | cable 140 | cache 141 | cadet 142 | cage 143 | cake 144 | calm 145 | cameo 146 | canal 147 | candy 148 | cane 149 | canon 150 | cape 151 | card 152 | cargo 153 | carol 154 | carry 155 | carve 156 | case 157 | cash 158 | cause 159 | cedar 160 | chain 161 | chair 162 | chant 163 | chaos 164 | charm 165 | chase 166 | cheek 167 | cheer 168 | chef 169 | chess 170 | chest 171 | chew 172 | chief 173 | chili 174 | chill 175 | chip 176 | chomp 177 | chop 178 | chow 179 | chuck 180 | chump 181 | chunk 182 | churn 183 | chute 184 | cider 185 | cinch 186 | city 187 | civic 188 | civil 189 | clad 190 | claim 191 | clamp 192 | clap 193 | clash 194 | clasp 195 | class 196 | claw 197 | clay 198 | clean 199 | clear 200 | cleat 201 | cleft 202 | clerk 203 | click 204 | cling 205 | clink 206 | clip 207 | cloak 208 | clock 209 | clone 210 | cloth 211 | cloud 212 | clump 213 | coach 214 | coast 215 | coat 216 | cod 217 | coil 218 | coke 219 | cola 220 | cold 221 | colt 222 | coma 223 | come 224 | comic 225 | comma 226 | cone 227 | cope 228 | copy 229 | coral 230 | cork 231 | cost 232 | cot 233 | couch 234 | cough 235 | cover 236 | cozy 237 | craft 238 | cramp 239 | crane 240 | crank 241 | crate 242 | crave 243 | crawl 244 | crazy 245 | creme 246 | crepe 247 | crept 248 | crib 249 | cried 250 | crisp 251 | crook 252 | crop 253 | cross 254 | crowd 255 | crown 256 | crumb 257 | crush 258 | crust 259 | cub 260 | cult 261 | cupid 262 | cure 263 | curl 264 | curry 265 | curse 266 | curve 267 | curvy 268 | cushy 269 | cut 270 | cycle 271 | dab 272 | dad 273 | daily 274 | dairy 275 | daisy 276 | dance 277 | dandy 278 | darn 279 | dart 280 | dash 281 | data 282 | date 283 | dawn 284 | deaf 285 | deal 286 | dean 287 | debit 288 | debt 289 | debug 290 | decaf 291 | decal 292 | decay 293 | deck 294 | decor 295 | decoy 296 | deed 297 | delay 298 | denim 299 | dense 300 | dent 301 | depth 302 | derby 303 | desk 304 | dial 305 | diary 306 | dice 307 | dig 308 | dill 309 | dime 310 | dimly 311 | diner 312 | dingy 313 | disco 314 | dish 315 | disk 316 | ditch 317 | ditzy 318 | dizzy 319 | dock 320 | dodge 321 | doing 322 | doll 323 | dome 324 | donor 325 | donut 326 | dose 327 | dot 328 | dove 329 | down 330 | dowry 331 | doze 332 | drab 333 | drama 334 | drank 335 | draw 336 | dress 337 | dried 338 | drift 339 | drill 340 | drive 341 | drone 342 | droop 343 | drove 344 | drown 345 | drum 346 | dry 347 | duck 348 | duct 349 | dude 350 | dug 351 | duke 352 | duo 353 | dusk 354 | dust 355 | duty 356 | dwarf 357 | dwell 358 | eagle 359 | early 360 | earth 361 | easel 362 | east 363 | eaten 364 | eats 365 | ebay 366 | ebony 367 | ebook 368 | echo 369 | edge 370 | eel 371 | eject 372 | elbow 373 | elder 374 | elf 375 | elk 376 | elm 377 | elope 378 | elude 379 | elves 380 | email 381 | emit 382 | empty 383 | emu 384 | enter 385 | entry 386 | envoy 387 | equal 388 | erase 389 | error 390 | erupt 391 | essay 392 | etch 393 | evade 394 | even 395 | evict 396 | evil 397 | evoke 398 | exact 399 | exit 400 | fable 401 | faced 402 | fact 403 | fade 404 | fall 405 | false 406 | fancy 407 | fang 408 | fax 409 | feast 410 | feed 411 | femur 412 | fence 413 | fend 414 | ferry 415 | fetal 416 | fetch 417 | fever 418 | fiber 419 | fifth 420 | fifty 421 | film 422 | filth 423 | final 424 | finch 425 | fit 426 | five 427 | flag 428 | flaky 429 | flame 430 | flap 431 | flask 432 | fled 433 | flick 434 | fling 435 | flint 436 | flip 437 | flirt 438 | float 439 | flock 440 | flop 441 | floss 442 | flyer 443 | foam 444 | foe 445 | fog 446 | foil 447 | folic 448 | folk 449 | food 450 | fool 451 | found 452 | fox 453 | foyer 454 | frail 455 | frame 456 | fray 457 | fresh 458 | fried 459 | frill 460 | frisk 461 | from 462 | front 463 | frost 464 | froth 465 | frown 466 | froze 467 | fruit 468 | gag 469 | gains 470 | gala 471 | game 472 | gap 473 | gas 474 | gave 475 | gear 476 | gecko 477 | geek 478 | gem 479 | genre 480 | gift 481 | gig 482 | gills 483 | given 484 | giver 485 | glad 486 | glass 487 | glide 488 | gloss 489 | glove 490 | glow 491 | glue 492 | goal 493 | going 494 | golf 495 | gong 496 | good 497 | gooey 498 | goofy 499 | gore 500 | gown 501 | grab 502 | grain 503 | grant 504 | grape 505 | graph 506 | grasp 507 | grass 508 | grave 509 | gravy 510 | gray 511 | green 512 | greet 513 | grew 514 | grid 515 | grief 516 | grill 517 | grip 518 | grit 519 | groom 520 | grope 521 | growl 522 | grub 523 | grunt 524 | guide 525 | gulf 526 | gulp 527 | gummy 528 | guru 529 | gush 530 | gut 531 | guy 532 | habit 533 | half 534 | halo 535 | halt 536 | happy 537 | harm 538 | hash 539 | hasty 540 | hatch 541 | hate 542 | haven 543 | hazel 544 | hazy 545 | heap 546 | heat 547 | heave 548 | hedge 549 | hefty 550 | help 551 | herbs 552 | hers 553 | hub 554 | hug 555 | hula 556 | hull 557 | human 558 | humid 559 | hump 560 | hung 561 | hunk 562 | hunt 563 | hurry 564 | hurt 565 | hush 566 | hut 567 | ice 568 | icing 569 | icon 570 | icy 571 | igloo 572 | image 573 | ion 574 | iron 575 | islam 576 | issue 577 | item 578 | ivory 579 | ivy 580 | jab 581 | jam 582 | jaws 583 | jazz 584 | jeep 585 | jelly 586 | jet 587 | jiffy 588 | job 589 | jog 590 | jolly 591 | jolt 592 | jot 593 | joy 594 | judge 595 | juice 596 | juicy 597 | july 598 | jumbo 599 | jump 600 | junky 601 | juror 602 | jury 603 | keep 604 | keg 605 | kept 606 | kick 607 | kilt 608 | king 609 | kite 610 | kitty 611 | kiwi 612 | knee 613 | knelt 614 | koala 615 | kung 616 | ladle 617 | lady 618 | lair 619 | lake 620 | lance 621 | land 622 | lapel 623 | large 624 | lash 625 | lasso 626 | last 627 | latch 628 | late 629 | lazy 630 | left 631 | legal 632 | lemon 633 | lend 634 | lens 635 | lent 636 | level 637 | lever 638 | lid 639 | life 640 | lift 641 | lilac 642 | lily 643 | limb 644 | limes 645 | line 646 | lint 647 | lion 648 | lip 649 | list 650 | lived 651 | liver 652 | lunar 653 | lunch 654 | lung 655 | lurch 656 | lure 657 | lurk 658 | lying 659 | lyric 660 | mace 661 | maker 662 | malt 663 | mama 664 | mango 665 | manor 666 | many 667 | map 668 | march 669 | mardi 670 | marry 671 | mash 672 | match 673 | mate 674 | math 675 | moan 676 | mocha 677 | moist 678 | mold 679 | mom 680 | moody 681 | mop 682 | morse 683 | most 684 | motor 685 | motto 686 | mount 687 | mouse 688 | mousy 689 | mouth 690 | move 691 | movie 692 | mower 693 | mud 694 | mug 695 | mulch 696 | mule 697 | mull 698 | mumbo 699 | mummy 700 | mural 701 | muse 702 | music 703 | musky 704 | mute 705 | nacho 706 | nag 707 | nail 708 | name 709 | nanny 710 | nap 711 | navy 712 | near 713 | neat 714 | neon 715 | nerd 716 | nest 717 | net 718 | next 719 | niece 720 | ninth 721 | nutty 722 | oak 723 | oasis 724 | oat 725 | ocean 726 | oil 727 | old 728 | olive 729 | omen 730 | onion 731 | only 732 | ooze 733 | opal 734 | open 735 | opera 736 | opt 737 | otter 738 | ouch 739 | ounce 740 | outer 741 | oval 742 | oven 743 | owl 744 | ozone 745 | pace 746 | pagan 747 | pager 748 | palm 749 | panda 750 | panic 751 | pants 752 | panty 753 | paper 754 | park 755 | party 756 | pasta 757 | patch 758 | path 759 | patio 760 | payer 761 | pecan 762 | penny 763 | pep 764 | perch 765 | perky 766 | perm 767 | pest 768 | petal 769 | petri 770 | petty 771 | photo 772 | plank 773 | plant 774 | plaza 775 | plead 776 | plot 777 | plow 778 | pluck 779 | plug 780 | plus 781 | poach 782 | pod 783 | poem 784 | poet 785 | pogo 786 | point 787 | poise 788 | poker 789 | polar 790 | polio 791 | polka 792 | polo 793 | pond 794 | pony 795 | poppy 796 | pork 797 | poser 798 | pouch 799 | pound 800 | pout 801 | power 802 | prank 803 | press 804 | print 805 | prior 806 | prism 807 | prize 808 | probe 809 | prong 810 | proof 811 | props 812 | prude 813 | prune 814 | pry 815 | pug 816 | pull 817 | pulp 818 | pulse 819 | puma 820 | punch 821 | punk 822 | pupil 823 | puppy 824 | purr 825 | purse 826 | push 827 | putt 828 | quack 829 | quake 830 | query 831 | quiet 832 | quill 833 | quilt 834 | quit 835 | quota 836 | quote 837 | rabid 838 | race 839 | rack 840 | radar 841 | radio 842 | raft 843 | rage 844 | raid 845 | rail 846 | rake 847 | rally 848 | ramp 849 | ranch 850 | range 851 | rank 852 | rant 853 | rash 854 | raven 855 | reach 856 | react 857 | ream 858 | rebel 859 | recap 860 | relax 861 | relay 862 | relic 863 | remix 864 | repay 865 | repel 866 | reply 867 | rerun 868 | reset 869 | rhyme 870 | rice 871 | rich 872 | ride 873 | rigid 874 | rigor 875 | rinse 876 | riot 877 | ripen 878 | rise 879 | risk 880 | ritzy 881 | rival 882 | river 883 | roast 884 | robe 885 | robin 886 | rock 887 | rogue 888 | roman 889 | romp 890 | rope 891 | rover 892 | royal 893 | ruby 894 | rug 895 | ruin 896 | rule 897 | runny 898 | rush 899 | rust 900 | rut 901 | sadly 902 | sage 903 | said 904 | saint 905 | salad 906 | salon 907 | salsa 908 | salt 909 | same 910 | sandy 911 | santa 912 | satin 913 | sauna 914 | saved 915 | savor 916 | sax 917 | say 918 | scale 919 | scam 920 | scan 921 | scare 922 | scarf 923 | scary 924 | scoff 925 | scold 926 | scoop 927 | scoot 928 | scope 929 | score 930 | scorn 931 | scout 932 | scowl 933 | scrap 934 | scrub 935 | scuba 936 | scuff 937 | sect 938 | sedan 939 | self 940 | send 941 | sepia 942 | serve 943 | set 944 | seven 945 | shack 946 | shade 947 | shady 948 | shaft 949 | shaky 950 | sham 951 | shape 952 | share 953 | sharp 954 | shed 955 | sheep 956 | sheet 957 | shelf 958 | shell 959 | shine 960 | shiny 961 | ship 962 | shirt 963 | shock 964 | shop 965 | shore 966 | shout 967 | shove 968 | shown 969 | showy 970 | shred 971 | shrug 972 | shun 973 | shush 974 | shut 975 | shy 976 | sift 977 | silk 978 | silly 979 | silo 980 | sip 981 | siren 982 | sixth 983 | size 984 | skate 985 | skew 986 | skid 987 | skier 988 | skies 989 | skip 990 | skirt 991 | skit 992 | sky 993 | slab 994 | slack 995 | slain 996 | slam 997 | slang 998 | slash 999 | slate 1000 | slaw 1001 | sled 1002 | sleek 1003 | sleep 1004 | sleet 1005 | slept 1006 | slice 1007 | slick 1008 | slimy 1009 | sling 1010 | slip 1011 | slit 1012 | slob 1013 | slot 1014 | slug 1015 | slum 1016 | slurp 1017 | slush 1018 | small 1019 | smash 1020 | smell 1021 | smile 1022 | smirk 1023 | smog 1024 | snack 1025 | snap 1026 | snare 1027 | snarl 1028 | sneak 1029 | sneer 1030 | sniff 1031 | snore 1032 | snort 1033 | snout 1034 | snowy 1035 | snub 1036 | snuff 1037 | speak 1038 | speed 1039 | spend 1040 | spent 1041 | spew 1042 | spied 1043 | spill 1044 | spiny 1045 | spoil 1046 | spoke 1047 | spoof 1048 | spool 1049 | spoon 1050 | sport 1051 | spot 1052 | spout 1053 | spray 1054 | spree 1055 | spur 1056 | squad 1057 | squat 1058 | squid 1059 | stack 1060 | staff 1061 | stage 1062 | stain 1063 | stall 1064 | stamp 1065 | stand 1066 | stank 1067 | stark 1068 | start 1069 | stash 1070 | state 1071 | stays 1072 | steam 1073 | steep 1074 | stem 1075 | step 1076 | stew 1077 | stick 1078 | sting 1079 | stir 1080 | stock 1081 | stole 1082 | stomp 1083 | stony 1084 | stood 1085 | stool 1086 | stoop 1087 | stop 1088 | storm 1089 | stout 1090 | stove 1091 | straw 1092 | stray 1093 | strut 1094 | stuck 1095 | stud 1096 | stuff 1097 | stump 1098 | stung 1099 | stunt 1100 | suds 1101 | sugar 1102 | sulk 1103 | surf 1104 | sushi 1105 | swab 1106 | swan 1107 | swarm 1108 | sway 1109 | swear 1110 | sweat 1111 | sweep 1112 | swell 1113 | swept 1114 | swim 1115 | swing 1116 | swipe 1117 | swirl 1118 | swoop 1119 | swore 1120 | syrup 1121 | tacky 1122 | taco 1123 | tag 1124 | take 1125 | tall 1126 | talon 1127 | tamer 1128 | tank 1129 | taper 1130 | taps 1131 | tarot 1132 | tart 1133 | task 1134 | taste 1135 | tasty 1136 | taunt 1137 | thank 1138 | thaw 1139 | theft 1140 | theme 1141 | thigh 1142 | thing 1143 | think 1144 | thong 1145 | thorn 1146 | those 1147 | throb 1148 | thud 1149 | thumb 1150 | thump 1151 | thus 1152 | tiara 1153 | tidal 1154 | tidy 1155 | tiger 1156 | tile 1157 | tilt 1158 | tint 1159 | tiny 1160 | trace 1161 | track 1162 | trade 1163 | train 1164 | trait 1165 | trap 1166 | trash 1167 | tray 1168 | treat 1169 | tree 1170 | trek 1171 | trend 1172 | trial 1173 | tribe 1174 | trick 1175 | trio 1176 | trout 1177 | truce 1178 | truck 1179 | trump 1180 | trunk 1181 | try 1182 | tug 1183 | tulip 1184 | tummy 1185 | turf 1186 | tusk 1187 | tutor 1188 | tutu 1189 | tux 1190 | tweak 1191 | tweet 1192 | twice 1193 | twine 1194 | twins 1195 | twirl 1196 | twist 1197 | uncle 1198 | uncut 1199 | undo 1200 | unify 1201 | union 1202 | unit 1203 | untie 1204 | upon 1205 | upper 1206 | urban 1207 | used 1208 | user 1209 | usher 1210 | utter 1211 | value 1212 | vapor 1213 | vegan 1214 | venue 1215 | verse 1216 | vest 1217 | veto 1218 | vice 1219 | video 1220 | view 1221 | viral 1222 | virus 1223 | visa 1224 | visor 1225 | vixen 1226 | vocal 1227 | voice 1228 | void 1229 | volt 1230 | voter 1231 | vowel 1232 | wad 1233 | wafer 1234 | wager 1235 | wages 1236 | wagon 1237 | wake 1238 | walk 1239 | wand 1240 | wasp 1241 | watch 1242 | water 1243 | wavy 1244 | wheat 1245 | whiff 1246 | whole 1247 | whoop 1248 | wick 1249 | widen 1250 | widow 1251 | width 1252 | wife 1253 | wifi 1254 | wilt 1255 | wimp 1256 | wind 1257 | wing 1258 | wink 1259 | wipe 1260 | wired 1261 | wiry 1262 | wise 1263 | wish 1264 | wispy 1265 | wok 1266 | wolf 1267 | womb 1268 | wool 1269 | woozy 1270 | word 1271 | work 1272 | worry 1273 | wound 1274 | woven 1275 | wrath 1276 | wreck 1277 | wrist 1278 | xerox 1279 | yahoo 1280 | yam 1281 | yard 1282 | year 1283 | yeast 1284 | yelp 1285 | yield 1286 | yo-yo 1287 | yodel 1288 | yoga 1289 | yoyo 1290 | yummy 1291 | zebra 1292 | zero 1293 | zesty 1294 | zippy 1295 | zone 1296 | zoom --------------------------------------------------------------------------------