【问题标题】:Generate password in python在python中生成密码
【发布时间】:2011-04-20 18:15:37
【问题描述】:

我想在 python 中生成一些字母数字密码。一些可能的方法是:

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

但我不喜欢两者,因为:

  • 方式 1 仅选择了唯一的字符,并且您无法生成长度 > len(chars) 的密码
  • 方式 2 我们有 i 变量未使用,我找不到避免这种情况的好方法

那么,还有其他好的选择吗?

附:因此,我们在这里对timeit 进行了 100000 次迭代的测试:

''.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

所以,获胜者是''.join(choice(chars) for _ in xrange(length))

【问题讨论】:

  • 第二个选项并没有什么问题。是不是慢了?你需要它更快吗?内存不足了吗?
  • 不要对第二个选项使用列表理解。使用生成器表达式。
  • 未使用的“我”打扰了我的思想和 IDE。 ;)
  • @SilengGhost: _ 的好把戏 如果你把它作为答案,我会接受它。
  • 仅供参考,草稿PEP 0506 -- Adding A Secrets Module To The Standard Library 刚刚发布,它专门链接到这个问题和答案。

标签: python passwords


【解决方案1】:

您应该使用secrets module 生成加密安全密码,该密码从 Python 3.6 开始可用。改编自文档:

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

有关食谱和最佳实践的更多信息,请参阅this section on recipes in the Python documentation。您还可以考虑添加string.punctuation,甚至只使用string.printable 以获得更广泛的字符集。

【讨论】:

  • 值得注意的是,这似乎需要 Python 3.6+
  • @user5359531 该注释已存在多年,但我删除了该注释,因为自 2020 年 9 月起不再支持 Python 3.5,我不想为未维护的 Python 版本保留答案。
  • 这是一个奇怪的立场,Python 的旧版本仍然存在于世界各地的系统上,无论 EOL 是什么。为了兼容,很多系统会安装多个版本的 Python。
  • @user5359531 从另一个角度看,是鲍里斯而不是我从答案中编辑了它,但我同意编辑。无论如何,答案的第一句话仍然指出自 Python 3.6 以来可用(只是不像以前那么突出)。
【解决方案2】:

对于那些使用加密 PRNG 的人来说:

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

    # original Python 2 (urandom returns str)
    # return "".join(chars[ord(c) % len(chars)] for c in urandom(length))

    # Python 3 (urandom returns bytes)
    return "".join(chars[c % len(chars)] for c in urandom(length))

请注意,对于均匀分布,chars 字符串长度应该是 128 的整数除数;否则,您将需要一种不同的方式从空间中统一选择。

【讨论】:

  • 您可以通过生成器表达式消除最后一行的列表:return "".join(chars[ord(c) % len(chars)] for c in urandom(length))
  • 这是最佳答案,但您应该明确答案,以便通过 Google 找到此答案的任何人都能理解为什么它是最佳答案。
  • crypto-PRNG 代表“密码安全的伪随机数生成器”。这个答案是最好的答案之一,因为它会生成安全密码。 random 模块中的默认伪随机数生成器不安全。
  • FWIW,经过多年的服务,此代码生成了一个淫秽密码。这是另一篇关于字母数字密码/ID 生成风险的帖子:softwareengineering.stackexchange.com/q/143405/32537
  • TypeError: ord() expected string of length 1, but int found 运行这段代码时出现这个错误,不知道为什么人们说这很好。
【解决方案3】:

警告由于严重的安全问题,应忽略此答案!

选项 #2 似乎很合理,但您可以添加一些改进:

''.join(choice(chars) for _ in range(length))          # in py2k use xrange

_ 是一个传统的“我不在乎里面有什么”变量。而且你不需要列表理解,生成器表达式对str.join 工作得很好。也不清楚“慢”是什么意思,如果它是唯一的正确方式。

【讨论】:

  • 您应该使用安全的随机数生成器来生成密码,否则您的密码很容易被泄露。默认的 python RNG 不是一个安全的。 “Python 使用 Mersenne Twister 作为核心生成器。...... Mersenne Twister......完全不适合加密目的。” -docs.python.org/library/random.html
  • 我想在此备份 Fasaxc。请查看@livibetter 的评论,使用PEP0506 中的secrets 模块。正如 livibetter 所说,这篇文章被认为是一个不好的例子。我知道这是一个我正在评论的老问题,但它仍然是热门搜索结果之一。我建议有人将接受的答案更改为 gerrit 的。
【解决方案4】:

使用内置 secrets (python 3.6+) 的两个配方

1。 secrets.token_urlsafe

这比接受的答案快很多。 (见下面的时间)

import secrets
password = secrets.token_urlsafe(32)

示例输出:

4EPn9Z7RE3l6jtCxEy7CPhia2EnYDEkE6N1O3-WnntU

token_urlsafe 的参数是字节数。平均而言,一个字节是 1.3 个字符(base64 编码)。

2。强制数字/大写字符等的数量

这是对机密文档的略微修改的副本。有了这个,您可以更精细地控制如何生成密码。当然,如果您需要生成很多个密码,这不是快速选项。

  • 强制长度为 20 个字符
  • 强制至少 4 个小写字符
  • 强制至少 4 个大写字符
  • 强制至少 4 位数字
  • 特殊字符可以添加到alphabet。在此示例中,仅添加了 -_
import string
import secrets
alphabet = string.ascii_letters + string.digits + '-_'
while True:
    password = ''.join(secrets.choice(alphabet) for i in range(20))
    if (sum(c.islower() for c in password) >=4
            and sum(c.isupper() for c in password) >=4
            and sum(c.isdigit() for c in password) >=4):
        break

示例输出:

HlxTm2fcFE54JA1I_Yp5

3。 “我不需要更细粒度的控制”

如果考虑速度,您也可以放弃 while 循环。在这种情况下,它实际上简化了 gerrit 的答案(但随后您会失去更细粒度的控制):

import string
import secrets
alphabet = string.ascii_letters + string.digits + '-_'
password = ''.join(secrets.choice(alphabet) for i in range(20))

速度对比

1. secrets.token_urlsafe

1.62 µs ± 96.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

2.强制数字/大写字符等的数量

107 µs ± 11.9 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

3. “我不需要更细粒度的控制”

77.2 µs ± 9.31 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

速度比较设置:Win10 上的 python 3.8.5 64 位,每个密码 43 个字符(token_urlsafe = 32 个字节)。

【讨论】:

    【解决方案5】:

    我认为这可以解决问题。 random.SystemRandom 使用与os.urandom 相同的底层加密随机函数,但它使用熟悉的random 接口。这个函数不会像 Ben 的回答那样受到奇怪的 128 字节的影响。

    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)
    

    【讨论】:

    • 为什么要将rng 设置为属性?
    • 这样您就不必每次都初始化random.SystemRandom()
    • 需要注意的是,这个解决方案(在当前版本的 python 中)并不比使用secrets.choice 的公认版本更安全。 secrets.choicerandom.SystemRandom().choice 的别名,尽管我会为较新的程序推荐 secrets 模块。 (这篇文章是在 python 2 还是一个东西并且秘密模块不是普遍可用的时候创建的)。
    【解决方案6】:

    我建议那些困在python

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

    与 Ben Mosher 的解决方案相比,这具有优势,即符号中的每个符号都有相同的发生变化,而使用模数稍微有利于字母表中的第一个符号。此建议中的符号字母表也更大。

    【讨论】:

    • 抛出类似invalid literal for int() with base 10: '\xba'的错误——所以需要添加一些容错
    • 嗯,我真的不明白我是怎么错过的。使用固定代码更新答案(添加 struct.unpack())并清理索引计算。
    【解决方案7】:

    我曾经在使用PyCryptodome 模块时做的是这样的:

    from Cryptodome.Random import get_random_bytes
    from base64 import b64encode, b64decode
    
    def get_password(lenght):
        return b64encode(get_random_bytes(lenght)).decode('utf-8')
    
    password = get_password(21)
    

    您决定希望密码在“get_random_bytes(lenght)”处保留多长时间。

    【讨论】:

      【解决方案8】:

      您可以将 random 和 chr 与 33 到 127 的符号一起使用:

      from random import randint
      
      symb_count = int(input('Enter count of symbols:'))
      passwd = []
      while len(passwd) < symb_count:
          passwd.append(chr(randint(33, 127)))
      print(''.join(passwd))
      

      【讨论】:

        【解决方案9】:

        这里在上面代码的第9行:

        return (choice(options) for _ in xrange(length))
        

        其实xrange没有定义,所以正确的编码是:

        return (choice(options) for _ in range(length))
        

        76 行也一样。

        【讨论】:

        • 这应该是对某个答案的评论吗?
        【解决方案10】:

        我根据自己的喜好写了script,主要是关于在转录和记忆时避免错误。 (例如:去掉有些歧义且没有重复的字符。)

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

        【讨论】:

          【解决方案11】:

          您可能想使用map 代替列表推导:

          ''.join(map(lambda x: random.choice(chars), range(length)))
          

          【讨论】:

            猜你喜欢
            • 2011-03-16
            • 2021-03-29
            • 2017-10-18
            • 1970-01-01
            • 1970-01-01
            • 2013-10-06
            • 1970-01-01
            • 2021-07-05
            • 1970-01-01
            相关资源
            最近更新 更多