URL: https://github.com/freeipa/freeipa/pull/317
Title: #317: Unify password generation across FreeIPA

pspacek commented:
"""
Talk is cheap so here is the code!
~~~
import math
import string
import random


class TokenGenerator(object):
    """Tunable token generator."""
    # without: = # ' " \ `
    _special = '!$%&()*+,-./:;<>?@[]^_{|}~'
    def_charsets = {
                     'uppercase':
                     {'chars': string.ascii_uppercase,
                      'entropy': math.log(len(string.ascii_uppercase), 2)},
                     'lowercase':
                     {'chars': string.ascii_lowercase,
                      'entropy': math.log(len(string.ascii_lowercase), 2)},
                     'digits':
                     {'chars': string.digits,
                      'entropy': math.log(len(string.digits), 2)},
                     'special':
                     {'chars': _special,
                      'entropy': math.log(len(_special), 2)},
            }

    def __init__(self, uppercase=0, lowercase=0, digits=0, special=0,
                 min_len=0):
        """Specify character contraints on generated tokens.

        Integer values specify minimal number of characters from given
        character class and length.
        Value False prevents given character from appearing in the token.

        Example:
        TokenGenerator(uppercase=3, lowercase=3, digits=0, special=False)

        At least 3 upper and 3 lower case ASCII chars, may contain digits,
        no special chars.
        """
        self.rng = random.SystemRandom()
        self.min_len = min_len
        self.req_classes = dict(
            uppercase=uppercase,
            lowercase=lowercase,
            digits=digits,
            special=special
        )

        self.todo_charsets = self.def_charsets.copy()
        # 'all' class is used when adding entropy to too-short tokens
        # it contains characters from all allowed classes
        self.todo_charsets['all'] = {'chars': ''.join(
                [charclass['chars']
                 for charclass_name, charclass
                 in self.todo_charsets.items()
                 if self.req_classes[charclass_name] is not False]
            )}
        self.todo_charsets['all']['entropy'] = math.log(
                len(self.todo_charsets['all']['chars']), 2)

    def __call__(self, req_entropy=128):
        """Generate token containing at least req_entropy bits.

        req_entropy is minimal number of entropy bits attacker has to guess:
        128 bits entropy: secure
        256 bits of entropy: secure enough if you care about quantum computers

        The generated token will fulfill containts specified in init.
        """
        todo_entropy = req_entropy
        password = ''
        # Generate required character classes:
        # The order of generated characters is fixed to comply with check in
        # NSS function sftk_newPinCheck() in nss/lib/softoken/fipstokn.c.
        for charclass_name in ['digits', 'uppercase', 'lowercase', 'special']:
            charclass = self.todo_charsets[charclass_name]
            todo_characters = self.req_classes[charclass_name]
            while todo_characters > 0:
                password += random.choice(charclass['chars'])
                todo_entropy -= charclass['entropy']
                todo_characters -= 1

        # required character classes do not provide sufficient entropy
        # or does not fulfill minimal length constraint
        allchars = self.todo_charsets['all']
        while todo_entropy > 0 or len(password) < self.min_len:
                password += random.choice(allchars['chars'])
                todo_entropy -= allchars['entropy']

        return password


if __name__ == '__main__':
    pwgen = TokenGenerator()
    for i in range(100):
        print(pwgen(256))
~~~

This code deterministically generates passwords. If character constraints are 
specified, the code might generate slightly longer passwords than the 
brute-force method. For example, 256 bit password with FIPS-compliant 
constrains (3 character classes) the difference is 41 vs. 40 characters. Given 
this different, I think that determinism trumphs shorter passwords.


Also, I think that it does not make sense to have `req_entropy` parameter in 
`__call__`. IMHO it makes sense to specify it along with other constrains in 
`__init__`.
"""

See the full comment at 
https://github.com/freeipa/freeipa/pull/317#issuecomment-266439544
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to