| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- """passlib.handlers.mssql - MS-SQL Password Hash
- Notes
- =====
- MS-SQL has used a number of hash algs over the years,
- most of which were exposed through the undocumented
- 'pwdencrypt' and 'pwdcompare' sql functions.
- Known formats
- -------------
- 6.5
- snefru hash, ascii encoded password
- no examples found
- 7.0
- snefru hash, unicode (what encoding?)
- saw ref that these blobs were 16 bytes in size
- no examples found
- 2000
- byte string using displayed as 0x hex, using 0x0100 prefix.
- contains hashes of password and upper-case password.
- 2007
- same as 2000, but without the upper-case hash.
- refs
- ----------
- https://blogs.msdn.com/b/lcris/archive/2007/04/30/sql-server-2005-about-login-password-hashes.aspx?Redirected=true
- http://us.generation-nt.com/securing-passwords-hash-help-35429432.html
- http://forum.md5decrypter.co.uk/topic230-mysql-and-mssql-get-password-hashes.aspx
- http://www.theregister.co.uk/2002/07/08/cracking_ms_sql_server_passwords/
- """
- #=============================================================================
- # imports
- #=============================================================================
- # core
- from binascii import hexlify, unhexlify
- from hashlib import sha1
- import re
- import logging; log = logging.getLogger(__name__)
- from warnings import warn
- # site
- # pkg
- from passlib.utils import consteq
- from passlib.utils.compat import bascii_to_str, unicode, u
- import passlib.utils.handlers as uh
- # local
- __all__ = [
- "mssql2000",
- "mssql2005",
- ]
- #=============================================================================
- # mssql 2000
- #=============================================================================
- def _raw_mssql(secret, salt):
- assert isinstance(secret, unicode)
- assert isinstance(salt, bytes)
- return sha1(secret.encode("utf-16-le") + salt).digest()
- BIDENT = b"0x0100"
- ##BIDENT2 = b("\x01\x00")
- UIDENT = u("0x0100")
- def _ident_mssql(hash, csize, bsize):
- """common identify for mssql 2000/2005"""
- if isinstance(hash, unicode):
- if len(hash) == csize and hash.startswith(UIDENT):
- return True
- elif isinstance(hash, bytes):
- if len(hash) == csize and hash.startswith(BIDENT):
- return True
- ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
- ## return True
- else:
- raise uh.exc.ExpectedStringError(hash, "hash")
- return False
- def _parse_mssql(hash, csize, bsize, handler):
- """common parser for mssql 2000/2005; returns 4 byte salt + checksum"""
- if isinstance(hash, unicode):
- if len(hash) == csize and hash.startswith(UIDENT):
- try:
- return unhexlify(hash[6:].encode("utf-8"))
- except TypeError: # throw when bad char found
- pass
- elif isinstance(hash, bytes):
- # assumes ascii-compat encoding
- assert isinstance(hash, bytes)
- if len(hash) == csize and hash.startswith(BIDENT):
- try:
- return unhexlify(hash[6:])
- except TypeError: # throw when bad char found
- pass
- ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes
- ## return hash[2:]
- else:
- raise uh.exc.ExpectedStringError(hash, "hash")
- raise uh.exc.InvalidHashError(handler)
- class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`.
- It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt string.
- If not specified, one will be autogenerated (this is recommended).
- If specified, it must be 4 bytes in length.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include
- ``salt`` strings that are too long.
- """
- #===================================================================
- # algorithm information
- #===================================================================
- name = "mssql2000"
- setting_kwds = ("salt",)
- checksum_size = 40
- min_salt_size = max_salt_size = 4
- #===================================================================
- # formatting
- #===================================================================
- # 0100 - 2 byte identifier
- # 4 byte salt
- # 20 byte checksum
- # 20 byte checksum
- # = 46 bytes
- # encoded '0x' + 92 chars = 94
- @classmethod
- def identify(cls, hash):
- return _ident_mssql(hash, 94, 46)
- @classmethod
- def from_string(cls, hash):
- data = _parse_mssql(hash, 94, 46, cls)
- return cls(salt=data[:4], checksum=data[4:])
- def to_string(self):
- raw = self.salt + self.checksum
- # raw bytes format - BIDENT2 + raw
- return "0x0100" + bascii_to_str(hexlify(raw).upper())
- def _calc_checksum(self, secret):
- if isinstance(secret, bytes):
- secret = secret.decode("utf-8")
- salt = self.salt
- return _raw_mssql(secret, salt) + _raw_mssql(secret.upper(), salt)
- @classmethod
- def verify(cls, secret, hash):
- # NOTE: we only compare against the upper-case hash
- # XXX: add 'full' just to verify both checksums?
- uh.validate_secret(secret)
- self = cls.from_string(hash)
- chk = self.checksum
- if chk is None:
- raise uh.exc.MissingDigestError(cls)
- if isinstance(secret, bytes):
- secret = secret.decode("utf-8")
- result = _raw_mssql(secret.upper(), self.salt)
- return consteq(result, chk[20:])
- #=============================================================================
- # handler
- #=============================================================================
- class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler):
- """This class implements the password hash used by MS-SQL 2005, and follows the :ref:`password-hash-api`.
- It supports a fixed-length salt.
- The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
- :type salt: bytes
- :param salt:
- Optional salt string.
- If not specified, one will be autogenerated (this is recommended).
- If specified, it must be 4 bytes in length.
- :type relaxed: bool
- :param relaxed:
- By default, providing an invalid value for one of the other
- keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
- and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
- will be issued instead. Correctable errors include
- ``salt`` strings that are too long.
- """
- #===================================================================
- # algorithm information
- #===================================================================
- name = "mssql2005"
- setting_kwds = ("salt",)
- checksum_size = 20
- min_salt_size = max_salt_size = 4
- #===================================================================
- # formatting
- #===================================================================
- # 0x0100 - 2 byte identifier
- # 4 byte salt
- # 20 byte checksum
- # = 26 bytes
- # encoded '0x' + 52 chars = 54
- @classmethod
- def identify(cls, hash):
- return _ident_mssql(hash, 54, 26)
- @classmethod
- def from_string(cls, hash):
- data = _parse_mssql(hash, 54, 26, cls)
- return cls(salt=data[:4], checksum=data[4:])
- def to_string(self):
- raw = self.salt + self.checksum
- # raw bytes format - BIDENT2 + raw
- return "0x0100" + bascii_to_str(hexlify(raw)).upper()
- def _calc_checksum(self, secret):
- if isinstance(secret, bytes):
- secret = secret.decode("utf-8")
- return _raw_mssql(secret, self.salt)
- #===================================================================
- # eoc
- #===================================================================
- #=============================================================================
- # eof
- #=============================================================================
|