#!/usr/bin/python
# coding: iso-8859-15

global hashlib
import hashlib
global os
import os
global random
import random
global AES
from Crypto.Cipher import AES
global array
import array
#//LIB_START
#//LIB_END

## @brief Klassen zur Verschlüsselung und Hash-Erzeugung. (pyCrypto Package)
## @details
## Falls der Simulator verwendet werden soll, muss @e pyCrypto @e 2.6 (ggf. neuere Version, siehe @e www.pycrypto.de) auf dem Entwicklungsrechner installiert sein.
class hsl20_4_crypto:

    ## Klasse zur Erzeugung eines MD5-Hashs. Teil des Pakets zur @b Verschlüsselung.
    ## @code
    ## class Crypto_HASH(hsl20_4.BaseModule):
    ##
    ##     def __init__(self, homeserver_context):
    ##         ....
    ##         #################################################
    ##
    ##     def on_input_value(self, index, value):
    ##         if index == self.PIN_I_DO_HASH:
    ##             hash = self.FRAMEWORK.create_md5_hash()
    ##             hash.update(self._get_input_value(self.PIN_I_DATA))
    ##             self._set_output_value(self.PIN_O_HEXCODE, hash.hex_digest())
    ##
    ## @endcode
    ## Trifft auf Eingang @e PIN_I_DO_HASH ein Telegramm ein, wird ein hash-Code zu dem am Eingang @e PIN_I_DATA liegenden Datenblock (String) gebildet.
    ## Anschließend wird auf dem Ausgang @e PIN_O_HEXCODE der hash-Code als Hexadezimal-Zahl ausgegeben.
    ## @if OLD_CHANGELOG
    ## @chlg03 Klasse hinzugefügt
    ## @chlg15 Beispiel-Code hinzugefügt
    ## @endif
    class MD5Hash:

        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self):
            ## @cond NO_DOC
            self.__md5 = hashlib.md5()
            ## @endcond

        ## Aktualisiert den MD5-Hash mit den übergebenen Daten
        ## @param data @e string @n Datenblock
        def update(self, data):
            self.__md5.update(data)

        ## Liefert den MD5-Code zurück.
        ## @result @e string @n MD5-Code
        def digest(self):
            return self.__md5.digest()

        ## Liefert den MD5-Code als Hex-String zurück.
        ## @result @e string @n MD5-Code
        def hex_digest(self):
            return self.__md5.hexdigest()

    ## Klasse zur Erzeugung eine SHA-1-Hashwerts. Teil des Pakets zur @b Verschlüsselung.
    ## @code
    ## class Crypto_HASH(hsl20_4.BaseModule):
    ##
    ##     def __init__(self, homeserver_context):
    ##         ....
    ##         #################################################
    ##
    ##     def on_input_value(self, index, value):
    ##         if index == self.PIN_I_DO_HASH:
    ##             hash = self.FRAMEWORK.create_sha1_hash()
    ##             hash.update(self._get_input_value(self.PIN_I_DATA))
    ##             self._set_output_value(self.PIN_O_HEXCODE, hash.hex_digest())
    ##
    ## @endcode
    ## Trifft auf Eingang 2 (PIN_I_DO_HASH) ein Telegramm ein, wird ein hash-Code zu dem an Eingang 1 (PIN_I_DATA) liegenden Datenblock (String) gebildet.
    ## Anschließend wird auf Ausgang 1 (PIN_O_HEXCODE) der hash-Code als Hexadezimal-Zahl ausgegeben.
    ## @if OLD_CHANGELOG
    ## @chlg03 Klasse hinzugefügt
    ## @chlg15 Beispiel-Code hinzugefügt
    ## @endif
    class SHA1Hash:

        ## Konstruktor
        def __init__(self):
            ## @cond NO_DOC
            self._sha1 = hashlib.sha1()
            ## @endcond

        ## Aktualisiert den SHA1-Hashwert mit den übergebenen Daten.
        ## @param data @e string @n Datenblock
        def update(self, data):
            self._sha1.update(data)

        ## Liefert den SHA1-Code zurück.
        ## @result @e string @n SHA1-Code
        def digest(self):
            return self._sha1.digest()

        ## Liefert den SHA1-Code als Hex-String zurück.
        ## @result @e string @n SHA1-Code
        def hex_digest(self):
            return self._sha1.hexdigest()

    ## Klasse zur Erzeugung eine SHA-2-Hashwerts. Teil des Pakets zur @b Verschlüsselung.
    ## @code
    ## class Crypto_HASH(hsl20_4.BaseModule):
    ##
    ##     def __init__(self, homeserver_context):
    ##         ....
    ##         #################################################
    ##
    ##     def on_input_value(self, index, value):
    ##         if index == self.PIN_I_DO_HASH:
    ##             if value == 1:
    ##                 hash = self.FRAMEWORK.create_sha224_hash()
    ##             elif value == 2:
    ##                 hash = self.FRAMEWORK.create_sha256_hash()
    ##             elif value == 3:
    ##                 hash = self.FRAMEWORK.create_sha384_hash()
    ##             else:
    ##                 hash = self.FRAMEWORK.create_sha512_hash()
    ##
    ##             hash.update(self._get_input_value(self.PIN_I_DATA))
    ##             self._set_output_value(self.PIN_O_HEXCODE, hash.hex_digest())
    ##
    ## @endcode
    ## Trifft auf dem Eingang @e PIN_I_DO_HASH ein Telegramm ein, wird ein hash-Code zu dem am Eingang @e PIN_I_DATA liegenden Datenblock (String) gebildet.
    ## Abhängig vom Wert des Telegramms wird ausgewählt, welche Methode dafür verwendet wird.
    ## Anschließend wird auf Ausgang @e PIN_O_HEXCODE wird der hash-Code als Hexadezimal-Zahl ausgegeben.
    ## @if OLD_CHANGELOG
    ## @chlg03 Klasse hinzugefügt
    ## @chlg15 Beispiel-Code hinzugefügt
    ## @endif
    class SHA2Hash:

        ## Konstruktor
        ## @param size @e int @n Länge des Hashwerts. Mögliche Werte: 224, 256, 384 und 512.
        def __init__(self, size=512):
            ## @cond NO_DOC
            if size==224:
                self._sha2 = hashlib.sha224()
            elif size==256:
                self._sha2 = hashlib.sha256()
            elif size==384:
                self._sha2 = hashlib.sha384()
            elif size==512:
                self._sha2 = hashlib.sha512()
            else:
                self._sha2 = None
            ## @endcond

        ## Aktualisiert den SHA2-Hashwert mit den übergebenen Daten.
        ## @param data @e string @n Datenblock
        def update(self, data):
            if self._sha2!=None:
                self._sha2.update(data)

        ## Liefert den SHA2-Code zurück.
        ## @result @e string @n SHA2-Code
        def digest(self):
            if self._sha2!=None:
                return self._sha2.digest()
            return ''

        ## Liefert den SHA2-Code als Hex-String zurück.
        ## @result @e string @n SHA2-Code
        def hex_digest(self):
            if self._sha2!=None:
                return self._sha2.hexdigest()
            return ''


    ## Klasse zur Verschlüssung von Daten mittels AES. Teil des Pakets zur @b Verschlüsselung.
    ## @details
    ## Die Methoden @e encrypt (verschlüsseln) und @e decrypt (entschlüsseln) dienen dazu, ganze Strings zu verarbeiten.
    ## Standardmäßig wird CBC (Cipher-Block Chaining) verwendet.
    ## @code
    ## class Crypto_AES(hsl20_4.BaseModule):
    ##
    ##     def __init__(self, homeserver_context):
    ##         ....
    ##         #################################################
    ##
    ##     def on_input_value(self, index, value):
    ##         if index == self.PIN_I_DO_CRYPTO:
    ##             data   = "val_with_16_char"
    ##             vector = "abcdefghijklmnop"
    ##
    ##             aes_1 = self.FRAMEWORK.create_aes()
    ##             aes_1.init("key_with_16_char", vector)
    ##             aes_1.use_cbc()
    ##             encrypted = aes_1.encrypt(data)
    ##
    ##             aes_2 = self.FRAMEWORK.create_aes()
    ##             aes_2.init("key_with_16_char", vector)
    ##             aes_2.use_cbc()
    ##             decrypted = aes_2.decrypt(encrypted)
    ##
    ## @endcode
    ## Trifft auf dem Eingang @e PIN_I_DO_CRYPTO ein Telegramm ein, wird der Datenblock @b data mit dem statischen Vektor @b vector verschlüsselt (aes_1)
    ## und in der Variablen @b encrypted gespeichert.@n
    ## Der verschlüsselte Datenblock @b encrypted wird mit demselben Vektor anschließend wieder entschlüsselt (aes_2) und in der Variablen @b decrypted gespeichert.@n
    ## In beiden Fällen wird die Methode @b CBC verwendet.@n
    ## @note Die Datenblöcke @b decrypted und @b data sind identisch!@n Zum ver- und entschlüsseln kann @b nicht das selbe AES-Objekt verwendet werden!
    ## @if OLD_CHANGELOG
    ## @chlg03 Klasse hinzugefügt
    ## @chlg15 Beispiel-Code hinzugefügt
    ## @endif
    class AESCipher:

        ## Generiert einen 16 Zeichen langen String. Dieser kann als Init-Vektor verwendet werden.
        ## @result @e string @n Init-Vektor
        @staticmethod
        def generate_init_vector():
            iv = ''
            try:
                iv = os.urandom(16)
            except:
                pass
            if len(iv)!=16:
                iv=''
                for x in range(16):
                    i = random.randint(1,255)
                    iv=iv+chr(i)
            return iv

        ## Konstruktor
        ##
        ## @warning Diese Klasse sollte nicht direkt instanziert werden.
        def __init__(self):
            ## @cond NO_DOC
            self.__aes = None
            self.__key = None
            self.__iv = None
            self.__mode = AES.MODE_CBC
            self.__counter = None
            ## @endcond

        ## Initialisieren
        ## @param key @e string @n Schlüssel. Muss die Länge 16, 24 oder 32 haben.
        ## @param iv @e string @n Optional. Init-Vektor, muss 16 Zeichen lang sein.
        def init(self, key, iv=None):
            ## @cond NO_DOC
            self.__key = key
            self.__iv = iv
            ## @endcond

        ## Verwendet zur Verschlüsselung den Modus ECB (Electronic Code Book)
        ## @note Diese Methode benötigt keinen Init-Vektor!
        ## @if OLD_CHANGELOG
        ## @chlg12 Hinweis hinzugefügt
        ## @endif
        def use_ecb(self):
            ## @cond NO_DOC
            self.__mode = AES.MODE_ECB
            ## @endcond

        ## Verwendet zur Verschlüsselung den Modus CBC (Cipher-Block Chaining)
        def use_cbc(self):
            ## @cond NO_DOC
            self.__mode = AES.MODE_CBC
            ## @endcond

        ## Verwendet zur Verschlüsselung den Modus CFB (Cipher FeedBack)
        def use_cfb(self):
            ## @cond NO_DOC
            self.__mode = AES.MODE_CFB
            ## @endcond

        ## Verwendet zur Verschlüsselung den Modus OFB (Output FeedBack)
        def use_ofb(self):
            ## @cond NO_DOC
            self.__mode = AES.MODE_OFB
            ## @endcond

        ## Verwendet zur Verschlüsselung den Modus CTR (Counter Mode)
        def use_ctr(self):
            ## @cond NO_DOC
            self.__mode = AES.MODE_CTR
            ## @endcond

        ## @cond NO_DOC
        ## Einfacher Zähler fuer CTR
        def __ctr_counter(self):
            for i, c in enumerate(self.__counter):
                if self.__counter[i]<255:
                    self.__counter[i] = c + 1
                    break
            return self.__counter.tostring()
        ## @endcond


        ## Verschlüsselt den übergebenen Datenblock. Es muss das gesamte zu verschlüsselnde Paket übergeben werden.
        ## @note Es muss stets das komplette zu verschlüsselnde Datenpaket übergeben werden: encrypt(a)+encrypt(b) != encrypt(a+b)
        ## @note Je nach Modus muss das übergebene Datenpaket ein Vielfaches der Blockgröße sein.
        ## @param data @e string @n Datenblock
        ## @exception AttributeError
        ## - Wird ausgelöst, wenn...
        ## - ...kein Schlüssel angegeben wurde
        ## - ...der Schlüssel eine ungültige Länge besitzt
        ## - ...ein Init-Vektor angegeben wurde, der nicht 16 Zeichen lang ist
        ## @exception ValueError
        ## - Wird ausgelöst, wenn...
        ## - ...die Größe des Datenpakets nicht korrekt ist
        def encrypt(self, data):
            ## @cond NO_DOC
            #print self.__key, len(self.__key)
            if (self.__key==None) or ((len(self.__key)!=16) and (len(self.__key)!=24) and (len(self.__key)!=32)):
                raise AttributeError("invald key size")
            if self.__iv!=None and len(self.__iv)!=16:
                raise AttributeError("invalid init vector")

            if self.__mode == AES.MODE_ECB:
                aes = AES.new(self.__key, self.__mode)
            elif self.__mode == AES.MODE_CTR:
                if self.__iv==None:
                    raise AttributeError("missing init vector/counter")
                self.__counter = array.array('B', self.__iv)
                aes = AES.new(self.__key, AES.MODE_CTR, counter=self.__ctr_counter)
            else:
                aes = AES.new(self.__key, self.__mode, self.__iv)
            return aes.encrypt(data)
            ## @endcond

        ## Entschlüsselt den übergebenen Datenblock.
        ## @note Es muss stets das komplette zu verschlüsselnde Datenpaket übergeben werden: decrypt(a)+decrypt(b) != decrypt(a+b)
        ## @note Je nach Modus muss das übergebene Datenpaket ein Vielfaches der Blockgröße sein.
        ## @param data @e string @n Datenblock
        ## @exception AttributeError
        ## - Wird ausgelöst, wenn...
        ## - ...kein Schlüssel angegeben wurde
        ## - ...der Schlüssel eine ungültige Länge besitzt
        ## - ...ein Init-Vektor angegeben wurde, der nicht 16 Zeichen lang ist
        ## @exception ValueError
        ## - Wird ausgelöst, wenn...
        ## - ...die Größe des Datenpakets nicht korrekt ist
        def decrypt(self, data):
            ## @cond NO_DOC
            if (self.__key==None) or ((len(self.__key)!=16) and (len(self.__key)!=24) and (len(self.__key)!=32)):
                raise AttributeError("invald key size")
            if self.__iv!=None and len(self.__iv)!=16:
                raise AttributeError("invalid init vector")

            if self.__mode == AES.MODE_ECB:
                aes = AES.new(self.__key, self.__mode)
            elif self.__mode == AES.MODE_CTR:
                if self.__iv==None:
                    raise AttributeError("missing init vector/counter")
                self.__counter = array.array('B', self.__iv)
                aes = AES.new(self.__key, self.__mode, counter=self.__ctr_counter)
            else:
                aes = AES.new(self.__key, self.__mode, self.__iv)
            return aes.decrypt(data)
            ## @endcond
