Высококачественный, простой генератор случайных паролей - PullRequest
65 голосов
/ 20 сентября 2011

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

import os, random, string

length = 13
chars = string.ascii_letters + string.digits + '!@#$%^&*()'
random.seed = (os.urandom(1024))

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

Ответы [ 24 ]

45 голосов
/ 20 сентября 2011

С паролями сложно сделать их достаточно сильными и при этом помнить их. Если пароль не предназначен для запоминания человеком, то на самом деле это не пароль.

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

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

38 голосов
/ 20 сентября 2011

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

http://xkcd.com/936/

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

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

13 голосов
/ 20 сентября 2011

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

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

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

Краген при неумолимости: ~ / devel / unfurable-misc $ ./mkpass.py 5 12 Ваш пароль - «выученные повреждения, спасенные жилые этапы». Это эквивалентно 60-битномуkey.

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

Самым распространенным алгоритмом хеширования паролей в наши дни является итеративный MD5 во FreeBSD, для взлома такого хеша потребуется 5.2e + 06 CPU-лет.

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

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

Я начал использовать пароль, сгенерированный таким образом, вместо случайного пароля из 9-печатаемых символов ASCII, который одинаково силен.Утверждение Манро, что эти пароли гораздо легче запомнить, является правильным.Тем не менее, проблема все еще остается: поскольку на символ приходится намного меньше битов энтропии (около 1,7 вместо 6,6), существует большая избыточность пароля, и поэтому такие атаки, как атака по каналу синхронизации ssh (песня,Атаки Вагнера и Тиана Травоядного, о которых я узнал от Брэма Коэна в кафе Багдад в утренние часы несколько лет назад, и атаки с помощью записи звука с клавиатуры имеют гораздо больше шансов собрать достаточно информации, чтобы сделать пароль доступным для атаки.

Моя контрмера атаки «Травоядный», которая хорошо работает с 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's 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's 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))
10 голосов
/ 16 декабря 2012

внедрение решения @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
7 голосов
/ 21 февраля 2012

Другая реализация метода 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
7 голосов
/ 12 мая 2014

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

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

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

4 голосов
/ 20 сентября 2011

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

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

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

import random, string
length = 13
chars = string.ascii_letters + string.digits + '!@#$%^&*()'

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

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

3 голосов
/ 20 сентября 2011

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

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

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

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

2 голосов
/ 17 января 2014

Решение проблемы с @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))
2 голосов
/ 08 мая 2015

Это легко:)

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

и делать:

print codegenerator()

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

...