私はphpでweb2pyで行われたパスワードハッシュを実装するつもりはありません.今、web2pyで作成されたmassimo depieroからそれを行う方法についていくつかの指示を受けましたが、私はまだphpでそれを実装できません.次のような指示:
多くのオプションを処理し、下位互換性を維持する必要があるため、ロジックは非常に複雑です。
通常、ハッシュされたパスワードは次のようになります
algorithm$salt$hash algorithm$$hash (salt なし) ハッシュ (レガシー)
ハッシュは、アルゴリズム、ソルト、およびオプションでユーザーが指定したキーを使用して計算されます。キーは一意です。ソルトはパスワードごとに異なります。
CRYPT()('password') を呼び出すたびに、LazyCrypt オブジェクトを取得します。このオブジェクトは、文字列にシリアル化できます。ランダムなソルトが含まれているため、得られる文字列は常に異なります。同じパスワードであっても、常に false になるため、これらの文字列の 2 つを比較することはできません。ただし、LazyObject を文字列と比較すると、遅延オブジェクトは同じアルゴリズムと文字列からの同じソルトを使用してハッシュを計算し、それを文字列内のハッシュと比較します。例:
>>> 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)
よろしくお願いします。