Bash on *nix (109)
while ! grep -Pq [A-Z].*[a-z].*[0-9].*[\\W_]<<<$a$a$a$a
do a=`tr -dc !-~</dev/urandom|head -c15`
done
echo $a
To work correctly, $a
must not be set to a valid but non-random password up front. If you want to include a=
and a line break up front, that's three more characters but it allows you to run the thing repeatedly. You can obviously also replace all newlines with ;
so you have a one-liner which you can execute as often as you whish.
Furthermore, you should have set LC_ALL=C
or not set any locale-specific environment variables (LANG
and LC_CTYPE
in particular), since the character ranges depend on collation order being equal to ascii order.
/dev/urandom
is the a source of random bytes. !-~
is the range of all permissible characters, as specified in the question. tr -dc
removes all characters not listed in its next argument. head
takes 15 of the remaining characters. grep
checks whether each of the required kinds does occur at least once. Its input consists of four copies of the candidate, so order of the symbols does not matter, hence all possible passwords stand a chance of getting selected. The -q
to grep suppresses output.
For reasons unknown, /dev/random
instead of /dev/urandom
takes ages. It seems like entropy got exhausted pretty quickly. If you cd
into /dev
, you can avoid some more bytes, but that feels a bit like cheating.
Python 2 (138)
import re,random
a=''
while not re.search('[A-Z].*[a-z].*[0-9].*[\W_]',a*4):
a=''.join(random.sample(map(chr,range(33,127))*15,15))
print a
To make the code readable I added a newline and indentation after the loop which is not neccessary and which I did not count.
This is essentially the same idea as in the bash version. The random source here is random.sample
, which will not repeat elements. To counter this fact, we use 15 copies of the list of permissible letters. That way, every combination can still occur, although those with repeated letters will occur less often. But I decide to consider this a feature, not a bug, since the question did not require equal probability for all permutations, only the possibility.
Python 3 (145)
import re,random
a=''
while not re.search('[A-Z].*[a-z].*[0-9].*[\W_]',a*4):
a=''.join(random.sample(list(map(chr,range(33,127)))*15,15))
print(a)
One newline and one indent again not counted. Apart from some Python-3-specific syntax overhead this is the same solution as for Python 2.
JavaScript (161)
a=[];for(i=33;i<127;)a.push(s=String.fromCharCode(i++));
while(!/[A-Z].*[a-z].*[0-9].*[\W_]/.test(s+s+s+s))
for(i=0,s="";i<15;++i)s+=a[Math.random()*94|0];alert(s)
I added the newlines for readability, but did not count them.
R (114)
s<-""
while(!grepl("[A-Z].*[a-z].*[0-9].*(\\W|_)",paste(rep(s,4),collapse="")))
s<-intToUtf8(sample(33:126,15,T))
s
Linebreak and indentation inside loop added but not counted. If you feel like it, you can again move this to a single ;
-separated line.
10You should also demand that all passwords which are allowed appear with the same probability (otherwise I can simply make a 30 characters long list with allowed characters, shuffle it, and give the first 15 ones) – Martin Thoma – 2014-01-05T22:36:34.107
@moose, agreed. I've added a new rule. – Hand-E-Food – 2014-01-05T22:53:03.070
22The IT guys should be fired, or at least better educated: If you do generate passwords randomly, then restricting the set of permissible passwords to those which include at least one character of each category in fact weakens the passwords, since it reduces the size of the permissible set. And our programs would be that much easier if we didn't have to check for that… OK, don't modify the contest after so many submissions have arrived; it's fine as a challenge. – MvG – 2014-01-06T05:15:53.640
Password Generator (mkpasswd) (Python recipe) – Grijesh Chauhan – 2014-01-06T05:45:02.503
10
@MvG Indeed:
– Jonathon Reinhart – 2014-01-06T05:46:22.987correcthorsebatterystaple
Well, technically those passwords are safe, but you can be 150% sure that they will be written down by everyone. This reminds me of https://codegolf.stackexchange.com/questions/6203/security-by-post-it, but it isn't a duplicate (because that one was about verifying).
– Konrad Borowski – 2014-01-06T08:21:08.387"It must be able to generate all permutations of all allowable characters." If you enforce that rule strictly, most answers will be invalid since the underlying PRNG is too bad. In many languages it's seeded by a 32 bit value which is obviously not enough to generate all possible values of such a complex password. – CodesInChaos – 2014-01-06T08:51:35.853
Just give everyone "Passw@rd1234" . They'll never suspect anyone else has the same password! – Carl Witthoft – 2014-01-06T13:08:53.143
@CodesInChaos for simplification we can probably assume true rng? – Cruncher – 2014-01-06T14:15:17.787
1You haven't really answered @moose by requiring that all passwords be generatable. They should appear with equal probability. – Ethan Bolker – 2014-01-06T14:53:57.290
@EthanBolker: Well, I guess that is now ok. It would be quite difficult to check if it really is with equal probability. And probably the only solutions that would come out of this would be "generate and test" ones. – Martin Thoma – 2014-01-06T14:59:29.283
@xfix I wouldn't even dare put the word "safe" near to such a broken password system. Firing the security guys should not be enough for such a mistake, you should also fire the guy who hired them. – o0'. – 2014-01-06T15:05:26.577
@CodesInChaos I don't see why a true RNG is required to create all possible passwords. To generate all possible passwords with equal probability, yes. But (unless I'm misunderstanding something) the PRNG limitation you describe does not preclude one from meeting the requirements specified above. Certainly, you could not generate all possible passwords from a single output of the PRNG. But if every character was individually generated according to separate PRNG outputs, as is done in my answer (and I'm sure some others), you need a very small fraction of that 32-bit PRNG space. – Iszi – 2014-12-07T09:56:35.930