fints/formals.py
class KeyName(DataElementGroup):
    bank_identifier = DataElementGroupField(type=BankIdentifier)
    user_id = DataElementField(type='id')
    key_type = CodeField(KeyType, length=1, _d="Schlüsselart")
    key_number = DataElementField(type='num', max_length=3)
    key_version = DataElementField(type='num', max_length=3)
fints/formals.py
class SecurityDateTime(DataElementGroup):
    date_time_type = CodeField(DateTimeType, max_length=3)
    date = DataElementField(type='dat', required=False)
    time = DataElementField(type='tim', required=False)
fints/message.py
class FinTSMessage(SegmentSequence):
    DIRECTION = None
    # Auto-Numbering, dialog relation, security base

    def __init__(self, dialog=None, *args, **kwargs):
        self.dialog = dialog
        self.next_segment_number = 1
        super().__init__(*args, **kwargs)

    def __iadd__(self, segment: FinTS3Segment):
        if not isinstance(segment, FinTS3Segment):
            raise TypeError("Can only append FinTS3Segment instances, not {!r}".format(segment))
        segment.header.number = self.next_segment_number
        self.next_segment_number += 1
        self.segments.append(segment)
        return self

    def response_segments(self, ref, *args, **kwargs):
        for segment in self.find_segments(*args, **kwargs):
            if segment.header.reference == ref.header.number:
                yield segment

    def responses(self, ref, code=None):
        for segment in self.response_segments(ref, HIRMS2):
            for response in segment.responses:
                if code is None or response.code == code:
                    yield response
fints/formals.py
class OperationMode(RepresentableEnum):
    CBC = '2'  #: Cipher Block Chaining
    ISO_9796_1 = '16'  #: ISO 9796-1 (bei RDH)
    ISO_9796_2_RANDOM = '17'  #: ISO 9796-2 mit Zufallszahl (bei RDH)
    PKCS1V15 = '18'  #: RSASSA-PKCS#1 V1.5 (bei RDH); RSAES-PKCS#1 V1.5 (bei RAH, RDH)
    PSS = '19'  #: RSASSA-PSS (bei RAH, RDH)
    ZZZ = '999'  #: Gegenseitig vereinbart (DDV: Retail-MAC)
fints/formals.py
class AlgorithmParameterIVName(RepresentableEnum):
    IVC = '1' #: Initialization value, clear text
fints/formals.py
class AlgorithmParameterName(RepresentableEnum):
    KYE = '5' #: Symmetrischer Schlüssel, verschlüsselt mit symmetrischem Schlüssel
    KYP = '6' #: Symmetrischer Schlüssel, verschlüsselt mit öffentlichem Schlüssel
fints/types.py
class SegmentSequence:
    """A sequence of FinTS3Segment objects"""

    def __init__(self, segments=None):
        if isinstance(segments, bytes):
            from .parser import FinTS3Parser
            parser = FinTS3Parser()
            data = parser.explode_segments(segments)
            segments = [parser.parse_segment(segment) for segment in data]
        self.segments = list(segments) if segments else []

    def render_bytes(self) -> bytes:
        from .parser import FinTS3Serializer
        return FinTS3Serializer().serialize_message(self)

    def __repr__(self):
        return "{}.{}({!r})".format(self.__class__.__module__, self.__class__.__name__, self.segments)

    def print_nested(self, stream=None, level=0, indent="    ", prefix="", first_level_indent=True, trailer="", print_doc=True, first_line_suffix=""):
        import sys
        stream = stream or sys.stdout
        stream.write(
            ((prefix + level * indent) if first_level_indent else "")
            + "{}.{}([".format(self.__class__.__module__, self.__class__.__name__)
            + first_line_suffix
            + "\n"
        )
        for segment in self.segments:
            docstring = print_doc and segment.__doc__
            if docstring:
                docstring = docstring.splitlines()[0].strip()
            if docstring:
                docstring = " # {}".format(docstring)
            else:
                docstring = ""
            segment.print_nested(stream=stream, level=level + 1, indent=indent, prefix=prefix, first_level_indent=True, trailer=",", print_doc=print_doc,
                                 first_line_suffix=docstring)
        stream.write((prefix + level * indent) + "]){}\n".format(trailer))

    def find_segments(self, query=None, version=None, callback=None, recurse=True, throw=False):
        """Yields an iterable of all matching segments.

        :param query: Either a str or class specifying a segment type (such as 'HNHBK', or :class:`~fints.segments.message.HNHBK3`), or a list or tuple of strings or classes.
                     If a list/tuple is specified, segments returning any matching type will be returned.
        :param version: Either an int specifying a segment version, or a list or tuple of ints.
                        If a list/tuple is specified, segments returning any matching version will be returned.
        :param callback: A callable that will be given the segment as its sole argument and must return a boolean indicating whether to return this segment.
        :param recurse: If True (the default), recurse into SegmentSequenceField values, otherwise only look at segments in this SegmentSequence.
        :param throw: If True, a FinTSNoResponseError is thrown if no result is found. Defaults to False.

        The match results of all given parameters will be AND-combined.
        """
        found_something = False

        if query is None:
            query = []
        elif isinstance(query, str) or not isinstance(query, (list, tuple, Iterable)):
            query = [query]

        if version is None:
            version = []
        elif not isinstance(version, (list, tuple, Iterable)):
            version = [version]

        if callback is None:
            callback = lambda s: True

        for s in self.segments:
            if ((not query) or any((isinstance(s, t) if isinstance(t, type) else s.header.type == t) for t in query)) and \
                    ((not version) or any(s.header.version == v for v in version)) and \
                    callback(s):
                yield s
                found_something = True

            if recurse:
                for name, field in s._fields.items():
                    val = getattr(s, name)
                    if val and hasattr(val, 'find_segments'):
                        for v in val.find_segments(query=query, version=version, callback=callback, recurse=recurse):
                            yield v
                            found_something = True

        if throw and not found_something:
            raise FinTSNoResponseError(
                'The bank\'s response did not contain a response to your request, please inspect debug log.'
            )

    def find_segment_first(self, *args, **kwargs):
        """Finds the first matching segment.

        Same parameters as find_segments(), but only returns the first match, or None if no match is found."""

        for m in self.find_segments(*args, **kwargs):
            return m

        return None

    def find_segment_highest_version(self, query=None, version=None, callback=None, recurse=True, default=None):
        """Finds the highest matching segment.

        Same parameters as find_segments(), but returns the match with the highest version, or default if no match is found."""
        # FIXME Test

        retval = None

        for s in self.find_segments(query=query, version=version, callback=callback, recurse=recurse):
            if not retval or s.header.version > retval.header.version:
                retval = s

        if retval is None:
            return default

        return retval
fints/formals.py
class SecurityProfile(DataElementGroup):
    """Sicherheitsprofil"""
    security_method = CodeField(enum=SecurityMethod, length=3, _d="Sicherheitsverfahren")
    security_method_version = DataElementField(type='num', _d="Version des Sicherheitsverfahrens")
fints/formals.py
class SecurityMethod(RepresentableEnum):
    DDV = 'DDV'
    RAH = 'RAH'
    RDH = 'RDH'
    PIN = 'PIN'
fints/segments/message.py
class HNSHA2(FinTS3Segment):
    """Signaturabschluss, version 2

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
    validation_result = DataElementField(type='bin', max_length=512, required=False, _d="Validierungsresultat")
    user_defined_signature = DataElementGroupField(type=UserDefinedSignature, required=False, _d="Benutzerdefinierte Signatur")
fints/formals.py
class KeyType(RepresentableEnum):
    """Schlüsselart"""
    D = 'D'  #: Schlüssel zur Erzeugung digitaler Signaturen
    S = 'S'  #: Signierschlüssel
    V = 'V'  #: Chiffrierschlüssel
fints/formals.py
class CompressionFunction(RepresentableEnum):
    """Komprimierungsfunktion, version 2

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    NULL = '0'  #: Keine Kompression
    LZW = '1'  #: Lempel, Ziv, Welch
    COM = '2'  #: Optimized LZW
    LZSS = '3'  #: Lempel, Ziv
    LZHuf = '4'  #: LZ + Huffman Coding
    ZIP = '5'  #: PKZIP
    GZIP = '6'  #: deflate (http://www.gzip.org/zlib)
    BZIP2 = '7'  #: bzip2 (http://sourceware.cygnus.com/bzip2/)
    ZZZ = '999'  #: Gegenseitig vereinbart
fints/formals.py
class SecurityIdentificationDetails(DataElementGroup):
    identified_role = CodeField(IdentifiedRole, max_length=3)
    cid = DataElementField(type='bin', max_length=256)
    identifier = DataElementField(type='id')
fints/segments/message.py
class HNSHK4(FinTS3Segment):
    """Signaturkopf, version 4

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
    security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
    security_reference = DataElementField(type='an', max_length=14, _d="Sicherheitskontrollreferenz")
    security_application_area = CodeField(SecurityApplicationArea, max_length=3, _d="Bereich der Sicherheitsapplikation, kodiert")
    security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
    security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
    security_reference_number = DataElementField(type='num', max_length=16, _d="Sicherheitsreferenznummer")
    security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
    hash_algorithm = DataElementGroupField(type=HashAlgorithm, _d="Hashalgorithmus")
    signature_algorithm = DataElementGroupField(type=SignatureAlgorithm, _d="Signaturalgorithmus")
    key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
    certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
fints/formals.py
class SecurityApplicationArea(RepresentableEnum):
    """Bereich der Sicherheitsapplikation, kodiert, version 2

    Informationen darüber, welche Daten vom kryptographischen Prozess verarbeitet werden.

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    SHM = '1'  #: Signaturkopf und HBCI-Nutzdaten
    SHT = '2'  #: Von Signaturkopf bis Signaturabschluss
fints/formals.py
class IdentifiedRole(RepresentableEnum):
    MS = '1' #: Message Sender
    MR = '2' #: Message Receiver
fints/segments/message.py
class HNVSD1(FinTS3Segment):
    """Verschlüsselte Daten, version 1

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    data = SegmentSequenceField(_d="Daten, verschlüsselt")
fints/formals.py
class EncryptionAlgorithmCoded(RepresentableEnum):
    TWOKEY3DES = '13' #: 2-Key-Triple-DES
    AES256 = '14' #: AES-256
fints/formals.py
class UserDefinedSignature(DataElementGroup):
    pin = PasswordField(max_length=99)
    tan = DataElementField(type='an', max_length=99, required=False)
fints/formals.py
class UsageEncryption(RepresentableEnum):
    OSY = '2'  #: Owner Symmetric
fints/formals.py
class HashAlgorithm(DataElementGroup):
    usage_hash = DataElementField(type='code', max_length=3)
    hash_algorithm = DataElementField(type='code', max_length=3)
    algorithm_parameter_name = DataElementField(type='code', max_length=3)
    algorithm_parameter_value = DataElementField(type='bin', max_length=512, required=False)
fints/exceptions.py
class FinTSError(Exception):
    pass
fints/formals.py
class EncryptionAlgorithm(DataElementGroup):
    usage_encryption = CodeField(UsageEncryption, max_length=3)
    operation_mode = CodeField(OperationMode, max_length=3)
    encryption_algorithm = CodeField(EncryptionAlgorithmCoded, max_length=3)
    algorithm_parameter_value = DataElementField(type='bin', max_length=512)
    algorithm_parameter_name = CodeField(AlgorithmParameterName, max_length=3)
    algorithm_parameter_iv_name = CodeField(AlgorithmParameterIVName, max_length=3)
    algorithm_parameter_iv_value = DataElementField(type='bin', max_length=512, required=False)
fints/formals.py
class SignatureAlgorithm(DataElementGroup):
    usage_signature = DataElementField(type='code', max_length=3)
    signature_algorithm = DataElementField(type='code', max_length=3)
    operation_mode = DataElementField(type='code', max_length=3)
fints/formals.py
class SecurityRole(RepresentableEnum):
    """Rolle des Sicherheitslieferanten, kodiert, version 2

    Kodierte Information über das Verhältnis desjenigen, der bezüglich der zu si-chernden Nachricht die Sicherheit gewährleistet.
    Die Wahl ist von der bankfachlichen Auslegung der Signatur, respektive vom vertraglichen Zustand zwischen Kunde und Kreditinstitut abhängig.

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    ISS = '1'  #: Erfasser, Erstsignatur
    CON = '3'  #: Unterstützer, Zweitsignatur
    WIT = '4'  #: Zeuge/Übermittler, nicht Erfasser
fints/segments/message.py
class HNVSK3(FinTS3Segment):
    """Verschlüsselungskopf, version 3

    Source: FinTS Financial Transaction Services, Sicherheitsverfahren HBCI"""
    security_profile = DataElementGroupField(type=SecurityProfile, _d="Sicherheitsprofil")
    security_function = DataElementField(type='code', max_length=3, _d="Sicherheitsfunktion, kodiert")
    security_role = CodeField(SecurityRole, max_length=3, _d="Rolle des Sicherheitslieferanten, kodiert")
    security_identification_details = DataElementGroupField(type=SecurityIdentificationDetails, _d="Sicherheitsidentifikation, Details")
    security_datetime = DataElementGroupField(type=SecurityDateTime, _d="Sicherheitsdatum und -uhrzeit")
    encryption_algorithm = DataElementGroupField(type=EncryptionAlgorithm, _d="Verschlüsselungsalgorithmus")
    key_name = DataElementGroupField(type=KeyName, _d="Schlüsselname")
    compression_function = CodeField(CompressionFunction, max_length=3, _d="Komprimierungsfunktion")
    certificate = DataElementGroupField(type=Certificate, required=False, _d="Zertifikat")
fints/formals.py
class DateTimeType(RepresentableEnum):
    STS = '1'  #: Sicherheitszeitstempel
    CRT = '6'  #: Certificate Revocation Time


Question: import datetime
import random
from fints.exceptions import FinTSError
from .formals import (
    AlgorithmParameterIVName, AlgorithmParameterName, CompressionFunction,
    DateTimeType, EncryptionAlgorithm, EncryptionAlgorithmCoded,
    HashAlgorithm, IdentifiedRole, KeyName, KeyType, OperationMode,
    SecurityApplicationArea, SecurityDateTime,
    SecurityIdentificationDetails, SecurityMethod, SecurityProfile,
    SecurityRole, SignatureAlgorithm, UsageEncryption, UserDefinedSignature,
)
from .message import FinTSMessage
from .segments.message import HNSHA2, HNSHK4, HNVSD1, HNVSK3
from .types import SegmentSequence



class EncryptionMechanism:
    def encrypt(self, message: FinTSMessage):
        raise NotImplemented()

    def decrypt(self, message: FinTSMessage):
        raise NotImplemented()


class AuthenticationMechanism:
    def sign_prepare(self, message: FinTSMessage):
        raise NotImplemented()
    
    def sign_commit(self, message: FinTSMessage):
        raise NotImplemented()
    
    def verify(self, message: FinTSMessage):
        raise NotImplemented()


class PinTanDummyEncryptionMechanism(EncryptionMechanism):
    def __init__(self, security_method_version=1):
        super().__init__()
        self.security_method_version = security_method_version

    def encrypt(self, message: FinTSMessage):
        assert message.segments[0].header.type == 'HNHBK'
        assert message.segments[-1].header.type == 'HNHBS'

        plain_segments = message.segments[1:-1]
        del message.segments[1:-1]

        _now = datetime.datetime.now()

        message.segments.insert(
            1,
            HNVSK3(
                security_profile=SecurityProfile(SecurityMethod.PIN, self.security_method_version),
                security_function='998',
                security_role=SecurityRole.ISS,
                security_identification_details=SecurityIdentificationDetails(
                    IdentifiedRole.MS,
                    identifier=message.dialog.client.system_id,
                ),
                security_datetime=SecurityDateTime(
                    DateTimeType.STS,
                    _now.date(),
                    _now.time(),
                ),
                encryption_algorithm=EncryptionAlgorithm(

Answer:                     UsageEncryption.OSY,