Высококачественный, простой генератор случайных паролей

Я заинтересован в создании очень простого, высокого (криптографического) генератора случайных паролей качества. Есть ли лучший способ сделать это?

import os, random, string

length = 13
chars = string.ascii_letters + string.digits + '[email protected]#$%^&*()'
random.seed = (os.urandom(1024))

print ''.join(random.choice(chars) for i in range(length))

Ответ 1

Трудная вещь с паролями заключается в том, чтобы сделать их достаточно сильными и все еще помнить о них. Если пароль не предназначен для запоминания человеком, то это не пароль.

Вы используете Python os.urandom(): это хорошо. Для любой практической цели (даже криптографии) вывод os.urandom() неотличим от истинного alea. Затем вы используете его как семя в random, что менее полезно: это некриптографический PRNG, и его вывод может содержать некоторую структуру, которая не будет регистрироваться в статистическом инструменте измерения, но может быть использована интеллектуальным злоумышленником, Вы должны работать с os.urandom(). Чтобы сделать вещи простыми: выберите алфавит длиной 64, например. буквы (в верхнем и нижнем регистре), цифры и два дополнительных символа пунктуации (например, "+" и "/" ). Затем для каждого символа пароля получите один байт из os.urandom(), уменьшите значение по модулю 64 (это непредвзято, потому что 64 делит 256) и использовать результат как индекс в массиве chars.

С алфавитом длиной 64, вы получите 6 бит энтропии на символ (потому что 2 6= 64). Таким образом, с 13 символами вы получаете 78 бит энтропии. Во всех случаях это не так сильно, но уже очень сильное (его можно победить с бюджетом, который будет рассчитываться месяцами и миллиардами долларов, а не миллионами).

Ответ 2

XKCD имеет отличное объяснение того, почему то, что вы думаете являются сильными паролями , не являются.

http://xkcd.com/936/

Тем, кто понимает теорию и безопасность информации и находится в беспричинный аргумент с кем-то, кто этого не делает (возможно, смешанный случай), я искренне извиняюсь. - Рэндалл Мунро

И если вы не понимаете математику за тем, что объясняет эта иллюстрация, не пытайтесь писать что-либо, что должно быть криптографически безопасным, потому что этого не будет. Просто поместите мышь и откройте клавиатуру.

Ответ 3

Всего два дня назад, Kragen Javier Sitaker опубликовал программу, чтобы сделать это на http://lists.canonical.org/pipermail/kragen-hacks/2011-September/000527.html (теперь ушел - попробуйте https://github.com/jesterpm/bin/blob/master/mkpasswd)

Создайте случайный, запоминающийся пароль: http://xkcd.com/936/

Пример выполнения:

kragen at inensorable: ~/devel/inexorable-misc $./mkpass.py 5 12 Ваш пароль "узнал поврежденные сохраненные жилые этапы". Это эквивалентно 60-битовому ключу.

Этот пароль потребует 2.5e + 03 CPU-лет, чтобы взломать мой недорогой Celeron E1200 с 2008 года, предполагая атаку в автономном режиме на хэш-код MS-Cache, который является самым худшим алгоритмом хэширования паролей в общем использовании, немного хуже, чем даже простой MD5.

Самым распространенным алгоритмом хеширования паролей в эти дни является итерация MD5 FreeBSDs; взломать такой хэш займет 5.2e + 06 CPU-years.

Но современный GPU может взломать примерно в 250 раз быстрее, так что тот же самый итерационный MD5 упадет в 2e + 04 GPU-years.

Этот графический процессор стоит около 1,45 доллара США в день для запуска в 2011 году, поэтому взломать пароль будет стоить около 3 долларов США + 09.

Я начал использовать пароль, сгенерированный таким образом вместо 9-печатного ASCII-символьного случайного пароля, который одинаково силен. Утверждение Манро о том, что эти пароли намного легче запомнить, является правильным. Тем не менее, все еще существует проблема: потому что количество символов в энтропии на один символ меньше (около 1.7 вместо 6.6), в пароле много избыточности, и поэтому атаки, такие как атака с временным каналом ssh (Song, Wagner и Tian Herbivore, о которых я узнал из Брэма Коэна в кафе Bagdad в ранние часы утром, много лет назад), и атаки с записью звука на клавиатуре имеют гораздо больше шансов собрать достаточную информацию, чтобы сделать пароль доступным.

Моя контрмера для атаки Herbivore, которая хорошо работает с 9-символьным паролем, но крайне раздражает мой новый пароль, заключается в том, чтобы ввести пароль с задержкой в ​​полсекунды между символами, так что канал синхронизации не слишком много информация о фактических используемых символах. Кроме того, более низкая длина 9-символьного пароля по своей сути дает подход Herbivore гораздо меньше информации, чтобы пережевывать.

Другие возможные контрмеры включают использование режима оболочки Emacs, который запрашивает вас локально для пароля, когда он распознает приглашение пароля, а затем сразу же отправляет весь пароль и копирует и вставляет пароль из другого места.

Как и следовало ожидать, этот пароль также занимает немного больше времени: примерно 6 секунд вместо 3 секунд.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import random, itertools, os, sys

def main(argv):
    try:
        nwords = int(argv[1])
    except IndexError:
        return usage(argv[0])

    try:
        nbits = int(argv[2])
    except IndexError:
        nbits = 11

    filename = os.path.join(os.environ['HOME'], 'devel', 'wordlist')
    wordlist = read_file(filename, nbits)
    if len(wordlist) != 2**nbits:
        sys.stderr.write("%r contains only %d words, not %d.\n" %
                         (filename, len(wordlist), 2**nbits))
        return 2

    display_password(generate_password(nwords, wordlist), nwords, nbits)
    return 0

def usage(argv0):
    p = sys.stderr.write
    p("Usage: %s nwords [nbits]\n" % argv0)
    p("Generates a password of nwords words, each with nbits bits\n")
    p("of entropy, choosing words from the first entries in\n")
    p("$HOME/devel/wordlist, which should be in the same format as\n")
    p("<http://canonical.org/~kragen/sw/wordlist>, which is a text file\n")
    p("with one word per line, preceded by its frequency, most frequent\n")
    p("words first.\n")
    p("\nRecommended:\n")
    p("    %s 5 12\n" % argv0)
    p("    %s 6\n" % argv0)
    return 1

def read_file(filename, nbits):
    return [line.split()[1] for line in
            itertools.islice(open(filename), 2**nbits)]

def generate_password(nwords, wordlist):
    choice = random.SystemRandom().choice
    return ' '.join(choice(wordlist) for ii in range(nwords))

def display_password(password, nwords, nbits):
    print 'Your password is "%s".' % password
    entropy = nwords * nbits
    print "That equivalent to a %d-bit key." % entropy
    print

    # My Celeron E1200
    # (<http://ark.intel.com/products/34440/Intel-Celeron-Processor-E1200-(512K-Cache-1_60-GHz-800-MHz-FSB)>)
    # was released on January 20, 2008.  Running it in 32-bit mode,
    # john --test (<http://www.openwall.com/john/>) reports that it
    # can do 7303000 MD5 operations per second, but I’m pretty sure
    # that’s a single-core number (I don’t think John is
    # multithreaded) on a dual-core processor.
    t = years(entropy, 7303000 * 2)
    print "That password would take %.2g CPU-years to crack" % t
    print "on my inexpensive Celeron E1200 from 2008,"
    print "assuming an offline attack on a MS-Cache hash,"
    print "which is the worst password hashing algorithm in common use,"
    print "slightly worse than even simple MD5."
    print

    t = years(entropy, 3539 * 2)
    print "The most common password-hashing algorithm these days is FreeBSD’s"
    print "iterated MD5; cracking such a hash would take %.2g CPU-years." % t
    print

    # (As it happens, my own machines use Drepper’s SHA-2-based
    # hashing algorithm that was developed to replace the one
    # mentioned above; I am assuming that it’s at least as slow as the
    # MD5-crypt.)

    # <https://en.bitcoin.it/wiki/Mining_hardware_comparison> says a
    # Core 2 Duo U7600 can do 1.1 Mhash/s (of Bitcoin) at a 1.2GHz
    # clock with one thread.  The Celeron in my machine that I
    # benchmarked is basically a Core 2 Duo with a smaller cache, so
    # I’m going to assume that it could probably do about 1.5Mhash/s.
    # All common password-hashing algorithms (the ones mentioned
    # above, the others implemented in John, and bcrypt, but not
    # scrypt) use very little memory and, I believe, should scale on
    # GPUs comparably to the SHA-256 used in Bitcoin.

    # The same mining-hardware comparison says a Radeon 5870 card can
    # do 393.46 Mhash/s for US$350.

    print "But a modern GPU can crack about 250 times as fast,"
    print "so that same iterated MD5 would fall in %.1g GPU-years." % (t / 250)
    print

    # Suppose we depreciate the video card by Moore’s law,
    # i.e. halving in value every 18 months.  That a loss of about
    # 0.13% in value every day; at US$350, that’s about 44¢ per day,
    # or US$160 per GPU-year.  If someone wanted your password as
    # quickly as possible, they could distribute the cracking job
    # across a network of millions of these cards.  The cards
    # additionally use about 200 watts of power, which at 16¢/kWh
    # works out to 77¢ per day.  If we assume an additional 20%
    # overhead, that’s US$1.45/day or US$529/GPU-year.
    cost_per_day = 1.45
    cost_per_crack = cost_per_day * 365 * t
    print "That GPU costs about US$%.2f per day to run in 2011," % cost_per_day
    print "so cracking the password would cost about US$%.1g." % cost_per_crack

def years(entropy, crypts_per_second):
    return float(2**entropy) / crypts_per_second / 86400 / 365.2422

if __name__ == '__main__':
    sys.exit(main(sys.argv))

Ответ 4

реализация решения @Thomas Pornin

import M2Crypto
import string

def random_password(length=10):
    chars = string.ascii_uppercase + string.digits + string.ascii_lowercase
    password = ''
    for i in range(length):
        password += chars[ord(M2Crypto.m2.rand_bytes(1)) % len(chars)]
    return password

Ответ 5

Другая реализация метода XKCD:

#!/usr/bin/env python
import random
import re

# apt-get install wbritish
def randomWords(num, dictionary="/usr/share/dict/british-english"):
  r = random.SystemRandom() # i.e. preferably not pseudo-random
  f = open(dictionary, "r")
  count = 0
  chosen = []
  for i in range(num):
    chosen.append("")
  prog = re.compile("^[a-z]{5,9}$") # reasonable length, no proper nouns
  if(f):
    for word in f:
      if(prog.match(word)):
        for i in range(num): # generate all words in one pass thru file
          if(r.randint(0,count) == 0): 
            chosen[i] = word.strip()
        count += 1
  return(chosen)

def genPassword(num=4):
  return(" ".join(randomWords(num)))

if(__name__ == "__main__"):
  print genPassword()

Пример вывода:

$ ./randompassword.py
affluent afford scarlets twines
$ ./randompassword.py
speedboat ellipse further staffer

Ответ 6

Я знаю, что этот вопрос был опубликован еще в 2011 году, но для тех, кто придет к нему сейчас, в 2014 году и далее, я хочу сказать одно: ПРОТИВОСТОЯТЬ НАСТОЯЩЕМУ, ЧТОБЫ ПОВЫСИТЬ РЕМОНТ КОЛЕСА.

В этих ситуациях вам лучше всего искать программное обеспечение с открытым исходным кодом, например, ограничить свой поиск результатами github. Безусловно лучшее, что я нашел:

https://github.com/redacted/XKCD-password-generator

Ответ 7

Вы не можете доверять генератору псевдослучайных чисел python при генерации пароля. Это не обязательно криптографически случайное. Вы высеваете генератор псевдослучайных чисел из os.urandom, который является хорошим началом. Но после этого вы зависите от генератора питона.

Лучшим выбором будет random.SystemRandom() класс, который принимает случайные числа из того же источника, что и urandom. Согласно документации на python, которая должна быть достаточно хороша для использования в криптографии. Класс SystemRandom дает вам все, что делает основной случайный класс, но вам не нужно беспокоиться о псевдослучайности.

Пример кода с использованием random.SystemRandom(для Python 2.6):

import random, string
length = 13
chars = string.ascii_letters + string.digits + '[email protected]#$%^&*()'

rnd = random.SystemRandom()
print ''.join(rnd.choice(chars) for i in range(length))

Примечание. Ваш пробег может отличаться. В документации на Python указано, что random.SystemRandom зависит от операционной системы.

Ответ 8

Учитывая ваш комментарий,

Мне просто нужно создать пароли, которые более безопасны, чем те, которые я придумал в своей голове.

Кажется, вы хотите использовать свою программу для генерации паролей, а не просто писать ее как упражнение. Предпочтительно использовать существующую реализацию, потому что, если вы допустили ошибку, выход может быть скомпрометирован. Читайте о атаках генератора случайных чисел; в частности, известная ошибка RNG в Debian подвергла действию закрытые ключи SSL.

Поэтому вместо этого рассмотрите возможность использования pwgen. Он предоставляет несколько опций, которые вы должны выбрать в зависимости от того, на что вы собираетесь использовать пароли.

Ответ 9

Implenting @Thomas Pornin решение: (не могу комментировать @Yossi неточный ответ)

import string, os
chars = string.letters + string.digits + '+/'
assert 256 % len(chars) == 0  # non-biased later modulo
PWD_LEN = 16
print ''.join(chars[ord(c) % len(chars)] for c in os.urandom(PWD_LEN))

Ответ 10

Это легко:)

def codegenerator():
    alphabet = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    pw_length = 8
    mypw = ""

    for i in range(pw_length):
        next_index = random.randrange(len(alphabet))
        mypw = mypw + alphabet[next_index]
    return mypw

а do:

print codegenerator()

Спасибо http://xkcd.com/936/

Ответ 11

import random


r = random.SystemRandom()


def generate_password(words, top=2000, k=4, numbers=None, characters=None,
                      first_upper=True):
    """Return a random password based on a sorted word list."""
    elements = r.sample(words[:top], k)

    if numbers:
        elements.insert(r.randint(1, len(elements)), r.choice(numbers))
    if characters:
        elements.insert(r.randint(1, len(elements)), r.choice(characters))
    if first_upper:
        elements[0] = elements[0].title()

    return ''.join(elements)


if __name__ == '__main__':
    with open('./google-10000-english-usa.txt') as f:
        words = [w.strip() for w in f]
    print(generate_password(words, numbers='0123456789', characters='[email protected]#$%'))
  • Создает пароли, которые вы можете запомнить
  • Использует os.urandom()
  • Управляет правилами реального мира, такими как добавление чисел, прописных букв и символов.

Конечно, его можно улучшить, но это то, что я использую.

Ответ 12

Таким образом работает. Это прекрасно. Если у вас есть дополнительные правила, такие как исключение слов словаря, то вы можете также включить эти фильтры, но вероятность случайного создания словарного слова с этой установкой крайне мала.

Ответ 13

В вашей реализации есть некоторые проблемы:

random.seed = (os.urandom(1024))

Это не засевает генератор случайных чисел; он заменяет функцию seed байтом. Вам нужно позвонить seed, например, random.seed(…).

print ''.join(random.choice(chars) for i in range(length))

PRNG по умолчанию Python - это Mersenne Twister, который не является криптографически сильным PRNG, поэтому я опасаюсь использовать его для криптографических целей. Модуль random включает random.SystemRandom, который по крайней мере для большинства систем * nix должен использовать CSPRNG. Тем не менее,

random.choice(chars)

... реализуется как...

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

... в Python 2. К сожалению, self.random здесь есть функция C, поэтому это становится трудно увидеть; запах кода здесь заключается в том, что этот код почти наверняка не выбирает равномерно. Код полностью изменился в Python 3 и делает гораздо лучшую работу по обеспечению единообразия. Документы Python 3 для randrange отмечают,

Изменено в версии 3.2: randrange() более сложна в создании одинаково распределенных значений. Раньше он использовал стиль типа int(random()*n), который мог создавать слегка неравномерные распределения.

randrange и choice оба вызова того же метода (_randbelow) под капотом.

В Python 3, choice отлично; в Python 2 он приближается только к равномерному распределению, но не гарантирует его. Поскольку это криптография, я опираюсь на сторону "не рискую" забор и хотел бы получить эту гарантию.

Ответ 14

Построил мой собственный ответ CLI в теме (полный исходный код по следующему URL-адресу):

http://0netenv.blogspot.com/2016/08/password-generator-with-argparse.html

Написал генератор паролей, используя argparse. Надеюсь, что это поможет кому-то (построив генератор паролей или используя argparse)!

В любом случае, было весело строить!

$ ./pwgen.py -h
usage: pwgen.py [-h] [-c COUNT] [-a] [-l] [-n] [-s] [-u] [-p]

 Create a random password
 Special characters, numbers, UPPERCASE -"Oscar",
 and lowercase -"lima" to avoid confusion.
 Default options (no arguments): -c 16 -a
                Enjoy! [email protected]

optional arguments:
  -h, --help            show this help message and exit
  -c COUNT, --count COUNT
                        password length
  -a, --all             same as -l -n -s -u
  -l, --lower           include lowercase characters
  -n, --number          include 0-9
  -s, --special         include special characters
  -u, --upper           include uppercase characters
  -p, --license         print license and exit

Здесь код:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

license = """
#  pwgen -- the pseudo-random password generator 
#
#  This software is distributed under the MIT license.
#    
#  The MIT License (MIT)
#
#  Copyright (c) 2016 0NetEnv [email protected]
#  Permission is hereby granted, free of charge, to any 
#  person obtaining a copy of this software and associated 
#  documentation files (the "Software"), to deal in the 
#  Software without restriction, including without 
#  limitation the rights to use, copy, modify, merge, 
#  publish, distribute, sublicense, and/or sell copies 
#  of the Software, and to permit persons to whom the 
#  Software is furnished to do so, subject to the following 
#  conditions:
#
#  The above copyright notice and this permission notice 
#  shall be included in all copies or substantial portions 
#  of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 
#  ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
#  TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
#  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 
#  SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
#  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
#  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
#  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
#  DEALINGS IN THE SOFTWARE.
#  
#  NOTE:
#  This software was tested on Slackware 14.2, Raspbian, & 
#  Mac OS X 10.11
#
"""

import string
import random
import sys
# first time using argparse library
import argparse
# wanted to change the formatting of the help menu a little bit, so used RawTextHelpFormatter directly
from argparse import RawTextHelpFormatter

typo = ''
c = 16
counter = 0
line = '-' * 40

# CREATE FUNCTION for PWGEN
def pwgen(z, t):
    # EMPTY SET OF CHARACTERS
    charsset = ''
    # UPPERCASE -"O"
    U = 'ABCDEFGHIJKLMNPQRSTUVWXYZ'
    # lowercase -"l"
    L = 'abcdefghijkmnopqrstuvwxyz'
    N = '0123456789'
    S = '[email protected]#$%^&*?<>'

    # make sure we're using an integer, not a char/string
    z = int(z)
    for type in t:
        if 'u' in t:
            charsset = charsset + U
        if 'l' in t:
            charsset = charsset + L
        if 'n' in t:
            charsset = charsset + N
        if 's' in t:
            charsset = charsset + S
        if 'a' == t:
            charsset = charsset + U + L + N + S

    return ''.join(random.choice(charsset) for _ in range(0, int(z)))

# GET ARGUMENTS using ARGPARSE
parser = argparse.ArgumentParser(description='\n Create a random password\n\
 Special characters, numbers, UPPERCASE -"Oscar",\n\
 and lowercase -"lima" to avoid confusion.\n\
 Default options (no arguments): -c 16 -a\n\
 \t\tEnjoy! [email protected]', formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-c", "--count", dest="count", action="store", help="password length")
parser.add_argument("-a", "--all", help="same as -l -n -s -u", action="store_true")
parser.add_argument("-l", "--lower", help="include lowercase characters", action="store_true")
parser.add_argument("-n", "--number", help="include 0-9", action="store_true")
parser.add_argument("-s", "--special", help="include special characters", action="store_true")
parser.add_argument("-u", "--upper", help="include uppercase characters", action="store_true")
parser.add_argument("-p", "--license", help="print license and exit", action="store_true")

# COLLECT ARGPARSE RESULTS
results = args = parser.parse_args()

# CHECK RESULTS
# Check that a length was given.
# If not, gripe and exit.
if args.count == '0':
    print ("Input error:\nCannot create a zero length password.\nExiting")
    exit (0)
# check character results and add to counter if 
# selection is made.
if args.lower:
    typo = typo + 'l'
    counter = counter + 1
    #print "lower"
if args.number:
    typo = typo + 'n'
    counter = counter + 1
    #print "number"
if args.special:
    typo = typo + 's'
    counter = counter + 1
    #print "special"
if args.upper:
    typo = typo + 'u'
    counter = counter + 1
    #print "upper"
if args.all:
    typo = 'a'
    counter = counter + 1
    #print "all"
if args.license:
    print (license)
    exit (1)

# CHECK COUNTER
# Check our counter and see if we used any command line 
# options. We don't want to error out.
# try it gracefully. If no arguments are given, 
# use defaults and tell the user.
# args.count comes from argparse and by default requires
# an input to '-c'. We want to get around that for the 
# sake of convenience.
# Without further adieu, here our if statement:
if args.count:
    if counter == 0:
        typo = 'a'
        print ("defaulting to '--all'")
    print (line)
    print (pwgen(results.count,typo))
else:
    if counter == 0:
        typo = 'a'
        print ("defaulting to '--count 16 --all'")
    print (line)
    print (pwgen(c,typo))
print (line)
#print typo

Ответ 15

Я люблю лингвистику, в моем подходе я создаю запоминающиеся псевдо-слова с высоким уровнем энтропии чередующимися согласными и гласными.

  • Не подвержен атакам по словарю
  • Произносимый и, следовательно, хороший шанс быть запоминающимся
  • Короткие пароли с приличной силой
  • Необязательный параметр для добавления случайной цифры для совместимости (менее запоминающийся, но соответствует приложениям, созданным с учетом концепции безопасности старого пароля, например, требующей цифры)

Код Python:

import random
import string


def make_pseudo_word(syllables=5, add_number=False):
    """Create decent memorable passwords.

    Alternate random consonants & vowels
    """
    rnd = random.SystemRandom()
    s = string.ascii_lowercase
    vowels = 'aeiou'
    consonants = ''.join([x for x in s if x not in vowels])
    pwd = ''.join([rnd.choice(consonants) + rnd.choice(vowels)
               for x in range(syllables)]).title()
    if add_number:
        pwd += str(rnd.choice(range(10)))
    return pwd


>>> make_pseudo_word(syllables=5)
'Bidedatuci'
>>> make_pseudo_word(syllables=5)
'Fobumehura'
>>> make_pseudo_word(syllables=5)
'Seganiwasi'
>>> make_pseudo_word(syllables=4)
'Dokibiqa'
>>> make_pseudo_word(syllables=4)
'Lapoxuho'
>>> make_pseudo_word(syllables=4)
'Qodepira'
>>> make_pseudo_word(syllables=3)
'Minavo'
>>> make_pseudo_word(syllables=3)
'Fiqone'
>>> make_pseudo_word(syllables=3)
'Wiwohi'

Минусы:

  • для носителей латинского и германского языков и тех, кто знаком с английским
  • нужно использовать гласные и согласные языка, преобладающего с пользователями приложения или фокус-группой, и настраивать

Ответ 16

Вот еще одна реализация (python 2; для ее работы в 3) потребуется немного мелких перезаписей, которая намного быстрее, чем OJW, которая, кажется, перебирает словарь для каждого слова, несмотря на комментарий/импликацию об обратном. Сроки OJW script на моей машине с 80 000 IOP SSD:

real    0m3.264s
user    0m1.768s
sys     0m1.444s

Следующий script загружает весь словарь в список, затем выбирает слова на основе случайного выбора значения индекса, используя регулярное выражение OJW для фильтрации.

Это также генерирует 10 наборов парольной фразы, позволяет передавать параметры командной строки для корректировки количества слов и добавляет количество и заполнение символов (также регулируемая длина).

Пример времени для этого script:

real    0m0.289s
user    0m0.176s
sys     0m0.108s

Использование: xkcdpass-mod.py 2 4 (например, это значения по умолчанию).

Он печатает пробелы на выходе для удобства чтения, хотя я почти никогда не сталкивался с онлайн-сервисом, который позволяет их использовать, поэтому я просто проигнорирую их. Это определенно можно было бы очистить с помощью argparse или getopt и позволить переключателям включать пробелы или нет, включая/исключая символы, капиталы и т.д., А также некоторые дополнительные рефакторинги, но я еще не дошел до этого. Итак, без дальнейших церемоний:

#!/usr/bin/env python
#Copyright AMH, 2013; dedicated to public domain.
import os, re, sys, random
from sys import argv

def getargs():
    if len(argv) == 3:
        numwords = argv[1]
        numpads = argv[2]
        return(numwords, numpads)
    elif len(argv) == 2:
        numwords = argv[1]
        numpads = 4
        return (numwords, numpads)
    else:
        numwords = 2
        numpads = 4
        return (numwords, numpads)

def dicopen(dictionary="/usr/share/dict/american-english"):
    f = open(dictionary, "r")
    dic = f.readlines()
    return dic

def genPassword(numwords, numpads):
    r = random.SystemRandom()
    pads = '[email protected]#$%^&*()'
    padding = []
    words = dicopen()
    wordlist = []
    for i in range (0,int(numpads)):
        padding.append(pads[r.randint(0,len(pads)-1)])
    #initialize counter for only adding filtered words to passphrase
    j = 0
    while (j < int(numwords)):
        inclusion_criteria = re.compile('^[a-z]{5,10}$')
        #Select a random number, then pull the word at that index value, rather than looping through the dictionary for each word
        current_word = words[r.randint(0,len(words)-1)].strip()
        #Only append matching words
        if inclusion_criteria.match(current_word):
            wordlist.append(current_word)
            j += 1
        else:
        #Ignore non-matching words
            pass
    return(" ".join(wordlist)+' '+''.join(padding))

if(__name__ == "__main__"):
    for i in range (1,11):
       print "item "+str(i)+"\n"+genPassword(getargs()[0], getargs()[1])

Пример вывода:

[✗]─[[email protected]]─[~/bin]
└──╼ xkcdpass-mod.py
item 1
digress basketball )%^)
item 2
graves giant &118
item 3
impelled maniacs ^@%1

И для полного "правильного штатива батареи лошади" (CHBS), нет прокладки:

┌─[[email protected]]─[~/bin]
└──╼ xkcdpass-mod.py 4 0
item 1
superseded warred nighthawk rotary 
item 2
idealize chirruping gabbing vegan 
item 3
wriggling contestant hiccoughs instanced 

Согласно https://www.grc.com/haystack.htm, для всех практических целей, предполагая 100 триллионов догадок в секунду (то есть 100 TH/s), более короткая версия потребуется около 50-60 миллионов столетий; полный CHBS = 1,24 сотен триллионов триллионов столетий; добавив дополнение к этому, 15,51 трлн триллион триллионов столетий.

Даже привлекая всю интеллектуальную сеть Bitcoin (~ 2500 TH/s на момент написания этой статьи), короткая версия, вероятно, займет 250-300 миллионов лет, что, вероятно, достаточно безопасно для большинства целей.

Ответ 17

import uuid
print('Your new password is: {0}').format(uuid.uuid4())

Ответ 18

Немного от темы, но я сделал это, используя также TKinter. Надеюсь, это поможет:

import os, random, string
from tkinter import *

def createPwd():
    try:
        length = int(e1.get())
    except ValueError:
        return
    chars = string.ascii_letters + string.digits + '[email protected]#$%^&*()?\/'
    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()

Ответ 19

Base64 позволяет кодировать двоичные данные в режиме чтения/записи на человека без потери данных.

import os
random_bytes=os.urandom(12)
secret=random_bytes.encode("base64")

Ответ 20

Это простая небольшая программа, адресованная людям, которые не могут определить безопасные пароли для собственных публичных учетных записей.

Просто запустите программу на командной консоли и передайте кучу писем, которые вам знакомы, и она будет генерировать последовательность символов на основе того, что вы вставили.

конечно, программа не поддерживает генерации нескольких последовательностей.

Вы можете загрузить код из моего github pull: https://github.com/abdechahidely/python_password_generator

from string import ascii_lowercase, ascii_uppercase, digits, punctuation
from random import randint, choice, shuffle
from math   import ceil
from re     import finditer

lower_cases  = ascii_lowercase
upper_cases  = ascii_uppercase
lower_upper  = dict(zip(lower_cases, upper_cases))
upper_lower  = dict(zip(upper_cases, lower_cases))
punctuations = '#$%&@!?.'
space        = ' '

class PunctOrDigit():

    def __init__(self, number_of_punctuations, number_of_digits):
        self.puncts = number_of_punctuations
        self.digits = number_of_digits
        self.dupl_puncts = self.puncts
        self.dupl_digits = self.digits

    def PorD(self):
        symbol_type = choice('pd')
        if symbol_type == 'p':
            if self.puncts == 0:
                return 'd'
            else:
                self.puncts -= 1
                return symbol_type
        if symbol_type == 'd':
            if self.digits == 0:
                return 'p'
            else:
                self.digits -= 1
                return symbol_type

    def reset(self):
        self.puncts = self.dupl_puncts
        self.digits = self.dupl_digits

def is_empty(text):
    for symbol in text:
        if symbol != space:
            return False
    return True

def contain_unauthorized_symbols(text):
    for symbol in text:
        if symbol in punctuation or symbol in digits:
            return True
    return False

def user_input():
    user_input = input('-- Sentence to transform: ')
    while is_empty(user_input) or len(user_input) < 8 or contain_unauthorized_symbols(user_input):
        user_input = input('-- Sentence to transform: ')
    return user_input

def number_of_punctuations(text):
    return ceil(len(text) / 2) - 3

def number_of_digits(text):
    return ceil(len(text) / 2) - 2

def total_symbols(text):
    return (number_of_digits(text) + number_of_punctuations(text), 
            number_of_punctuations(text),
            number_of_digits(text))

def positions_to_change(text):
    pos_objct = PunctOrDigit(number_of_punctuations(text), number_of_digits(text))
    positions = {}
    while len(positions) < total_symbols(text)[0]:
        i = randint(0,len(text)-1)
        while i in positions:
            i = randint(0,len(text)-1)
        positions[i] = pos_objct.PorD()
    pos_objct.reset()
    return positions

def random_switch(letter):
    if letter in lower_cases:
        switch_or_pass = choice('sp')
        if switch_or_pass == 's': return lower_upper[letter]
        else:                     return letter
    if letter in upper_cases:
        switch_or_pass = choice('sp')
        if switch_or_pass == 's': return upper_lower[letter]
        else:                     return letter

def repeated(text):
    reps = {}
    for letter in set(list(text)):
        indexs = [w.start() for w in finditer(letter, text)]
        if letter != ' ':
            if len(indexs) != 1:
                reps[letter] = indexs
    return reps

def not_repeated(text):
    reps = {}
    for letter in set(list(text)):
        indexs = [w.start() for w in finditer(letter, text)]
        if letter != ' ':
            if len(indexs) == 1:
                reps[letter] = indexs
    return reps

def generator(text, positions_to_change):
    rep     = repeated(text)
    not_rep = not_repeated(text)
    text    = list(text)

    for x in text:
        x_pos = text.index(x)
        if x not in positions_to_change:
            text[x_pos] = random_switch(x)

    for x in rep:
        for pos in rep[x]:
            if pos in positions_to_change:
                if positions_to_change[pos] == 'p':
                    shuffle(list(punctuations))
                    text[pos] = choice(punctuations)
                if positions_to_change[pos] == 'd':
                    shuffle(list(digits))
                    text[pos] = choice(digits)
    for x in not_rep:
        for pos in not_rep[x]:
            if pos in positions_to_change:
                if positions_to_change[pos] == 'p':
                    shuffle(list(punctuations))
                    text[pos] = choice(punctuations)
                if positions_to_change[pos] == 'd':
                    shuffle(list(digits))
                    text[pos] = choice(digits)

    text = ''.join(text)
    return text

if __name__ == '__main__':
    x = user_input()
    print(generator(x, positions_to_change(x)))

Ответ 21

Вот мой генератор случайных паролей после исследования этой темы:

`import os, random, string
   #Generate Random Password
   UPP = random.SystemRandom().choice(string.ascii_uppercase)
   LOW1 = random.SystemRandom().choice(string.ascii_lowercase)
   LOW2 = random.SystemRandom().choice(string.ascii_lowercase)
   LOW3 = random.SystemRandom().choice(string.ascii_lowercase)
   DIG1 = random.SystemRandom().choice(string.digits)
   DIG2 = random.SystemRandom().choice(string.digits)
   DIG3 = random.SystemRandom().choice(string.digits)
   SPEC = random.SystemRandom().choice('[email protected]#$%^&*()')
   PWD = None
   PWD = UPP + LOW1 + LOW2 + LOW3 + DIG1 + DIG2 + DIG3 + SPEC
   PWD = ''.join(random.sample(PWD,len(PWD)))
   print(PWD)`

Это приведет к созданию случайного пароля с 1 случайной буквой в верхнем регистре, 3 случайными строчными буквами, 3 случайными цифрами и 1 случайным специальным символом - это можно отрегулировать по мере необходимости. Затем он объединяет каждый случайный символ и создает случайный порядок. Я не знаю, считается ли это "высоким качеством", но он выполняет свою работу.

Ответ 22

Мое решение основано на ответе @Thomas Pornin (Обновлено)

import os, string

def get_pass(password_len=12):
  new_password=None
  symbols='+!'
  chars=string.ascii_lowercase+\
        string.ascii_uppercase+\
        string.digits+\
        symbols

  while new_password is None or \
        new_password[0] in string.digits or \
        new_password[0] in symbols:
     new_password=''.join([chars[ord(os.urandom(1)) % len(chars)] \
                             for i in range(password_len)])
  return new_password

print(get_pass())

Эта функция возвращает случайный пароль (без цифры или символа в начале пароля).

Ответ 23

Я только недавно начал изучать Python, и это то, что я написал сегодня. Надеюсь это поможет.

import random

characters = '[email protected]#$%^()}{/<>'
print('Password Length: ')
passwordLength = int(input())
password = ''

for i in range(passwordLength):
    password += random.choice(characters)
print(password)

Ответ 24

Это больше для удовольствия, чем что-либо. Оценки выгодно в passwordmeter.com, но невозможно запомнить.

#!/usr/bin/ruby

puts (33..126).map{|x| ('a'..'z').include?(x.chr.downcase) ?
                       (0..9).to_a.shuffle[0].to_s + x.chr :
                       x.chr}.uniq.shuffle[0..41].join[0..41]