1"""passlib.handlers.mssql - MS-SQL Password Hash 2 3Notes 4===== 5MS-SQL has used a number of hash algs over the years, 6most of which were exposed through the undocumented 7'pwdencrypt' and 'pwdcompare' sql functions. 8 9Known formats 10------------- 116.5 12 snefru hash, ascii encoded password 13 no examples found 14 157.0 16 snefru hash, unicode (what encoding?) 17 saw ref that these blobs were 16 bytes in size 18 no examples found 19 202000 21 byte string using displayed as 0x hex, using 0x0100 prefix. 22 contains hashes of password and upper-case password. 23 242007 25 same as 2000, but without the upper-case hash. 26 27refs 28---------- 29https://blogs.msdn.com/b/lcris/archive/2007/04/30/sql-server-2005-about-login-password-hashes.aspx?Redirected=true 30http://us.generation-nt.com/securing-passwords-hash-help-35429432.html 31http://forum.md5decrypter.co.uk/topic230-mysql-and-mssql-get-password-hashes.aspx 32http://www.theregister.co.uk/2002/07/08/cracking_ms_sql_server_passwords/ 33""" 34#============================================================================= 35# imports 36#============================================================================= 37# core 38from binascii import hexlify, unhexlify 39from hashlib import sha1 40import re 41import logging; log = logging.getLogger(__name__) 42from warnings import warn 43# site 44# pkg 45from passlib.utils import consteq 46from passlib.utils.compat import bascii_to_str, unicode, u 47import passlib.utils.handlers as uh 48# local 49__all__ = [ 50 "mssql2000", 51 "mssql2005", 52] 53 54#============================================================================= 55# mssql 2000 56#============================================================================= 57def _raw_mssql(secret, salt): 58 assert isinstance(secret, unicode) 59 assert isinstance(salt, bytes) 60 return sha1(secret.encode("utf-16-le") + salt).digest() 61 62BIDENT = b"0x0100" 63##BIDENT2 = b("\x01\x00") 64UIDENT = u("0x0100") 65 66def _ident_mssql(hash, csize, bsize): 67 """common identify for mssql 2000/2005""" 68 if isinstance(hash, unicode): 69 if len(hash) == csize and hash.startswith(UIDENT): 70 return True 71 elif isinstance(hash, bytes): 72 if len(hash) == csize and hash.startswith(BIDENT): 73 return True 74 ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes 75 ## return True 76 else: 77 raise uh.exc.ExpectedStringError(hash, "hash") 78 return False 79 80def _parse_mssql(hash, csize, bsize, handler): 81 """common parser for mssql 2000/2005; returns 4 byte salt + checksum""" 82 if isinstance(hash, unicode): 83 if len(hash) == csize and hash.startswith(UIDENT): 84 try: 85 return unhexlify(hash[6:].encode("utf-8")) 86 except TypeError: # throw when bad char found 87 pass 88 elif isinstance(hash, bytes): 89 # assumes ascii-compat encoding 90 assert isinstance(hash, bytes) 91 if len(hash) == csize and hash.startswith(BIDENT): 92 try: 93 return unhexlify(hash[6:]) 94 except TypeError: # throw when bad char found 95 pass 96 ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes 97 ## return hash[2:] 98 else: 99 raise uh.exc.ExpectedStringError(hash, "hash") 100 raise uh.exc.InvalidHashError(handler) 101 102class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): 103 """This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`. 104 105 It supports a fixed-length salt. 106 107 The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords: 108 109 :type salt: bytes 110 :param salt: 111 Optional salt string. 112 If not specified, one will be autogenerated (this is recommended). 113 If specified, it must be 4 bytes in length. 114 115 :type relaxed: bool 116 :param relaxed: 117 By default, providing an invalid value for one of the other 118 keywords will result in a :exc:`ValueError`. If ``relaxed=True``, 119 and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` 120 will be issued instead. Correctable errors include 121 ``salt`` strings that are too long. 122 """ 123 #=================================================================== 124 # algorithm information 125 #=================================================================== 126 name = "mssql2000" 127 setting_kwds = ("salt",) 128 checksum_size = 40 129 min_salt_size = max_salt_size = 4 130 131 #=================================================================== 132 # formatting 133 #=================================================================== 134 135 # 0100 - 2 byte identifier 136 # 4 byte salt 137 # 20 byte checksum 138 # 20 byte checksum 139 # = 46 bytes 140 # encoded '0x' + 92 chars = 94 141 142 @classmethod 143 def identify(cls, hash): 144 return _ident_mssql(hash, 94, 46) 145 146 @classmethod 147 def from_string(cls, hash): 148 data = _parse_mssql(hash, 94, 46, cls) 149 return cls(salt=data[:4], checksum=data[4:]) 150 151 def to_string(self): 152 raw = self.salt + self.checksum 153 # raw bytes format - BIDENT2 + raw 154 return "0x0100" + bascii_to_str(hexlify(raw).upper()) 155 156 def _calc_checksum(self, secret): 157 if isinstance(secret, bytes): 158 secret = secret.decode("utf-8") 159 salt = self.salt 160 return _raw_mssql(secret, salt) + _raw_mssql(secret.upper(), salt) 161 162 @classmethod 163 def verify(cls, secret, hash): 164 # NOTE: we only compare against the upper-case hash 165 # XXX: add 'full' just to verify both checksums? 166 uh.validate_secret(secret) 167 self = cls.from_string(hash) 168 chk = self.checksum 169 if chk is None: 170 raise uh.exc.MissingDigestError(cls) 171 if isinstance(secret, bytes): 172 secret = secret.decode("utf-8") 173 result = _raw_mssql(secret.upper(), self.salt) 174 return consteq(result, chk[20:]) 175 176#============================================================================= 177# handler 178#============================================================================= 179class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): 180 """This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`. 181 182 It supports a fixed-length salt. 183 184 The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords: 185 186 :type salt: bytes 187 :param salt: 188 Optional salt string. 189 If not specified, one will be autogenerated (this is recommended). 190 If specified, it must be 4 bytes in length. 191 192 :type relaxed: bool 193 :param relaxed: 194 By default, providing an invalid value for one of the other 195 keywords will result in a :exc:`ValueError`. If ``relaxed=True``, 196 and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning` 197 will be issued instead. Correctable errors include 198 ``salt`` strings that are too long. 199 """ 200 #=================================================================== 201 # algorithm information 202 #=================================================================== 203 name = "mssql2005" 204 setting_kwds = ("salt",) 205 206 checksum_size = 20 207 min_salt_size = max_salt_size = 4 208 209 #=================================================================== 210 # formatting 211 #=================================================================== 212 213 # 0x0100 - 2 byte identifier 214 # 4 byte salt 215 # 20 byte checksum 216 # = 26 bytes 217 # encoded '0x' + 52 chars = 54 218 219 @classmethod 220 def identify(cls, hash): 221 return _ident_mssql(hash, 54, 26) 222 223 @classmethod 224 def from_string(cls, hash): 225 data = _parse_mssql(hash, 54, 26, cls) 226 return cls(salt=data[:4], checksum=data[4:]) 227 228 def to_string(self): 229 raw = self.salt + self.checksum 230 # raw bytes format - BIDENT2 + raw 231 return "0x0100" + bascii_to_str(hexlify(raw)).upper() 232 233 def _calc_checksum(self, secret): 234 if isinstance(secret, bytes): 235 secret = secret.decode("utf-8") 236 return _raw_mssql(secret, self.salt) 237 238 #=================================================================== 239 # eoc 240 #=================================================================== 241 242#============================================================================= 243# eof 244#============================================================================= 245