php中的密码加密,如web2py

php中的密码加密,如web2py,php,python,web2py,salt,password-hash,Php,Python,Web2py,Salt,Password Hash,我习惯于在php中实现web2py中的密码哈希,现在我从web2py的创建者massimo de piero那里得到了一些关于如何实现密码哈希的说明,但我仍然无法在php中实现密码哈希,说明如下: 逻辑非常复杂,因为它需要处理许多选项,并且不破坏向后兼容性 通常,哈希密码看起来像 算法$salt$hash 算法$$hash(无盐) 散列(遗留) 散列使用算法、salt和可选的用户提供的密钥进行计算。钥匙是独一无二的。每个密码的salt都不同 每次调用CRYPT()('password')都会得到

我习惯于在php中实现web2py中的密码哈希,现在我从web2py的创建者massimo de piero那里得到了一些关于如何实现密码哈希的说明,但我仍然无法在php中实现密码哈希,说明如下:

逻辑非常复杂,因为它需要处理许多选项,并且不破坏向后兼容性

通常,哈希密码看起来像

算法$salt$hash 算法$$hash(无盐) 散列(遗留)

散列使用算法、salt和可选的用户提供的密钥进行计算。钥匙是独一无二的。每个密码的salt都不同

每次调用CRYPT()('password')都会得到一个LazyCrypt对象。此对象可以序列化为字符串。您得到的字符串总是不同的,因为它包含一个随机的salt。您无法比较其中两个字符串,因为即使对于相同的密码,您也会得到false。但是,您可以将懒散对象与字符串进行比较,而懒散对象将使用相同的算法和字符串中相同的salt来计算哈希值,并将其与字符串中的哈希值进行比较。例如:

>>> a = CRYPT()('password')
>>> b = CRYPT()('password')
>>> sa = str(a)
>>> sb = str(b)
>>> sa == sb
False
>>> a == sb
True
>>> c = CRYPT()('wrong')
>>> c == sb
False
下面是web2py框架中用于实现该加密的类:

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) 
类LazyCrypt(对象):
"""
存储惰性密码散列
"""
定义初始化(self、crypt、密码):
"""
crypt是crypt validator的一个实例,
password是用户插入的密码
"""
self.crypt=crypt
self.password=密码
self.crypted=None
定义(自我):
"""
加密的self.password并将其缓存在self.crypted中。
如果self.crypt.salt,则输出的格式为$$
尝试从键获取摘要(如果存在)
否则,假设默认摘要值。如果根本没有键,则设置键=“”
如果指定了salt,则使用它;如果salt为True,则将salt设置为uuid
(这一切都应该是向后兼容的)
选项:
键='uuid'
key='md5:uuid'
key='sha512:uuid'
...
key='pbkdf2(1000,64,sha512):uuid'1000次迭代和64个字符长度
"""
如果自加密:
返回自加密
如果self.crypt.key:
如果self.crypt.key中的“:”:
digest_alg,key=self.crypt.key.split(“:”,1)
其他:
digest\u alg,key=self.crypt.digest\u alg,self.crypt.key
其他:
digest_alg,key=self.crypt.digest_alg''
如果self.crypt.salt:
如果self.crypt.salt==True:
salt=str(web2py_uuid())。替换('-','')[-16:]
其他:
salt=self.crypt.salt
其他:
盐=“”
hashed=简单散列(self.password、key、salt、digest\u alg)
self.crypted=“%s$%s$%s%”(摘要,salt,散列)
返回自加密
def _; eq ___;(自身、存储的密码):
"""
将当前惰性加密密码与存储的密码进行比较
"""
#LazyCrypt对象比较
如果存在(存储的\u密码、self.\u类\u):
返回((自存密码)或
((self.crypt.key==存储的密码.crypt.key)和
(self.password==存储的\u password.password)))
如果self.crypt.key:
如果self.crypt.key中的“:”:
key=self.crypt.key.split(“:”)[1]
其他:
key=self.crypt.key
其他:
键=“”
如果存储的密码为“无”:
返回错误
elif存储的密码。计数(“$”)==2:
(digest_alg、salt、hash)=存储的_password.split(“$”)
h=简单散列(self.password、key、salt、digest\u alg)
临时通行证=“%s$%s$%s%”(摘要,盐,h)
否则:#不要加盐
#猜一猜
digest\u alg=digest\u alg\u BY\u SIZE.get(len(存储的\u密码),无)
如果没有,请执行以下操作:
返回错误
其他:
temp\u pass=简单散列(self.password,key',digest\u alg)
返回临时密码==存储的密码
类CRYPT(对象):
"""
例子::
输入(_type='text',_name='name',requires=CRYPT())
使用摘要对验证时的值进行编码。
如果没有提供参数,CRYPT将使用MD5算法。
如果提供了关键参数,则使用HMAC+MD5算法。
如果指定了摘要,则使用该摘要替换
MD5,例如SHA512
作为字符串或算法本身的hashlib算法的名称。
min_length是最小密码长度(默认值为4)-对于严重的安全性而言,min_length是强大的
错误消息是密码太短时的消息
请注意,接受的密码为空,但无效。它将不允许重新登录。
将垃圾邮件存储为哈希密码。
指定一个算法,否则默认情况下我们将使用sha512。
典型的可用算法:
md5、sha1、SH224、SH256、SH384、SH512
如果是salt,则用salt散列密码。
如果salt为True,此方法将自动生成一个。
无论是哪种情况,它都会返回以下格式的加密密码字符串:
$$
重要提示:哈希密码作为LazyCrypt对象返回,仅在需要时计算。
LasyCrypt对象还知道如何将自己与现有的salt密码进行比较
支持标准算法
>>>对于('md5'、'sha1'、'sha256'、'sha384'、'sha512')中的alg:
…打印str(CRYPT(digest_alg=alg,salt=True)('test')[0])
md5$…$。。。
sha1$…$。。。
sha256$…$。。。
sha384$…$。。。
sha512$…$。。。
语法始终为alg$salt$hash
对pbkdf2的支持
>>>alg='pbkdf2(1000,20,sha512)'
>>>打印str(CRYPT(digest_alg=alg,salt=True)('test')[0])
pbkdf2(1000,20SHA512)$。。。
可以指定可选的hmac_密钥,并将其用作salt前缀
>>>a=str(哭)