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
						
					
					
				"""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
 | 
						|
#=============================================================================
 |