python - Password encryption in php like web2py

714

i wont to implement the password hashing done in web2py in php , now i got some instructions on how to do it from massimo de piero the created of web2py but i am still cant implement it in php , the instruction as following :

The logic is very complex because it needs to deal with many options and not-break backward compatibility.

Normally an hashed password looks like

algorithm$salt$hash algorithm$$hash (no salt) hash (legacy)

the hash is computing using the algorithm, the salt, and optionally a user provided key. The key is unique. The salt is different for each password.

Every time you call CRYPT()('password') you get a LazyCrypt object. This object can be serialized into a string. The string you get is always different because it contains a random salt. You cannot compare two of those strings because you always get false, even for the same password. Yet you can compare a LazyObject with a string and the lazy object will use the same algorithm and the same salt from the string to compute the hash and compare it with the hash in the string. Example:

>>> a = CRYPT()('password')
>>> b = CRYPT()('password')
>>> sa = str(a)
>>> sb = str(b)
>>> sa == sb
False
>>> a == sb
True
>>> c = CRYPT()('wrong')
>>> c == sb
False

and the following is the class used in web2py framework to implement that encryption :

class LazyCrypt(object):
    """
    Stores a lazy password hash
    """

    def __init__(self, crypt, password):
        """
        crypt is an instance of the CRYPT validator,
        password is the password as inserted by the user
        """
        self.crypt = crypt
        self.password = password
        self.crypted = None

    def __str__(self):
        """
        Encrypted self.password and caches it in self.crypted.
        If self.crypt.salt the output is in the format <algorithm>$<salt>$<hash>
        Try get the digest_alg from the key (if it exists)
        else assume the default digest_alg. If not key at all, set key=''
        If a salt is specified use it, if salt is True, set salt to uuid
        (this should all be backward compatible)
        Options:
        key = 'uuid'
        key = 'md5:uuid'
        key = 'sha512:uuid'
        ...
        key = 'pbkdf2(1000,64,sha512):uuid' 1000 iterations and 64 chars length
        """
        if self.crypted:
            return self.crypted
        if self.crypt.key:
            if ':' in self.crypt.key:
                digest_alg, key = self.crypt.key.split(':', 1)
            else:
                digest_alg, key = self.crypt.digest_alg, self.crypt.key
        else:
            digest_alg, key = self.crypt.digest_alg, ''
        if self.crypt.salt:
            if self.crypt.salt == True:
                salt = str(web2py_uuid()).replace('-', '')[-16:]
            else:
                salt = self.crypt.salt
        else:
            salt = ''

        hashed = simple_hash(self.password, key, salt, digest_alg)
        self.crypted = '%s$%s$%s' % (digest_alg, salt, hashed)
        return self.crypted

    def __eq__(self, stored_password):
        """
        compares the current lazy crypted password with a stored password
        """
        # LazyCrypt objects comparison
        if isinstance(stored_password, self.__class__):
            return ((self is stored_password) or
                   ((self.crypt.key == stored_password.crypt.key) and
                   (self.password == stored_password.password)))

        if self.crypt.key:
            if ':' in self.crypt.key:
                key = self.crypt.key.split(':')[1]
            else:
                key = self.crypt.key
        else:
            key = ''
        if stored_password is None:
            return False
        elif stored_password.count('$') == 2:
            (digest_alg, salt, hash) = stored_password.split('$')
            h = simple_hash(self.password, key, salt, digest_alg)
            temp_pass = '%s$%s$%s' % (digest_alg, salt, h)
        else:  # no salting
            # guess digest_alg
            digest_alg = DIGEST_ALG_BY_SIZE.get(len(stored_password), None)

            if not digest_alg:
                return False
            else:
                temp_pass = simple_hash(self.password, key, '', digest_alg)
        return temp_pass == stored_password


class CRYPT(object):

    """
    example::
        INPUT(_type='text', _name='name', requires=CRYPT())
    encodes the value on validation with a digest.
    If no arguments are provided CRYPT uses the MD5 algorithm.
    If the key argument is provided the HMAC+MD5 algorithm is used.
    If the digest_alg is specified this is used to replace the
    MD5 with, for example, SHA512. The digest_alg can be
    the name of a hashlib algorithm as a string or the algorithm itself.
    min_length is the minimal password length (default 4) - IS_STRONG for serious security
    error_message is the message if password is too short

    Notice that an empty password is accepted but invalid. It will not allow login back.

    Stores junk as hashed password.
    Specify an algorithm or by default we will use sha512.
    Typical available algorithms:
      md5, sha1, sha224, sha256, sha384, sha512

    If salt, it hashes a password with a salt.
    If salt is True, this method will automatically generate one.
    Either case it returns an encrypted password string in the following format:

      <algorithm>$<salt>$<hash>

    Important: hashed password is returned as a LazyCrypt object and computed only if needed.
    The LasyCrypt object also knows how to compare itself with an existing salted password

    Supports standard algorithms

    >>> for alg in ('md5','sha1','sha256','sha384','sha512'):

    ...     print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
    md5$...$...
    sha1$...$...
    sha256$...$...
    sha384$...$...
    sha512$...$...

    The syntax is always alg$salt$hash

    Supports for pbkdf2
    >>> alg = 'pbkdf2(1000,20,sha512)'
    >>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
    pbkdf2(1000,20,sha512)$...$...

    An optional hmac_key can be specified and it is used as salt prefix
    >>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
    >>> print a
    md5$...$...

    Even if the algorithm changes the hash can still be validated
    >>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a
    True

    If no salt is specified CRYPT can guess the algorithms from length:
    >>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0])
    >>> a
    'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
    >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a
    True
    >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:]
    True
    >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a
    True
    >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:]
    True
    """

    def __init__(self,
                 key=None,
                 digest_alg='pbkdf2(1000,20,sha512)',
                 min_length=0,
                 error_message='too short', salt=True):

        """
        important, digest_alg='md5' is not the default hashing algorithm for
        web2py. This is only an example of usage of this function.

        The actual hash algorithm is determined from the key which is
        generated by web2py in tools.py. This defaults to hmac+sha512.
        """

        self.key = key
        self.digest_alg = digest_alg
        self.min_length = min_length
        self.error_message = error_message
        self.salt = salt

    def __call__(self, value):
        if len(value) < self.min_length:
            return ('', translate(self.error_message))
        return (LazyCrypt(self, value), None) 

Hope anyone can guide me a little , Best Regards,

People are also looking for solutions to the problem: Remove unused CSS from site with PHP script

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.