34
\$\begingroup\$

Introduction

Apparently, this question has been asked here and it unfortunately closed. I thought it was a good idea to try again with it, but done right.

XKCD looks at the how we are trained to use "hard to remember passwords", thinking it's secure, but instead, would take a computer 3 days to crack. On the flip side, remembering 4-5 words brings Kuan's Password Intropy up, and is easy to remember. Crazy how that works, huh?

Challenge

The job today is to create 5 passwords using words. 4 words per password and a minimum of 4 letters per word, but no maximum. Kuan's Password Intropy will need to be calculated for every password, but a forced minimum will not be set.

What is Kuan's Password Intropy?

Kuan's Password Intropy is a measurement of how unpredictable a password is, according to Kuan. There is a simple calculation: E = log2(R) * L. E being Kuan's Password Intropy, R being range of available characters and L for password length.

Range of available characters is self explanatory. It's the range of characters that a password can have, in this case is Upper and lower case. Since there is 26 characters in the alphabet, 26 x 2 = 52 characters in the whole range of the password.

Password Length is also self explanatory. It's the total length of the password after creation.

Constraints

  • No input.
  • A word cannot reappear in the same password.
  • No symbols or numbers allowed in a password.
  • 4 words per password, but a forced minimum of 4 letters per word.
  • No spaces between words.
  • You cannot generate the same password over and over again.
  • Each word has to be capitalized in a password.
  • Output has to be human-readable, must be spaced out. Must also include Kuan's Password Intropy of the password with it using Kuan's Password Intropy equation above.
  • Dictionary. You must use this, download it as a text file and integrate accordingly. This will be the list from which you grab words from. Your code should assume its available.
  • This is , shortest bytes win.

Output

TriedScarProgressPopulation 153.9
TryingPastOnesPutting 119.7
YearnGasesDeerGiven 108.3
DoubtFeetSomebodyCreature 142.5
LiquidSureDreamCatch 114.0
\$\endgroup\$
  • 16
    \$\begingroup\$ For the test cases, why is the password entropy varying? All 4 word passwords that are generated from the same dictionary should have the same entropy. \$\endgroup\$ – NonlinearFruit May 25 '17 at 16:21
  • 20
    \$\begingroup\$ Password entropy is dependent on the symbol set. If your password is N symbols from the set S, the password entropy is log2(|S|)*N. Here the size of the symbol set is the size of the dictionary ( |S|=4284 ) and the number of symbols is the number of words ( N=4 ), so the entropy for each password is 48.3. \$\endgroup\$ – NonlinearFruit May 25 '17 at 16:33
  • 48
    \$\begingroup\$ This definition of entropy is dangerously wrong! If each character is chosen uniformly at random from a set of size R, then indeed a length-L password has R^L possibilities, so the entropy is the log of that: log₂(R^L) = log₂(R)*L which is your formula. However, if passwords are chosen at random from a different set (e.g. you'll never have a password like 3t1ta#asd), then the entropy will be the logarithm of the number of possible passwords. If you always choose 4 words uniformly at random from a 4284-word dictionary, then there are 4284^4 passwords, each with entropy log₂(4284)*4 ≈ 48.26. \$\endgroup\$ – ShreevatsaR May 25 '17 at 21:12
  • 5
    \$\begingroup\$ For the record, this kind of passwords predates the XKCD comic. They're called "diceware" passwords. \$\endgroup\$ – user2428118 May 26 '17 at 12:43
  • 5
    \$\begingroup\$ Aside from the issue of words having less entropy than random characters, your question requires that the words be capitalised, meaning the case is fixed and cannot be counted for entropy. \$\endgroup\$ – Niet the Dark Absol May 26 '17 at 13:15

16 Answers 16

13
\$\begingroup\$

Python 2, 102 101 97 91 bytes

from random import*
exec"x=''.join(x.title()for x in sample(f,4));print(x,57*len(x)/10);"*5

Assumes the dictionary as a list named f.

Can be tested by saving the file as dict.txt and calling

f = open('dict.txt').readlines()
\$\endgroup\$
  • \$\begingroup\$ Python lists don't have shuffle method, and you can save two bytes in Python 2 by removing parenthesis around exec (exec is a keyword in Python 2). \$\endgroup\$ – Konrad Borowski May 25 '17 at 20:35
  • \$\begingroup\$ @xfix Yeah it should be shuffle(f);. \$\endgroup\$ – Jonathan Allan May 25 '17 at 20:41
  • \$\begingroup\$ Whoops, fixing that asap \$\endgroup\$ – Martmists May 25 '17 at 21:17
  • 4
    \$\begingroup\$ You could use my trick of noting that the rounding at 5.7 is fine to 1 decimal place so long as floating point errors are not introduced and save five bytes with 57*len(x)/10.. Save another byte by removing the parentheses making the print take a tuple. Here is a cut-down version: TIO \$\endgroup\$ – Jonathan Allan May 25 '17 at 21:27
  • \$\begingroup\$ Use sample(f,4) instead of shuffle. Also f can just be open('dict.txt').read().split('\n'), open('dict.txt').readlines(), or just open('dict.txt') (I know it's not golfed but still). \$\endgroup\$ – Alex Hall May 25 '17 at 22:32
10
\$\begingroup\$

PowerShell (3.0+), 77 bytes

1..5|%{($p=-join($d|random -c 4|%{culture|% te*|% tot* $_}));57*$p.Length/10}

Try it online!

Using Jonathan Allan's 57*len/10 trick.

$d contains the dictionary as an array of words. If you're playing at home and want to fill $d:

$d=-split(irm pastebin.com/raw/eMRSQ4u2)

Using a golfed version of (Get-Culture).TextInfo.ToTitleCase() to capitalize the first letter; I don't think there's a shorter way to do that in PowerShell.

The rest is pretty straightforward I think.

The TIO link has the whole dictionary; disable the cache and go nuts!

\$\endgroup\$
  • \$\begingroup\$ Can someone point me to a reference for " Jonathan Allan's 57*len/10 trick" ? \$\endgroup\$ – James Curran May 26 '17 at 18:06
  • \$\begingroup\$ @JamesCurran See the breakdown of his answer here, and also his comment on this answer. \$\endgroup\$ – briantist May 26 '17 at 18:08
  • \$\begingroup\$ This will not work in 2.0 correct. That should be noted in the title. I also think you need to read in $d as supposed to assuming it is present in the environment. (gc d)| random.. where the dictionary is a file called d in the same directory. \$\endgroup\$ – Matt May 26 '17 at 18:42
  • 1
    \$\begingroup\$ @Matt on SO I might go out of my way to make an answer work with v2 (or do 2 versions), but this is code golf man! The more arcane the better ;-p \$\endgroup\$ – briantist May 26 '17 at 19:00
  • 1
    \$\begingroup\$ I am just trying to save bytes in my answer titles. \$\endgroup\$ – Matt May 26 '17 at 19:01
7
\$\begingroup\$

Jelly, 22 bytes

Ẋḣ4ŒtFµL×57÷⁵⁸,K
çЀ5Y

A monadic link taking a list of list of characters, the parsed dictionary (as allowed in chat).

Try it online! (Click "Arguments" to hide the dictionary and reduce the need to scroll.)

How?

Since the dictionary only contains valid words (4characters or more, only [a-z]), there is no need to check this condition.

Since all the words in the dictionary have lengths in [4-8] the possible password lengths are in [16,32], and the possible entropies will never round differently to one decimal place than by replacing log(52,2) with 5.7. The only problem is that using a floating point value of 5.7 will give floating point rounding errors for lengths 18, 26, and 31. However multiplying by 57 and then dividing by 10 using ×57÷⁵ avoids this (while still being a byte shorter than printing the full floating point precision value using ×52l2¤).

çЀ5Y - Main link: list of list of characters (the parsed dictionary)
   5  - literal 5
 Ѐ   - map across the implicit range [1,2,3,4,5]:
ç     -   last link (1) as a dyad
    Y - join with newlines
      - implicit print

Ẋḣ4ŒtFµL×57÷⁵⁸,K - Link 1, get password and entropy: list of lists of characters, number
Ẋ                - shuffle the list of lists (shuffle all the words)
 ḣ4              - head to 4 (the first four words)
   Œt            - title case (make the first letter of each uppercase)
     F           - flatten into one list of characters
      µ          - monadic chain separation, call that p
       L         - length of p
         57      - 57
        ×        - multiply
            ⁵    - 10
           ÷     - divide -> entropy to 1 decimal place
             ⁸   - link's left argument, p
              ,  - pair -> [p, entropy]
               K - join with (a) space(s)
\$\endgroup\$
5
\$\begingroup\$

Ruby, 89 83 bytes

d.select!{|w|w[3]}
5.times{p w=d.sample(4).map(&:capitalize)*'',5.700439718*w.size}

Assumes that the passwords are stored in the variable d. You can add this line before the code:

d=$<.map(&:chomp)

and call the script for instance like this:

$ ruby generate_passwords.rb < dictionary_file.txt

Sample output:

"MarginStarvedOnusInsulted"
142.51099295
"KitchenMiseryLurkJoints"
131.110113514
"InducesNotablePitfallsPrecede"
165.312751822
"FarmersAbortFutileWrapper"
142.51099295
"RoutesBishopGlowFaithful"
136.81055323200002

KitchenMiseryLurkJoints... wow.


-6 bytes from Ajedi32

\$\endgroup\$
  • 1
    \$\begingroup\$ Might be able to save a few bytes by removing shuffle! and replacing pop with sample. \$\endgroup\$ – Ajedi32 May 25 '17 at 21:07
  • \$\begingroup\$ @Ajedi32 Oh, you're right! I actually thought about it, but I had misread this rule A word cannot reappear in the same password, thinking it meant no reuse of words across all passwords. Thanks :) \$\endgroup\$ – daniero May 25 '17 at 21:16
4
\$\begingroup\$

Mathematica, 178 bytes

t=1;l=Length;While[t<6,s=RandomChoice[Import["https://pastebin.com/raw/eMRSQ4u2"],4];c=Capitalize/@s;f=Flatten@Characters[c];Print[StringJoin[c]," ",Log[2,l@Union@f]*l@f//N];t++]

Try it online

copy and paste using ctrl-v and press shift+enter to run


Mathematica, 136 bytes

assuming that m is the dictionary the code is

m=ImportString[Import["C:\a.txt"]]

.

t=1;l=Length;While[t<6,s=RandomChoice[m,4];c=Capitalize/@s;f=Flatten@Characters[c];Print[StringJoin[c]," ",Log[2,l@Union@f]*l@f//N];t++]
\$\endgroup\$
  • \$\begingroup\$ "The job today is to create 5 passwords using words. " Need 5 instead of one. \$\endgroup\$ – KuanHulio May 25 '17 at 17:41
  • \$\begingroup\$ ok...5 passwords.. fixed.. \$\endgroup\$ – J42161217 May 25 '17 at 18:13
  • \$\begingroup\$ Why didn't you made the dictionary available local to shorten the code by avoiding the hyperlink text? \$\endgroup\$ – sergiol May 25 '17 at 20:49
  • \$\begingroup\$ in order to be easy for you to test it... \$\endgroup\$ – J42161217 May 25 '17 at 20:53
  • \$\begingroup\$ It's best to provide simple, ungolfed helper code to ease testing than it is to golf your submission less so that it is self-contained. Also, the dictionary is supposed to be variable without hijacking the local DNS server (or modifying the hosts file). \$\endgroup\$ – wizzwizz4 May 25 '17 at 21:59
4
\$\begingroup\$

Bash, 66 65 bytes

for w in `shuf -n4 -`;{((l+=${#w}));printf ${w^};};bc<<<$l*5.7004

Try it online!

Dictionary is recived by STDIN. Shuffles all words in dictionary and outputs first 4.

For each word, adds up its length in var l, and echoes the word capitalized. In the end calls bc to do the math.

Awk solution, 112 bytes, four passwords:

shuf -n16 -|xargs -n4|awk '{for(i=1;i<5;i++)printf toupper(substr($i,1,1))substr($i,2);print(length($0)-3)*5.7}'
\$\endgroup\$
3
\$\begingroup\$

(This is an adaptation of Martmists' answer, but I don't have the rep to comment)

Python, 88 86 bytes

g={*f}
exec('x="".join(g.pop().title()for i in "a"*4);print(x,len(x)*5.700439718);'*5)

By exploiting how set is nondeterministic, you can avoid having to import any randomness libraries.

\$\endgroup\$
  • \$\begingroup\$ This consistently produces the same output for me. If it does work on some implementation then you could save a few bytes by doing set(f).pop(). \$\endgroup\$ – Jonathan Allan May 25 '17 at 21:13
  • 1
    \$\begingroup\$ I don't think this is really valid. It's not deterministic, so it isn't guaranteed to produce the same password, but in practice it will rarely create different results. \$\endgroup\$ – DJMcMayhem May 25 '17 at 21:15
  • \$\begingroup\$ I suspected it might be implementation-dependent. I did it on a freshly-installed Windows release of Anaconda Python 3, and it worked. However set(f).pop() doesn't work, I tried it. It gives the same result each time. \$\endgroup\$ – dain May 25 '17 at 21:16
  • \$\begingroup\$ "In practice it will rarely create different results" -- it seems to for me, here's a sample: pastebin.com/raw/ZHiHgzxV \$\endgroup\$ – dain May 25 '17 at 21:22
  • \$\begingroup\$ @dain I am curious. Please provide information about your Python build. \$\endgroup\$ – wizzwizz4 May 25 '17 at 22:00
3
\$\begingroup\$

Japt, 30 bytes

5Ç[V=Uö4 ®g u +Zt1ìMm52 *Vl]¸

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Nice! But unfortunately it creates the same password 5 times, and it should be different every time.. \$\endgroup\$ – Iain Ward May 26 '17 at 8:55
  • \$\begingroup\$ This may be 30 characters, but at least in UTF-8, my system clocks it at 35 bytes. \$\endgroup\$ – a CVn May 26 '17 at 16:25
  • 1
    \$\begingroup\$ @MichaelKjörling Japt uses ISO 8859-1, not UTF-8. \$\endgroup\$ – Dennis May 26 '17 at 16:33
  • \$\begingroup\$ @Dennis Interesting. Thank you. \$\endgroup\$ – a CVn May 26 '17 at 20:48
3
\$\begingroup\$

JavaScript (ES6), 164 bytes

d=>{for(i=5;i--;)console.log(p="....".replace(/./g,_=>(w=d.splice(Math.random()*d.length|0,1)[0])[0].toUpperCase()+w.slice(1)),(Math.log2(52)*p.length).toFixed(1))}

Assumes the dictionary is passed to the function as an array.

Test Snippet

\$\endgroup\$
2
\$\begingroup\$

Mathematica, 71 Bytes

Assuming the dictionary is already loaded into an array called d.

Table[{#,Log[2,52]StringLength[#]}&[""<>Capitalize@d~RandomSample~4],5]

Explaination:

                                        Capitalize@d                    - Capitalize all the dictionary
                                                    ~RandomSample~4     - make an array with 4 values. By default values can not repeat.
                                    ""<>                                - Concatenate with empty string to turn array into single string.
      {#,Log[2,52]StringLength[#]}&[                               ]    - Put current string next to log(2,52) times length of current string
Table[                                                              ,5] - Repeat this 5 times.
\$\endgroup\$
  • \$\begingroup\$ What about the entropy number?! \$\endgroup\$ – Jonathan Allan May 26 '17 at 5:28
  • \$\begingroup\$ Oops missed that bit. Updated. \$\endgroup\$ – Ian Miller May 26 '17 at 13:01
2
\$\begingroup\$

PHP, 136 129 bytes

-7 bytes, thanks Jörg

for(shuffle($a);$i++<5;){for($s='',$c=0;$c<4;)strlen($w=$a[$k++])<4?:$s.=ucfirst($w).!++$c;echo$s.' '.log(52, 2)*strlen($s)."
";}

Try it online!

\$\endgroup\$
  • \$\begingroup\$ @JörgHülsermann That seems to work, thanks. \$\endgroup\$ – M.E May 29 '17 at 15:57
2
\$\begingroup\$

Python 3, 252 bytes

This is my first ever code golf challenge I've done! I know there are other Python answers on here (that are probably better than mine) but this looked fun, and so I wanted to try it anyways. Here's the golfed version:

import random, math
with open("d") as f: d=f.read()
l=d.split()
for a in range(5):
 u=[]
 p=""
 for b in range(4):
  w=random.choice([w for w in l if not w in u and len(w)>=4])
  u.append(w)
  w=w.title()
  p+=w
 print("%s %s"%(p,math.log2(52)*len(p)))

I would post a Try it Online! link, but that doesn't support multiple files. So here's a repl.it link: https://repl.it/InIl/0

Also, here's the ungolfed version:

import random
import math
with open("d") as f:
    dictionary = f.read() #this is the dictionary text file, simply saved as "d" as to use as few bytes as possible
words = dictionary.split() #here we turn that dictionary string into a list
for a in range(5): #here we iterate through 5 passwords
    used_words = []
    password = ""
    for b in range(4): #here we iterate through the 4 words in each password
        word = ""
        word = random.choice([word for word in words if not word in used_words and len(word) >= 4]) #Thanks to blackadder1337 from #python on freenode IRC for helping me with this.
        used_words.append(word)
        word = word.title()
        password = password + word
    print("%s %s"%(password, math.log2(52) * len(password)))

Like I said, this is my first time code gofling, so I'm sure this could be improved a lot.

\$\endgroup\$
  • \$\begingroup\$ Welcome to PPCG! \$\endgroup\$ – Taylor Scott Jun 15 '17 at 15:29
2
\$\begingroup\$

tcl, 137

Not a winner for sure, but I think it can be a golfed a little more.

time {set p "";time {set p [string totitle [lindex $d [expr int(rand()*[llength $d])]]]$p} 4;puts $p\ [expr 5.7004*[string length $p]]} 5

demo — The line 1 purpose is only to put the dictionary contents into the variable d

\$\endgroup\$
  • \$\begingroup\$ You could probably golf it down since the password requires 4 words instead of 5. \$\endgroup\$ – KuanHulio May 25 '17 at 23:06
  • \$\begingroup\$ And you asked for 5 passwords instead of 4. LOL! I mismatched the numbers! \$\endgroup\$ – sergiol May 25 '17 at 23:11
  • \$\begingroup\$ Hahaha! @sergiol \$\endgroup\$ – KuanHulio May 25 '17 at 23:12
  • \$\begingroup\$ Fixed! @KuanHulio \$\endgroup\$ – sergiol May 25 '17 at 23:13
  • \$\begingroup\$ That's better. Nice job. \$\endgroup\$ – KuanHulio May 25 '17 at 23:14
1
\$\begingroup\$

ColdFusion 216 bytes

p={};z=arrayLen(c);for(x=0;x<5;x++){pw="";r={};while(structCount(r)<4){n=RandRange(1,z);r.append({"#c[n]#":true});}for(w in structKeyList(r)){pw&=REReplace(w,"\b(\w)","\u\1","All");};p.append({"#pw#":57*len(pw)/10})}

This works in ColdFusion 11+ and Lucee 4.5+

To run it: https://trycf.com/gist/ff14e2b27d66f28ff69ab90365361b12/acf11?theme=monokai

The TryCF link has less-golf-ish but the same code.

I didn't really expect to have a competitive golfing answer; I just wanted to see what it would take to complete this challenge in ColdFusion. Especially since there isn't much CF in these answers. :-) After the setup, it was surprisingly shorter than I expected.

My first attempt was a little shorter until I remembered that the same word can't be used more than once. Even though it's highly unlikely that the randomizer would pick the same index more than once, I dump the indexes into the keys of a structure, which will prevent duplication. Then I use that list of keys to build my final password string. I also used the math trick to find entropy.

\$\endgroup\$
0
\$\begingroup\$

Vim, 87 keystrokes

qq:r!echo "$RANDOM"l<CR>D:w o|e w<CR>@"ev4bd:w|bp<CR>p0~wX~wX~wX~Y:.!wc -c<CR>A*5.7003<Esc>:.!bc<CR>PJq4@q

Assumes that the dictionary is in a file named w. Will always use 4 consecutive words

Explanation:

qq                       Start recording a macro named 'q'
:r!echo "$RANDOM"l<CR>   Append the result of the shell command `echo "$RANDOM"l`
D                        Delete what you just appended
:w o|                    Save the buffer to the file 'o' and ..
e w<CR>                  Open the file 'w'
@"                       Execute the text we deleted as a normal-mode command
                         This will move the cursor a random number of characters
                         to the right
e                        Go to the end of the next word
v4bd                     Delete 4 words backwards
:w|                      Save the file and ..
bp<CR>                   Open the last buffer (the 'o' file)
p                        Paste the 4 words we deleted
0                        Move the cursor to the beginning of the line
~wX~wX~wX~               Remove the spaces between the words and capitalize
Y                        Copy current line
:.!wc -c<CR>             Pipe the current line through 'wc -c'
A*5.7003<Esc>            Append "*5.7003" to the end of the line
:.!bc<CR>                Pipe the current line through 'bc'
P                        Paste the password above the current line
J                        Join with line bellow
q                        Stop recording the 'q' macro
4@q                      Run the 'q' macro 4 times
\$\endgroup\$
0
\$\begingroup\$

q/kdb+, 76 74 65 56 bytes

Solution:

{(x;5.70044*(#)x)}(,/)@[;0;upper]each -4?" "vs(*)(0:)`:w

Example:

q){(x;5.70044*(#)x)}(,/)@[;0;upper]each -4?" "vs(*)(0:)`:w
"RulingOverheadSaddensPriest"
153.9119

Explanation:

Read in the word list, break apart on " ", pick 4 random words from this list, uppercase first letter of each word, then join together. Feed this into a lambda function which returns the password and the calculated 'entropy':

                                                     `:w / the wordlist is a file called 'w'
                                                 (0:)    / read in the file list (\n separated list)
                                              (*)        / take first (and only) item in the list
                                         " "vs           / split this on " "
                                      -4?                / take 4 random items from this list, neg means 'dont put back'
                      @[; ;     ]                        / apply a function to variable at indices (variable is implicit)
                           upper                         / uppercase (the function being applied)
                         0                               / index 0, the first character
                                 each                    / each of the 4 random items
                  (,/)                                   / 'raze' (flatten lists)
{                }                                       / anonymous lambda function
 (x;            )                                        / a 2-item list, x is first item
            (#)x                                         / count x, return the length of the list
    5.70044*                                             / multiply by 5.70044

Notes:

I caved in and used 5.70044 instead of 2 xlog 52 xexp...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.