I\'m interested in creating a very simple, high (cryptographic) quality random password generator. Is there a better way to do this?
import os, random, strin
There are some problems with your implementation:
random.seed = (os.urandom(1024))
This does not seed the random number generator; it replaces the seed
function with a bytestring. You need to call seed
, like, random.seed(…)
.
print ''.join(random.choice(chars) for i in range(length))
Python's default PRNG is a Mersenne Twister, which is not a cryptographically strong PRNG, so I'm wary of using it for cryptographic purposes. The random
module includes random.SystemRandom
, which on at least most *nix systems, should use a CSPRNG. However,
random.choice(chars)
…is implemented as…
def choice(self, seq):
"""Choose a random element from a non-empty sequence."""
return seq[int(self.random() * len(seq))] # raises IndexError if seq is empty
…in Python 2. Unfortunately, self.random
here is a C function, so this gets hard to see; the code smell here is that this code almost certainly doesn't choose uniformly. The code has completely changed in Python 3, and does a much better job of ensuring uniformity. The Python 3 docs for randrange
note,
Changed in version 3.2:
randrange()
is more sophisticated about producing equally distributed values. Formerly it used a style likeint(random()*n)
which could produce slightly uneven distributions.
randrange
and choice
both call the same method (_randbelow
) under the hood.
In Python 3, choice
is fine; in Python 2, it only comes close to a uniform distribution, but does not guarantee it. Since this is crypto, I lean on the "take no chances" side of the fence, and would like to have that guarantee.
A little bit off topic, but I made this, using also TKinter. Hope it can helps:
import os, random, string
from tkinter import *
def createPwd():
try:
length = int(e1.get())
except ValueError:
return
chars = string.ascii_letters + string.digits + '!@#$%^&*()?\/'
random.seed = (os.urandom(1024))
e2.config(state=NORMAL)
e2.delete(0,'end')
e2.insert(0,''.join(random.choice(chars) for i in range(length)))
e2.config(state="readonly")
mainWindow = Tk()
mainWindow.title('Password generator')
mainWindow.resizable(0,0)
f0 = Frame(mainWindow)
f0.pack(side=TOP,pady=5,padx=5,fill=X,expand=1)
Label(f0,text="Length: ",anchor=E).grid(row=0,column=0,sticky=E)
e1 = Entry(f0)
e1.insert(0,'12')
e1.grid(row=0,column=1)
btn = Button(f0,text="Generate")
btn['command'] = lambda: createPwd()
btn.grid(row=0,column=2,rowspan=1,padx=10,ipadx=10)
Label(f0,text="Generated password: ",anchor=E).grid(row=1,column=0,sticky=E)
e2 = Entry(f0)
e2.grid(row=1,column=1)
createPwd()
#starting main window
mainWindow.mainloop()
That way works. It is perfectly fine. If you had additional rules, such as excluding dictionary words, then you may want to include those filters as well, but the likelihood of randomly generating a dictionary word with that setup is extremely small.