Exemple #1
0
def pbkdf1(digest, secret, salt, rounds, keylen=None):
    """pkcs#5 password-based key derivation v1.5

    :arg digest:
        digest name or constructor.
        
    :arg secret:
        secret to use when generating the key.
        may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).
        
    :arg salt:
        salt string to use when generating key.
        may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).

    :param rounds:
        number of rounds to use to generate key.

    :arg keylen:
        number of bytes to generate (if omitted / ``None``, uses digest's native size)

    :returns:
        raw :class:`bytes` of generated key

    .. note::

        This algorithm has been deprecated, new code should use PBKDF2.
        Among other limitations, ``keylen`` cannot be larger
        than the digest size of the specified hash.
    """
    # resolve digest
    const, digest_size, block_size = lookup_hash(digest)
    
    # validate secret & salt
    secret = to_bytes(secret, param="secret")
    salt = to_bytes(salt, param="salt")

    # validate rounds
    if not isinstance(rounds, int_types):
        raise exc.ExpectedTypeError(rounds, "int", "rounds")
    if rounds < 1:
        raise ValueError("rounds must be at least 1")

    # validate keylen
    if keylen is None:
        keylen = digest_size
    elif not isinstance(keylen, int_types):
        raise exc.ExpectedTypeError(keylen, "int or None", "keylen")
    elif keylen < 0:
        raise ValueError("keylen must be at least 0")
    elif keylen > digest_size:
        raise ValueError("keylength too large for digest: %r > %r" %
                         (keylen, digest_size))

    # main pbkdf1 loop
    block = secret + salt
    for _ in irange(rounds):
        block = const(block).digest()
    return block[:keylen]
Exemple #2
0
 def encrypt(cls, secret, user, realm, encoding=None):
     # NOTE: this was deliberately written so that raw bytes are passed through
     # unchanged, the encoding kwd is only used to handle unicode values.
     if not encoding:
         encoding = cls.default_encoding
     uh.validate_secret(secret)
     if isinstance(secret, unicode):
         secret = secret.encode(encoding)
     user = to_bytes(user, encoding, "user")
     realm = to_bytes(realm, encoding, "realm")
     data = render_bytes("%s:%s:%s", user, realm, secret)
     return hashlib.md5(data).hexdigest()
Exemple #3
0
 def encrypt(cls, secret, user, realm, encoding=None):
     # NOTE: this was deliberately written so that raw bytes are passed through
     # unchanged, the encoding kwd is only used to handle unicode values.
     if not encoding:
         encoding = cls.default_encoding
     uh.validate_secret(secret)
     if isinstance(secret, unicode):
         secret = secret.encode(encoding)
     user = to_bytes(user, encoding, "user")
     realm = to_bytes(realm, encoding, "realm")
     data = render_bytes("%s:%s:%s", user, realm, secret)
     return hashlib.md5(data).hexdigest()
 def get_fuzz_settings(self):
     secret, other, kwds = super(_bcrypt_test,self).get_fuzz_settings()
     from lib.passlib.handlers.bcrypt import IDENT_2, IDENT_2X
     from lib.passlib.utils import to_bytes
     ident = kwds.get('ident')
     if ident == IDENT_2X:
         # 2x is just recognized, not supported. don't test with it.
         del kwds['ident']
     elif ident == IDENT_2 and other and repeat_string(to_bytes(other), len(to_bytes(secret))) == to_bytes(secret):
         # avoid false failure due to flaw in 0-revision bcrypt:
         # repeated strings like 'abc' and 'abcabc' hash identically.
         other = self.get_fuzz_password()
     return secret, other, kwds
 def get_fuzz_settings(self):
     secret, other, kwds = super(_bcrypt_test, self).get_fuzz_settings()
     from lib.passlib.handlers.bcrypt import IDENT_2, IDENT_2X
     from lib.passlib.utils import to_bytes
     ident = kwds.get('ident')
     if ident == IDENT_2X:
         # 2x is just recognized, not supported. don't test with it.
         del kwds['ident']
     elif ident == IDENT_2 and other and repeat_string(
             to_bytes(other), len(to_bytes(secret))) == to_bytes(secret):
         # avoid false failure due to flaw in 0-revision bcrypt:
         # repeated strings like 'abc' and 'abcabc' hash identically.
         other = self.get_fuzz_password()
     return secret, other, kwds
Exemple #6
0
    def test_to_bytes(self):
        """test to_bytes()"""
        from lib.passlib.utils import to_bytes

        # check unicode inputs
        self.assertEqual(to_bytes(u('abc')), b('abc'))
        self.assertEqual(to_bytes(u('\x00\xff')), b('\x00\xc3\xbf'))

        # check unicode w/ encodings
        self.assertEqual(to_bytes(u('\x00\xff'), 'latin-1'), b('\x00\xff'))
        self.assertRaises(ValueError, to_bytes, u('\x00\xff'), 'ascii')

        # check bytes inputs
        self.assertEqual(to_bytes(b('abc')), b('abc'))
        self.assertEqual(to_bytes(b('\x00\xff')), b('\x00\xff'))
        self.assertEqual(to_bytes(b('\x00\xc3\xbf')), b('\x00\xc3\xbf'))

        # check byte inputs ignores enocding
        self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1"),
                         b('\x00\xc3\xbf'))

        # check bytes transcoding
        self.assertEqual(to_bytes(b('\x00\xc3\xbf'), "latin-1", "", "utf-8"),
                         b('\x00\xff'))

        # check other
        self.assertRaises(AssertionError, to_bytes, 'abc', None)
        self.assertRaises(TypeError, to_bytes, None)
Exemple #7
0
def scrypt(secret, salt, n, r, p=1, keylen=32):
    """run SCrypt key derivation function using specified parameters.

    :arg secret:
        passphrase string (unicode is encoded to bytes using utf-8).

    :arg salt:
        salt string (unicode is encoded to bytes using utf-8).

    :arg n:
        integer 'N' parameter

    :arg r:
        integer 'r' parameter

    :arg p:
        integer 'p' parameter

    :arg keylen:
        number of bytes of key to generate.
        defaults to 32 (the internal block size).

    :returns:
        a *keylen*-sized bytes instance

    SCrypt imposes a number of constraints on it's input parameters:

    * ``r * p < 2**30`` -- due to a limitation of PBKDF2-HMAC-SHA256.
    * ``keylen < (2**32 - 1) * 32`` -- due to a limitation of PBKDF2-HMAC-SHA256.
    * ``n`` must a be a power of 2, and > 1 -- internal limitation of scrypt() implementation

    :raises ValueError: if the provided parameters are invalid (see constraints above).

    .. warning::

        Unless the third-party ``scrypt <https://pypi.python.org/pypi/scrypt/>``_ package
        is installed, passlib will use a builtin pure-python implementation of scrypt,
        which is *considerably* slower (and thus requires a much lower / less secure
        ``n`` value in order to be usuable). Installing the :mod:`!scrypt` package
        is strongly recommended.
    """
    validate(n, r, p)
    secret = to_bytes(secret, param="secret")
    salt = to_bytes(salt, param="salt")
    if keylen < 1:
        raise ValueError("keylen must be at least 1")
    if keylen > MAX_KEYLEN:
        raise ValueError("keylen too large, must be <= %d" % MAX_KEYLEN)
    return _scrypt(secret, salt, n, r, p, keylen)
Exemple #8
0
 def _calc_checksum(self, secret):
     # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
     uh.validate_secret(secret)
     secret = to_bytes(secret, "utf-8")
     kwds = dict(
         password=secret,
         salt=self.salt,
         time_cost=self.rounds,
         memory_cost=self.memory_cost,
         parallelism=self.parallelism,
         tag_length=self.checksum_size,
         type_code=self._get_backend_type(self.type),
         version=self.version,
     )
     if self.max_threads > 0:
         kwds['threads'] = self.max_threads
     if self.pure_use_threads:
         kwds['use_threads'] = True
     if self.data:
         kwds['associated_data'] = self.data
     # NOTE: should return raw bytes
     # NOTE: this may raise _argon2pure.Argon2ParameterError,
     #       but it if does that, there's a bug in our own parameter checking code.
     try:
         return _argon2pure.argon2(**kwds)
     except _argon2pure.Argon2Error as err:
         raise self._adapt_backend_error(err, self=self)
    def test_02_from_string(self):
        """test CryptPolicy.from_string() constructor"""
        # test "\n" linesep
        policy = CryptPolicy.from_string(self.sample_config_1s)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test "\r\n" linesep
        policy = CryptPolicy.from_string(
            self.sample_config_1s.replace("\n", "\r\n"))
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test with unicode
        data = to_unicode(self.sample_config_1s)
        policy = CryptPolicy.from_string(data)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test with non-ascii-compatible encoding
        uc2 = to_bytes(self.sample_config_1s,
                       "utf-16",
                       source_encoding="utf-8")
        policy = CryptPolicy.from_string(uc2, encoding="utf-16")
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test category specific options
        policy = CryptPolicy.from_string(self.sample_config_4s)
        self.assertEqual(policy.to_dict(), self.sample_config_4pd)
Exemple #10
0
 def _calc_checksum(self, secret):
     secret = to_bytes(secret, param="secret")
     return _scrypt.scrypt(secret,
                           self.salt,
                           n=(1 << self.rounds),
                           r=self.block_size,
                           p=self.parallelism,
                           keylen=self.checksum_size)
Exemple #11
0
 def hash(cls, secret):
     # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
     uh.validate_secret(secret)
     secret = to_bytes(secret, "utf-8")
     # XXX: doesn't seem to be a way to make this honor max_threads
     try:
         return bascii_to_str(_argon2_cffi.low_level.hash_secret(
             type=cls._get_backend_type(cls.type),
             memory_cost=cls.memory_cost,
             time_cost=cls.default_rounds,
             parallelism=cls.parallelism,
             salt=to_bytes(cls._generate_salt()),
             hash_len=cls.checksum_size,
             secret=secret,
         ))
     except _argon2_cffi.exceptions.HashingError as err:
         raise cls._adapt_backend_error(err)
 def check_bcrypt(secret, hash):
     """bcrypt"""
     secret = to_bytes(secret, self.fuzz_password_encoding)
     if hash.startswith(IDENT_2B):
         # bcrypt <1.1 lacks 2b support
         hash = IDENT_2A + hash[4:]
     elif hash.startswith(IDENT_2):
         # bcrypt doesn't support $2$ hashes; but we can fake it
         # using the $2a$ algorithm, by repeating the password until
         # it's 72 chars in length.
         hash = IDENT_2A + hash[3:]
         if secret:
             secret = repeat_string(secret, 72)
     hash = to_bytes(hash)
     try:
         return bcrypt.hashpw(secret, hash) == hash
     except ValueError:
         raise ValueError("bcrypt rejected hash: %r (secret=%r)" % (hash, secret))
 def check_bcrypt(secret, hash):
     """bcrypt"""
     secret = to_bytes(secret, self.fuzz_password_encoding)
     if hash.startswith(IDENT_2B):
         # bcrypt <1.1 lacks 2b support
         hash = IDENT_2A + hash[4:]
     elif hash.startswith(IDENT_2):
         # bcrypt doesn't support $2$ hashes; but we can fake it
         # using the $2a$ algorithm, by repeating the password until
         # it's 72 chars in length.
         hash = IDENT_2A + hash[3:]
         if secret:
             secret = repeat_string(secret, 72)
     hash = to_bytes(hash)
     try:
         return bcrypt.hashpw(secret, hash) == hash
     except ValueError:
         raise ValueError("bcrypt rejected hash: %r (secret=%r)" %
                          (hash, secret))
Exemple #14
0
    def verify(cls, secret, hash):
        # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
        uh.validate_secret(secret)
        secret = to_bytes(secret, "utf-8")
        hash = to_bytes(hash, "ascii")

        # read type from start of hash
        # NOTE: don't care about malformed strings, lowlevel will throw error for us
        type = cls._byte_ident_map.get(hash[:1+hash.find(b"$", 1)], TYPE_I)
        type_code = cls._get_backend_type(type)

        # XXX: doesn't seem to be a way to make this honor max_threads
        try:
            result = _argon2_cffi.low_level.verify_secret(hash, secret, type_code)
            assert result is True
            return True
        except _argon2_cffi.exceptions.VerifyMismatchError:
            return False
        except _argon2_cffi.exceptions.VerificationError as err:
            raise cls._adapt_backend_error(err, hash=hash)
Exemple #15
0
 def genhash(cls, secret, config):
     # TODO: add in 'encoding' support once that's finalized in 1.8 / 1.9.
     uh.validate_secret(secret)
     secret = to_bytes(secret, "utf-8")
     self = cls.from_string(config)
     # XXX: doesn't seem to be a way to make this honor max_threads
     try:
         result = bascii_to_str(_argon2_cffi.low_level.hash_secret(
             type=cls._get_backend_type(self.type),
             memory_cost=self.memory_cost,
             time_cost=self.rounds,
             parallelism=self.parallelism,
             salt=to_bytes(self.salt),
             hash_len=self.checksum_size,
             secret=secret,
             version=self.version,
         ))
     except _argon2_cffi.exceptions.HashingError as err:
         raise cls._adapt_backend_error(err, hash=config)
     if self.version == 0x10:
         # workaround: argon2 0x13 always returns "v=" segment, even for 0x10 hashes
         result = result.replace("$v=16$", "$")
     return result
    def test_01_from_path(self):
        """test CryptPolicy.from_path() constructor with encodings"""
        path = self.mktemp()

        # test "\n" linesep
        set_file(path, self.sample_config_1s)
        policy = CryptPolicy.from_path(path)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test "\r\n" linesep
        set_file(path, self.sample_config_1s.replace("\n","\r\n"))
        policy = CryptPolicy.from_path(path)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test with custom encoding
        uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
        set_file(path, uc2)
        policy = CryptPolicy.from_path(path, encoding="utf-16")
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)
    def test_02_from_string(self):
        """test CryptPolicy.from_string() constructor"""
        # test "\n" linesep
        policy = CryptPolicy.from_string(self.sample_config_1s)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test "\r\n" linesep
        policy = CryptPolicy.from_string(
            self.sample_config_1s.replace("\n","\r\n"))
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test with unicode
        data = to_unicode(self.sample_config_1s)
        policy = CryptPolicy.from_string(data)
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test with non-ascii-compatible encoding
        uc2 = to_bytes(self.sample_config_1s, "utf-16", source_encoding="utf-8")
        policy = CryptPolicy.from_string(uc2, encoding="utf-16")
        self.assertEqual(policy.to_dict(), self.sample_config_1pd)

        # test category specific options
        policy = CryptPolicy.from_string(self.sample_config_4s)
        self.assertEqual(policy.to_dict(), self.sample_config_4pd)
Exemple #18
0
 def _calc_checksum(self, secret):
     if isinstance(secret, unicode):
         secret = secret.encode("utf-8")
     user = to_bytes(self.user, "utf-8", param="user")
     return str_to_uascii(md5(secret + user).hexdigest())
Exemple #19
0
 def load_string(self, data):
     """Load state from unicode or bytes string, replacing current state"""
     data = to_bytes(data, self.encoding, "data")
     self._mtime = 0
     self._load_lines(BytesIO(data))
Exemple #20
0
def compile_hmac(digest, key, multipart=False):
    """
    This function returns an efficient HMAC function, hardcoded with a specific digest & key.
    It can be used via ``hmac = compile_hmac(digest, key)``.

    :arg digest:
        digest name or constructor.

    :arg key:
        secret key as :class:`!bytes` or :class:`!unicode` (unicode will be encoded using utf-8).

    :param multipart:
        request a multipart constructor instead (see return description).

    :returns:
        By default, the returned function has the signature ``hmac(msg) -> digest output``.

        However, if ``multipart=True``, the returned function has the signature
        ``hmac() -> update, finalize``, where ``update(msg)`` may be called multiple times,
        and ``finalize() -> digest_output`` may be repeatedly called at any point to
        calculate the HMAC digest so far.

        The returned object will also have a ``digest_info`` attribute, containing
        a :class:`lookup_hash` instance for the specified digest.

    This function exists, and has the weird signature it does, in order to squeeze as
    provide as much efficiency as possible, by omitting much of the setup cost
    and features of the stdlib :mod:`hmac` module.
    """
    # all the following was adapted from stdlib's hmac module

    # resolve digest (cached)
    digest_info = lookup_hash(digest)
    const, digest_size, block_size = digest_info
    assert block_size >= 16, "block size too small"

    # prepare key
    if not isinstance(key, bytes):
        key = to_bytes(key, param="key")
    klen = len(key)
    if klen > block_size:
        key = const(key).digest()
        klen = digest_size
    if klen < block_size:
        key += b'\x00' * (block_size - klen)

    # create pre-initialized hash constructors
    _inner_copy = const(key.translate(_TRANS_36)).copy
    _outer_copy = const(key.translate(_TRANS_5C)).copy

    if multipart:
        # create multi-part function
        # NOTE: this is slightly slower than the single-shot version,
        #       and should only be used if needed.
        def hmac():
            """generated by compile_hmac(multipart=True)"""
            inner = _inner_copy()
            def finalize():
                outer = _outer_copy()
                outer.update(inner.digest())
                return outer.digest()
            return inner.update, finalize
    else:

        # single-shot function
        def hmac(msg):
            """generated by compile_hmac()"""
            inner = _inner_copy()
            inner.update(msg)
            outer = _outer_copy()
            outer.update(inner.digest())
            return outer.digest()

    # add info attr
    hmac.digest_info = digest_info
    return hmac
Exemple #21
0
 def load_string(self, data):
     """Load state from unicode or bytes string, replacing current state"""
     data = to_bytes(data, self.encoding, "data")
     self._mtime = 0
     self._load_lines(BytesIO(data))
Exemple #22
0
 def _calc_checksum(self, secret):
     if isinstance(secret, unicode):
         secret = secret.encode("utf-8")
     user = to_bytes(self.user, "utf-8", param="user")
     return str_to_uascii(md5(secret + user).hexdigest())
Exemple #23
0
def pbkdf2_hmac(digest, secret, salt, rounds, keylen=None):
    """pkcs#5 password-based key derivation v2.0 using HMAC + arbitrary digest.

    :arg digest:
        digest name or constructor.

    :arg secret:
        passphrase to use to generate key.
        may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).

    :arg salt:
        salt string to use when generating key.
        may be :class:`!bytes` or :class:`unicode` (encoded using UTF-8).

    :param rounds:
        number of rounds to use to generate key.

    :arg keylen:
        number of bytes to generate.
        if omitted / ``None``, will use digest's native output size.

    :returns:
        raw bytes of generated key

    .. versionchanged:: 1.7

        This function will use the first available of the following backends:

        * `fastpbk2 <https://pypi.python.org/pypi/fastpbkdf2>`_
        * :func:`hashlib.pbkdf2_hmac` (only available in py2 >= 2.7.8, and py3 >= 3.4)
        * builtin pure-python backend

        See :data:`passlib.crypto.digest.PBKDF2_BACKENDS` to determine
        which backend(s) are in use.
    """
    # validate secret & salt
    secret = to_bytes(secret, param="secret")
    salt = to_bytes(salt, param="salt")

    # resolve digest
    digest_info = lookup_hash(digest)
    digest_size = digest_info.digest_size

    # validate rounds
    if not isinstance(rounds, int_types):
        raise exc.ExpectedTypeError(rounds, "int", "rounds")
    if rounds < 1:
        raise ValueError("rounds must be at least 1")

    # validate keylen
    if keylen is None:
        keylen = digest_size
    elif not isinstance(keylen, int_types):
        raise exc.ExpectedTypeError(keylen, "int or None", "keylen")
    elif keylen < 1:
        # XXX: could allow keylen=0, but want to be compat w/ stdlib
        raise ValueError("keylen must be at least 1")

    # find smallest block count s.t. keylen <= block_count * digest_size;
    # make sure block count won't overflow (per pbkdf2 spec)
    # this corresponds to throwing error if keylen > digest_size * MAX_UINT32
    # NOTE: stdlib will throw error at lower bound (keylen > MAX_SINT32)
    # NOTE: have do this before other backends checked, since fastpbkdf2 raises wrong error
    #       (InvocationError, not OverflowError)
    block_count = (keylen + digest_size - 1) // digest_size
    if block_count > MAX_UINT32:
        raise OverflowError("keylen too long for digest")

    #
    # check for various high-speed backends
    #

    # ~3x faster than pure-python backend
    # NOTE: have to do this after above guards since fastpbkdf2 lacks bounds checks.
    if digest_info.supported_by_fastpbkdf2:
        return _fast_pbkdf2_hmac(digest_info.name, secret, salt, rounds, keylen)

    # ~1.4x faster than pure-python backend
    # NOTE: have to do this after fastpbkdf2 since hashlib-ssl is slower,
    #       will support larger number of hashes.
    if digest_info.supported_by_hashlib_pbkdf2:
        return _stdlib_pbkdf2_hmac(digest_info.name, secret, salt, rounds, keylen)

    #
    # otherwise use our own implementation
    #

    # generated keyed hmac
    keyed_hmac = compile_hmac(digest, secret)

    # get helper to calculate pbkdf2 inner loop efficiently
    calc_block = _get_pbkdf2_looper(digest_size)

    # assemble & return result
    return join_bytes(
        calc_block(keyed_hmac, keyed_hmac(salt + _pack_uint32(i)), rounds)
        for i in irange(1, block_count + 1)
    )[:keylen]