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 = {
                     {'chars': string.ascii_uppercase,
                      'entropy': math.log(len(string.ascii_uppercase), 2)},
                     {'chars': string.ascii_lowercase,
                      'entropy': math.log(len(string.ascii_lowercase), 2)},
                     {'chars': string.digits,
                      'entropy': math.log(len(string.digits), 2)},
                     {'chars': _special,
                      'entropy': math.log(len(_special), 2)},

    def __init__(self, uppercase=0, lowercase=0, digits=0, special=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.

        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(

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

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 

See the full comment at 
Manage your subscription for the Freeipa-devel mailing list:
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to