1"""passlib.handlers.mysql
2
3MySQL 3.2.3 / OLD_PASSWORD()
4
5    This implements Mysql's OLD_PASSWORD algorithm, introduced in version 3.2.3, deprecated in version 4.1.
6
7    See :mod:`passlib.handlers.mysql_41` for the new algorithm was put in place in version 4.1
8
9    This algorithm is known to be very insecure, and should only be used to verify existing password hashes.
10
11    http://djangosnippets.org/snippets/1508/
12
13MySQL 4.1.1 / NEW PASSWORD
14    This implements Mysql new PASSWORD algorithm, introduced in version 4.1.
15
16    This function is unsalted, and therefore not very secure against rainbow attacks.
17    It should only be used when dealing with mysql passwords,
18    for all other purposes, you should use a salted hash function.
19
20    Description taken from http://dev.mysql.com/doc/refman/6.0/en/password-hashing.html
21"""
22#=============================================================================
23# imports
24#=============================================================================
25# core
26from hashlib import sha1
27import re
28import logging; log = logging.getLogger(__name__)
29from warnings import warn
30# site
31# pkg
32from passlib.utils import to_native_str
33from passlib.utils.compat import bascii_to_str, unicode, u, \
34                                 byte_elem_value, str_to_uascii
35import passlib.utils.handlers as uh
36# local
37__all__ = [
38    'mysql323',
39    'mysq41',
40]
41
42#=============================================================================
43# backend
44#=============================================================================
45class mysql323(uh.StaticHandler):
46    """This class implements the MySQL 3.2.3 password hash, and follows the :ref:`password-hash-api`.
47
48    It has no salt and a single fixed round.
49
50    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
51    """
52    #===================================================================
53    # class attrs
54    #===================================================================
55    name = "mysql323"
56    checksum_size = 16
57    checksum_chars = uh.HEX_CHARS
58
59    #===================================================================
60    # methods
61    #===================================================================
62    @classmethod
63    def _norm_hash(cls, hash):
64        return hash.lower()
65
66    def _calc_checksum(self, secret):
67        # FIXME: no idea if mysql has a policy about handling unicode passwords
68        if isinstance(secret, unicode):
69            secret = secret.encode("utf-8")
70
71        MASK_32 = 0xffffffff
72        MASK_31 = 0x7fffffff
73        WHITE = b' \t'
74
75        nr1 = 0x50305735
76        nr2 = 0x12345671
77        add = 7
78        for c in secret:
79            if c in WHITE:
80                continue
81            tmp = byte_elem_value(c)
82            nr1 ^= ((((nr1 & 63)+add)*tmp) + (nr1 << 8)) & MASK_32
83            nr2 = (nr2+((nr2 << 8) ^ nr1)) & MASK_32
84            add = (add+tmp) & MASK_32
85        return u("%08x%08x") % (nr1 & MASK_31, nr2 & MASK_31)
86
87    #===================================================================
88    # eoc
89    #===================================================================
90
91#=============================================================================
92# handler
93#=============================================================================
94class mysql41(uh.StaticHandler):
95    """This class implements the MySQL 4.1 password hash, and follows the :ref:`password-hash-api`.
96
97    It has no salt and a single fixed round.
98
99    The :meth:`~passlib.ifc.PasswordHash.hash` and :meth:`~passlib.ifc.PasswordHash.genconfig` methods accept no optional keywords.
100    """
101    #===================================================================
102    # class attrs
103    #===================================================================
104    name = "mysql41"
105    _hash_prefix = u("*")
106    checksum_chars = uh.HEX_CHARS
107    checksum_size = 40
108
109    #===================================================================
110    # methods
111    #===================================================================
112    @classmethod
113    def _norm_hash(cls, hash):
114        return hash.upper()
115
116    def _calc_checksum(self, secret):
117        # FIXME: no idea if mysql has a policy about handling unicode passwords
118        if isinstance(secret, unicode):
119            secret = secret.encode("utf-8")
120        return str_to_uascii(sha1(sha1(secret).digest()).hexdigest()).upper()
121
122    #===================================================================
123    # eoc
124    #===================================================================
125
126#=============================================================================
127# eof
128#=============================================================================
129