"""  hmacHash.py

    Implemention of Request for Comments: 2104
    HMAC: Keyed-Hashing for Message Authentication

    HMAC is a mechanism for message authentication
    using cryptographic hash functions. HMAC can be used with any
    iterative cryptographic hash function, e.g., MD5, SHA-1, in
    combination with a secret shared key.  The cryptographic strength of
    HMAC depends on the properties of the underlying hash function.

    This implementation of HMAC uses a generic cryptographic 'hashFunction'
    (self.H). Hash functions must conform to the cryptopy.hash method
    conventions and are not directly compatible with the Python sha1 or md5 algorithms.

    [IETF]  RFC 2104 "HMAC: Keyed-Hashing for Message Authentication"
    
    >>>key = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    >>>keyedHashAlg = HMAC(SHA1, key)
    >>>result = keyedHashAlg(data)
"""
from cryptopy.hash.hash     import Hash

class HMAC(Hash):
    """    To compute HMAC over the data `text' we perform
                    H(K XOR opad, H(K XOR ipad, text))
    """
    def __init__(self, hashFunction, key = None):
        """ initialize HMAC with hashfunction and optionally the key """
        # should check for right type of function
        self.H       = hashFunction()      # a new instance for inner hash
        self.H_outer = hashFunction()      # separate outer context to allow intermediate digests
        self.B = self.H.raw_block_size     # in bytes, note - hash block size typically 1
                                           # and raw_block_size much larger
                                           # e.g. raw_block_size is 64 bytes for SHA1 and MD5
        self.name = 'HMAC_'+self.H.name
        self.blocksize      = 1   # single octets can be hashed by padding to raw block size
        self.raw_block_size = self.H.raw_block_size
        self.digest_size    = self.H.digest_size
        if key != None:
            self.setKey(key)
        else:
            self.keyed = None

    def setKey(self,key):
        """ setKey(key) ... key is binary string """
        if len(key) > self.B:   # if key is too long then hash it
            key = self.H(key)   # humm... this is odd, hash can be smaller than B
        else:                   # should raise error on short key, but breaks tests :-(
            key =key + (self.B-len(key)) * chr(0)
        self.k_xor_ipad = ''.join([chr(ord(bchar)^0x36) for bchar in key])
        self.k_xor_opad = ''.join([chr(ord(bchar)^0x5C) for bchar in key])
        self.keyed = 1
        self.reset()

    def reset(self):
        self.H.reset()
        if self.keyed == None :
            raise 'no key defined'
        self.H.update(self.k_xor_ipad) # start inner hash with key xored with ipad
                                       # outer hash always called as one full pass (no updates)
    def update(self,data):
        if self.keyed == None :
            raise 'no key defined'
        self.H.update(data)
    def digest(self):
        if self.keyed == None :
            raise 'no key defined'
        return self.H_outer(self.k_xor_opad+self.H.digest())

from cryptopy.hash.sha1Hash import SHA1
class HMAC_SHA1(HMAC):
    """ Predefined HMAC built on SHA1 """
    def __init__(self, key = None):
        """ optionally initialize with key """
        HMAC.__init__(self,SHA1,key)

from cryptopy.hash.md5Hash  import MD5
class HMAC_MD5(HMAC):
    """ Predefined HMAC built on SHA1 """
    def __init__(self, key = None):
        """ optionally initialize with key """
        HMAC.__init__(self,MD5,key)
