You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					173 lines
				
				6.5 KiB
			
		
		
			
		
	
	
					173 lines
				
				6.5 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.handlers.oracle - Oracle DB Password Hashes"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								from binascii import hexlify, unhexlify
							 | 
						||
| 
								 | 
							
								from hashlib import sha1
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.utils import to_unicode, xor_bytes
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import irange, u, \
							 | 
						||
| 
								 | 
							
								                                 uascii_to_str, unicode, str_to_uascii
							 | 
						||
| 
								 | 
							
								from passlib.crypto.des import des_encrypt_block
							 | 
						||
| 
								 | 
							
								import passlib.utils.handlers as uh
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "oracle10g",
							 | 
						||
| 
								 | 
							
								    "oracle11g"
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# oracle10
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								def des_cbc_encrypt(key, value, iv=b'\x00' * 8, pad=b'\x00'):
							 | 
						||
| 
								 | 
							
								    """performs des-cbc encryption, returns only last block.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this performs a specific DES-CBC encryption implementation
							 | 
						||
| 
								 | 
							
								    as needed by the Oracle10 hash. it probably won't be useful for
							 | 
						||
| 
								 | 
							
								    other purposes as-is.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    input value is null-padded to multiple of 8 bytes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg key: des key as bytes
							 | 
						||
| 
								 | 
							
								    :arg value: value to encrypt, as bytes.
							 | 
						||
| 
								 | 
							
								    :param iv: optional IV
							 | 
						||
| 
								 | 
							
								    :param pad: optional pad byte
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns: last block of DES-CBC encryption of all ``value``'s byte blocks.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    value += pad * (-len(value) % 8) # null pad to multiple of 8
							 | 
						||
| 
								 | 
							
								    hash = iv # start things off
							 | 
						||
| 
								 | 
							
								    for offset in irange(0,len(value),8):
							 | 
						||
| 
								 | 
							
								        chunk = xor_bytes(hash, value[offset:offset+8])
							 | 
						||
| 
								 | 
							
								        hash = des_encrypt_block(key, chunk)
							 | 
						||
| 
								 | 
							
								    return hash
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# magic string used as initial des key by oracle10
							 | 
						||
| 
								 | 
							
								ORACLE10_MAGIC = b"\x01\x23\x45\x67\x89\xAB\xCD\xEF"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class oracle10(uh.HasUserContext, uh.StaticHandler):
							 | 
						||
| 
								 | 
							
								    """This class implements the password hash used by Oracle up to version 10g, and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    It does a single round of hashing, and relies on the username as the salt.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.hash`, :meth:`~passlib.ifc.PasswordHash.genhash`, and :meth:`~passlib.ifc.PasswordHash.verify` methods all require the
							 | 
						||
| 
								 | 
							
								    following additional contextual keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type user: str
							 | 
						||
| 
								 | 
							
								    :param user: name of oracle user account this password is associated with.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # algorithm information
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    name = "oracle10"
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.HEX_CHARS
							 | 
						||
| 
								 | 
							
								    checksum_size = 16
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # methods
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _norm_hash(cls, hash):
							 | 
						||
| 
								 | 
							
								        return hash.upper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        # FIXME: not sure how oracle handles unicode.
							 | 
						||
| 
								 | 
							
								        #        online docs about 10g hash indicate it puts ascii chars
							 | 
						||
| 
								 | 
							
								        #        in a 2-byte encoding w/ the high byte set to null.
							 | 
						||
| 
								 | 
							
								        #        they don't say how it handles other chars, or what encoding.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        #        so for now, encoding secret & user to utf-16-be,
							 | 
						||
| 
								 | 
							
								        #        since that fits, and if secret/user is bytes,
							 | 
						||
| 
								 | 
							
								        #        we assume utf-8, and decode first.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        #        this whole mess really needs someone w/ an oracle system,
							 | 
						||
| 
								 | 
							
								        #        and some answers :)
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, bytes):
							 | 
						||
| 
								 | 
							
								            secret = secret.decode("utf-8")
							 | 
						||
| 
								 | 
							
								        user = to_unicode(self.user, "utf-8", param="user")
							 | 
						||
| 
								 | 
							
								        input = (user+secret).upper().encode("utf-16-be")
							 | 
						||
| 
								 | 
							
								        hash = des_cbc_encrypt(ORACLE10_MAGIC, input)
							 | 
						||
| 
								 | 
							
								        hash = des_cbc_encrypt(hash, input)
							 | 
						||
| 
								 | 
							
								        return hexlify(hash).decode("ascii").upper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# oracle11
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class oracle11(uh.HasSalt, uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """This class implements the Oracle11g password hash, 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: str
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        Optional salt string.
							 | 
						||
| 
								 | 
							
								        If not specified, one will be autogenerated (this is recommended).
							 | 
						||
| 
								 | 
							
								        If specified, it must be 20 hexadecimal characters.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :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.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    #--GenericHandler--
							 | 
						||
| 
								 | 
							
								    name = "oracle11"
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt",)
							 | 
						||
| 
								 | 
							
								    checksum_size = 40
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.UPPER_HEX_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--HasSalt--
							 | 
						||
| 
								 | 
							
								    min_salt_size = max_salt_size = 20
							 | 
						||
| 
								 | 
							
								    salt_chars = uh.UPPER_HEX_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # methods
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    _hash_regex = re.compile(u("^S:(?P<chk>[0-9a-f]{40})(?P<salt>[0-9a-f]{20})$"), re.I)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        hash = to_unicode(hash, "ascii", "hash")
							 | 
						||
| 
								 | 
							
								        m = cls._hash_regex.match(hash)
							 | 
						||
| 
								 | 
							
								        if not m:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.InvalidHashError(cls)
							 | 
						||
| 
								 | 
							
								        salt, chk = m.group("salt", "chk")
							 | 
						||
| 
								 | 
							
								        return cls(salt=salt, checksum=chk.upper())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        chk = self.checksum
							 | 
						||
| 
								 | 
							
								        hash = u("S:%s%s") % (chk.upper(), self.salt.upper())
							 | 
						||
| 
								 | 
							
								        return uascii_to_str(hash)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        chk = sha1(secret + unhexlify(self.salt.encode("ascii"))).hexdigest()
							 | 
						||
| 
								 | 
							
								        return str_to_uascii(chk).upper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |