views:

111

answers:

5

Hi Folks,

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))

Thanks all

A: 

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:]))
Roger Pate
+2  A: 

I think you worry too much.

  • Way 1 (unique characters): Limiting a password of, say, 10 characters from a palette of 36 to using only unique characters barely has any effect on the available password space. (Proof left as an exercise for the reader, but intuitively, if duplicates were allowed, the majority of passwords wouldn't have them.)

  • Way 2: (length limit): Will you ever need passwords anywhere near 36 characters long?

  • Way 2: Slow? How many thousands of passwords per second do you need to generate?

RichieHindle
I think unique character is a real issue for the 1st option.
SilentGhost
way 1: All chars are unique, that's not good. Let's say I need to generate some set of characters from 'chars' collectionway 2: Don't really care that it's slow. Unused 'i' flags in my IDE as unused ;)
HardQuestions
A: 

Do you use a *NIX operating system? See if your variant supports mkpassword. You can perhaps write a thin wrapper over it to do what you want. Or even invoke it using shell.

There is also this Python script (from sometime ago; I have't used it myself) that implements the same features.

Manoj Govindan
+1  A: 

You may want to use map instead of list comprehensions:

''.join(map(lambda x: random.choice(chars), range(length)))
Sergei Stolyarov
this is slow indeed.
SilentGhost
@SilentGhost: 8 microseconds on my PC.
RichieHindle
@Richie: compared to how many for generator expression?
SilentGhost
@SilentGhost: My point is that the ability to generate a hundred million passwords in one second is not "slow indeed". It's fast enough - time to move on to solving a real problem. :-)
RichieHindle
10% faster than way 1; 20% slower than way 2
HardQuestions
I timed it on my POS laptop and it's 34 microseconds for the comprehension and 33 for map for `length = 8`. Kick it up to `length = 1000` and the generator expression pulls ahead 3.16 msecs vs 3.56. at 1000000, it's still the expression winning (by a slightly wider margin) at 3.25 secs to 3.79 secs.
aaronasterling
@Sergei Good solution, i like it, but it's not fastest.
HardQuestions
+3  A: 

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.

SilentGhost
With xrange in python 2.6 it's 5% faster than with range
HardQuestions