# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 """Des fonctions pour gérer les formes cryptées des mots de passe Les scheme supportés sont {CRYPT}, {SSHA}, {SMD5}, {SHA}, {MD5} """ __all__ = ( 'is_crypt_scheme', 'is_smd5_scheme', 'is_ssha_scheme', 'is_md5_scheme', 'is_sha_scheme', 'get_scheme', 'get_crypt_salt', 'get_smd5_salt', 'get_ssha_salt', 'get_salt', 'gen_crypt_salt', 'gen_binary_salt', 'gen_salt', 'gen_crypt_hash', 'gen_binary_hash', 'gen_hash', ) import base64, random, hashlib try: from crypt import crypt except: crypt = None from .uio import Utf8IO _utf8 = Utf8IO() def is_crypt_scheme(pw): if pw is None: return False return pw[:7].lower() == '{crypt}' or pw.lower() == 'crypt' def is_smd5_scheme(pw): if pw is None: return False return pw[:6].lower() == '{smd5}' or pw.lower() == 'smd5' def is_ssha_scheme(pw): if pw is None: return False return pw[:6].lower() == '{ssha}' or pw.lower() == 'ssha' def is_md5_scheme(pw): if pw is None: return False return pw[:5].lower() == '{md5}' or pw.lower() == 'md5' def is_sha_scheme(pw): if pw is None: return False return pw[:5].lower() == '{sha}' or pw.lower() == 'sha' def get_scheme(pw): if pw is None: return None if is_crypt_scheme(pw): return '{CRYPT}' elif is_smd5_scheme(pw): return '{SMD5}' elif is_ssha_scheme(pw): return '{SSHA}' elif is_md5_scheme(pw): return '{MD5}' elif is_sha_scheme(pw): return '{SHA}' else: return '{CRYPT}' def get_crypt_salt(pw): if pw is None: return None if pw[:7].lower() == '{crypt}': pw = pw[7:] return pw[:2] def get_smd5_salt(pw): if pw is None: return None if pw[:6].lower() == '{smd5}': pw = pw[6:] return base64.decodestring(pw)[16:] def get_ssha_salt(pw): if pw is None: return None if pw[:6].lower() == '{ssha}': pw = pw[6:] return base64.decodestring(pw)[20:] def get_salt(pw): if pw is None: return None if pw[:7].lower() == '{crypt}': return get_crypt_salt(pw) if pw[:6].lower() == '{smd5}': return get_smd5_salt(pw) if pw[:6].lower() == '{ssha}': return get_ssha_salt(pw) # pas de salt if pw[:5].lower() == '{md5}': return '' if pw[:5].lower() == '{sha}': return '' # par défaut, considérer le salt comme pour crypt return get_crypt_salt(pw) CRYPT_SALT_CHARS = "./abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" def gen_crypt_salt(): return random.choice(CRYPT_SALT_CHARS) + random.choice(CRYPT_SALT_CHARS) def gen_binary_salt(length=20): salt = [] for i in range(length): salt.append(chr(random.randint(0, 255))) return ''.join(salt) def gen_salt(pw=None, length=20): if pw is not None: if pw[:7].lower() == '{crypt}' or pw.lower() == 'crypt': return gen_crypt_salt() if pw[:6].lower() == '{smd5}' or pw.lower() == 'smd5': return gen_binary_salt(length) if pw[:6].lower() == '{ssha}' or pw.lower() == 'ssha': return gen_binary_salt(length) # pas de salt pour MD5 et SHA if pw[:5].lower() == '{md5}' or pw.lower() == 'md5': return '' if pw[:5].lower() == '{sha}' or pw.lower() == 'sha': return '' # par défaut, générer un salt pour crypt return gen_crypt_salt() def gen_crypt_hash(pw, salt=None): if salt is None: salt = gen_crypt_salt() if crypt is None: raise ValueError("crypt is unvailable") return crypt(pw, salt) def gen_binary_hash(pw, scheme, salt=None): if is_md5_scheme(scheme): m = hashlib.md5() elif is_sha_scheme(scheme): m = hashlib.sha1() elif is_smd5_scheme(scheme): m = hashlib.md5() if salt is None: salt = gen_binary_salt() elif is_ssha_scheme(scheme): m = hashlib.sha1() if salt is None: salt = gen_binary_salt() else: raise ValueError("unsupported scheme: %s" % scheme) pw = _utf8.s(pw) m.update(pw) if salt is not None: m.update(salt) hash = m.digest() if salt is not None: hash += salt return base64.b64encode(hash) def gen_hash(pw, scheme=None, ref=None, salt=None, normalized=True): if ref is not None: scheme = get_scheme(ref) if salt is None: salt = get_salt(ref) if is_crypt_scheme(scheme): hash = gen_crypt_hash(pw, salt) else: hash = gen_binary_hash(pw, scheme, salt) elif scheme is not None: scheme = get_scheme(scheme) if is_crypt_scheme(scheme): hash = gen_crypt_hash(pw, salt) else: hash = gen_binary_hash(pw, scheme, salt) else: # Par défaut, hasher en crypt scheme = '{CRYPT}' hash = gen_crypt_hash(pw, salt) if normalized: hash = scheme + hash return hash