可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'dl like to generate some alphanumeric passwords in python. Some possible ways are:
import string
from random import sample, choice
chars = string.letters + string.digits
length = 8
''.join(sample(chars,length)) # way 1
''.join([choice(chars) for i in range(length)]) # way 2
But I don't like both because:
- way 1 only unique chars selected and you can't generate passwords where length > len(chars)
- way 2 we have
i
variable unused and I can't find good way how to avoid that
So, any other good options?
P.S. So here we are with some testing with timeit
for 100000 iterations:
''.join(sample(chars,length)) # way 1; 2.5 seconds
''.join([choice(chars) for i in range(length)]) # way 2; 1.8 seconds (optimizer helps?)
''.join(choice(chars) for _ in range(length)) # way 3; 1.8 seconds
''.join(choice(chars) for _ in xrange(length)) # way 4; 1.73 seconds
''.join(map(lambda x: random.choice(chars), range(length))) # way 5; 2.27 seconds
So, the winner is ''.join(choice(chars) for _ in xrange(length))
.
回答1:
Python 3.6 onwards
You should use the secrets module to generate cryptographically safe passwords, which is available starting in Python 3.6. Adapted from the documentation:
import secrets
import string
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(20)) # for a 20-character password
回答2:
For the crypto-PRNG folks out there:
def generate_temp_password(length):
if not isinstance(length, int) or length < 8:
raise ValueError("temp password must have positive length")
chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
from os import urandom
return "".join(chars[ord(c) % len(chars)] for c in urandom(length))
Note that for an even distribution, the chars
string length ought to be an integral divisor of 128; otherwise, you'll need a different way to choose uniformly from the space.
回答3:
WARNING this answer should be ignored due to critical security issues!
Option #2 seems quite reasonable except you could add a couple of improvements:
''.join(choice(chars) for _ in range(length)) # in py2k use xrange
_
is a conventional "I don't care what is in there" variable. And you don't need list comprehension there, generator expression works just fine for str.join
. It is also not clear what "slow" means, if it is the only correct way.
回答4:
I think this'll do the trick. random.SystemRandom
uses the same underlying crypto random function as os.urandom
but it uses the familiar random
interface. This function won't be subject to the weird 128 byte thing as in Ben's answer.
import random
import string
def gen_random_string(char_set, length):
if not hasattr(gen_random_string, "rng"):
gen_random_string.rng = random.SystemRandom() # Create a static variable
return ''.join([ gen_random_string.rng.choice(char_set) for _ in xrange(length) ])
password_charset = string.ascii_letters + string.digits
gen_random_string(password_charset, 32)
回答5:
I suggest the following for those stuck on python <3.6:
import os, math, string, struct
def generate_password(pass_len):
symbols = string.printable.strip()
return ''.join([symbols[x * len(symbols) / 256] for x in struct.unpack('%dB' % (pass_len,), os.urandom(pass_len))])
This has the advantage over Ben Mosher's solution that the each symbol from symbols has an equal change of occurring whereas using modulus slightly favors the first symbols in the alpabet. The alphabet of symbols is also larger in this suggestion.
回答6:
I wrote a script with my preferences, which mostly are concerned with avoiding mistakes when transcribing and remembering. (For example: remove somewhat ambiguous and no repeated characters.)
import optparse
import os
import random
import sys
DEFAULT_CHARS = "234679ADEFGHJKLMNPRTUWabdefghijkmnpqrstuwy"
DEFAULT_LEN = 18
def choices(options, length, choice=random.choice):
return (choice(options) for _ in xrange(length))
def choices_non_repeated(options, length, choice=random.choice):
assert len(options) > 1
last = choice(options)
count = 0
while count < length:
yield last
count += 1
while True:
value = choice(options)
if value != last:
last = value
break
def main(args):
op = optparse.OptionParser(add_help_option=False)
op.add_option("--help", action="help",
help="show help message and exit")
op.add_option("-b", "--bare", action="store_true", default=False,
help="print passwords without trailing newline")
op.add_option("-c", "--chars", metavar="SET", nargs=1, default=DEFAULT_CHARS,
help="character set to use (default: %default)")
op.add_option("--repeat", action="store_true", default=False,
help="allow repetition")
op.add_option("-l", "--len", dest="max", nargs=1, type="int", default=DEFAULT_LEN,
help="max length (default: %default)")
op.add_option("--min", nargs=1, type="int", default=None,
help="min length (defaults to max)")
op.add_option("-n", "--count", nargs=1, type="int", default=None,
help="number of passwords to generate (default: %default)")
op.add_option("--cols", type="int", default=None,
help="number of columns to use")
opts, args = op.parse_args(args)
if args:
op.error("unknown arguments")
if os.isatty(sys.stdin.fileno()) and (
opts.count is None and opts.cols is None
and not opts.bare
):
opts.cols = 80 // (opts.max + 1)
opts.count = opts.cols * 25
else:
if opts.count is None:
opts.count = 1
if opts.cols is None:
opts.cols = 1
if opts.bare and opts.cols != 1:
op.error("bare output requires --cols=1")
if opts.min == None:
opts.min = opts.max
if any(x < 1 for x in [opts.cols, opts.count, opts.min, opts.max]):
op.error("values must be >= 1")
choices_func = choices_non_repeated
if opts.repeat:
choices_func = choices
elif len(set(opts.chars)) < 2:
op.error("must allow repetition or provide a longer character set")
return "op.error shouldn't return"
col = 0
for _ in xrange(opts.count):
length = random.randint(opts.min, opts.max)
password = "".join(choices_func(opts.chars, length))
sys.stdout.write(password)
if not opts.bare:
col += 1
if col == opts.cols:
sys.stdout.write("\n")
col = 0
else:
sys.stdout.write(" ")
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
回答7:
You may want to use map
instead of list comprehensions:
''.join(map(lambda x: random.choice(chars), range(length)))