--- /dev/null
+__pycache__/
+env
+.idea/
+test*.py
+
--- /dev/null
+Inspiration from https://github.com/barnslig/fints4fun
+
+Only FinTS 3.0 supported at the moment, only reading data and only PIN/TAN
--- /dev/null
+from .segments.crypto import FinTS3CryptoHeader, FinTS3CryptoBody
+from .segments.message import FinTS3Footer, FinTS3Header
+from .utils import segments_to_ascii
+
+
+class FinTS3:
+ version = 300
+
+ def __init__(self):
+ self.segments = []
+
+ # global segment counter; used for "sub-segments" in the crypto body
+ self.i = 1
+
+ # these segments are special and have to be controlled by this class
+ self.header = FinTS3Header(self.version)
+ self.footer = FinTS3Footer()
+
+ def append(self, segment):
+ self.segments.append(segment)
+
+ def to_ascii(self):
+ self.i, ascii = segments_to_ascii(self.segments)
+ size = len(ascii)
+
+ # footer stuff for correct size calculation
+ self.footer.set_counter(self.i + 1)
+ footer = self.footer.to_ascii() + "'"
+ size += len(footer)
+
+ # prepend header with correct size
+ self.header.set_counter(1)
+ self.header.set_size(size + 1)
+ ascii = self.header.to_ascii() + "'" + ascii
+
+ # append footer
+ ascii += footer
+
+ return ascii
+
+
+class FinTS3PinTan(FinTS3):
+ def __init__(self, customer, blz, server=None):
+ self.server = server
+
+ super().__init__()
+
+ self.crypto_header = FinTS3CryptoHeader(customer, blz)
+ self.crypto_body = FinTS3CryptoBody()
+ self.append(self.crypto_header)
+ self.append(self.crypto_body)
+
+ def append(self, segment):
+ self.segments.append(segment)
+
+ def to_ascii(self):
+ # create the crypto body
+ self.i, ascii = segments_to_ascii(self.segments, self.i)
+ self.crypto_body.set_data(ascii)
+
+ return super().to_ascii()
--- /dev/null
+from collections import OrderedDict
+
+
+class FinTS3Segment:
+ def __init__(self):
+ raise NotImplementedError()
+
+ def get_counter(self):
+ return self.elements['head']['counter']
+
+ def set_counter(self, counter):
+ self.elements['head']['counter'] = counter
+
+ # Parse/encode binary dataelements while ignoring the size
+ def __parse_binary(self, DE):
+ if DE[0:1] == '@':
+ bdata = DE.split('@')
+ return bytes(bdata[2], 'ISO-8859-2')
+ return DE
+
+ def __encode_binary(self, DE):
+ if type(DE) is bytes:
+ return str('@{0}@{1}'.format(len(DE), str(DE, 'ISO-8859-2')))
+ return str(DE)
+
+ def from_ascii(self, ascii):
+ for i, DG in enumerate(ascii.split('+')):
+ key = list(self.elements.keys())[i]
+
+ if type(self.elements[key]) is OrderedDict:
+
+ for ii, DE in enumerate(DG.split(':')):
+ kkey = list(self.elements[key].keys())[ii]
+ self.elements[key][kkey] = self.__parse_binary(DE)
+
+ else:
+ self.elements[key] = self.__parse_binary(DG)
+
+ def to_ascii(self):
+ ascii = ''
+
+ for key in self.elements.keys():
+ if type(self.elements[key]) is OrderedDict:
+ for element in self.elements[key]:
+ ascii += self.__encode_binary(self.elements[key][element]) + ':'
+ ascii = ascii[:-1]
+ else:
+ ascii += self.__encode_binary(self.elements[key])
+ ascii += '+'
+ ascii = ascii[:-1]
+
+ return ascii
--- /dev/null
+# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf
+import time
+from collections import OrderedDict
+
+from . import FinTS3Segment
+
+
+# See B.5.3
+# Currently PINTAN only
+class FinTS3CryptoHeader(FinTS3Segment):
+ def __init__(self, customer=None, blz=None):
+ self.elements = OrderedDict([
+ ('head', OrderedDict([
+ ('identifier', 'HNVSK'),
+ ('counter', 998), # See B.8
+ ('version', 3)
+ ])),
+ ('profile', OrderedDict([
+ ('mode', 'PIN'),
+ ('version', 1)
+ ])),
+ ('function', 998), # kinda magic, but this is according to the example E.3.1
+ ('role', '1'),
+ ('identification', OrderedDict([
+ ('identifier', 1),
+ ('cid', ''), # according to the documentation we can ignore this
+ ('part', 0) # according to the documentation we can ignore this
+ ])),
+ ('datetime', OrderedDict([
+ ('identifier', 1), # Sicherheitszeitstempel
+ ('date', time.strftime('%Y%m%d')), # YYYYMMDD
+ ('time', time.strftime('%H%M%S')) # HHMMSS
+ ])),
+ # p. 191
+ ('encryption', OrderedDict([
+ ('usage', 2), # Owner Symmetric (OSY)
+ ('mode', 2), # Cipher Block Chaining (CBC)
+ ('algorithm', 13), # 2-Key-Triple-DES
+ ('algparams', b'NOKEY'), # Do we care?
+ ('algkeyidentifier', 6), # Must be right, according to aqBanking!
+ ('algparamidentifier', 1) # Only possible value --> Clear text (IVC)
+ ])),
+ # p. 184
+ ('keyname', OrderedDict([
+ ('foo', 280),
+ ('bank', blz), # so-called Bankleitzahl
+ ('customer', customer), # Username, in Germany often the account number
+ ('keytype', 'V'), # Chiffrierschlüssel
+ ('keynumber', 1),
+ ('keyversion', 1)
+ ])),
+ ('compression', 0) # No compression
+ ])
+
+
+# See B.5.4
+class FinTS3CryptoBody(FinTS3Segment):
+ def __init__(self, data=None):
+ self.elements = OrderedDict([
+ ('head', OrderedDict([
+ ('identifier', 'HNVSD'),
+ ('counter', 999), # See B.8
+ ('version', 1)
+ ])),
+ ('data', b'')
+ ])
+
+ def set_data(self, data):
+ self.elements['data'] = bytes(data, 'ISO-8859-2')
+
+ def get_data(self, data):
+ return self.elements['data']
+
+# See B.5.1
+# class FinTS3SignatureHeader(FinTS3Segment):
--- /dev/null
+# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf
+from collections import OrderedDict
+
+from . import FinTS3Segment
+
+
+# See B.5.2
+class FinTS3Header(FinTS3Segment):
+ def __init__(self, version=300):
+ self.elements = OrderedDict([
+ ('head', OrderedDict([
+ ('identifier', 'HNHBK'),
+ ('counter', 0),
+ ('version', 3)
+ ])),
+ ('size', 0),
+ ('version', version),
+ ('dialog', 0),
+ ('msg', 0)
+ ])
+
+ def set_size(self, size):
+ selfSize = len(self.to_ascii()) + 1
+ self.elements['size'] = size + selfSize
+
+ def to_ascii(self):
+ self.elements['size'] = str(self.elements['size']).zfill(12)
+ return super(FinTS3Header, self).to_ascii()
+
+
+# See B.5.3
+class FinTS3Footer(FinTS3Segment):
+ def __init__(self):
+ self.elements = OrderedDict([
+ ('head', OrderedDict([
+ ('identifier', 'HNHBS'),
+ ('counter', 0),
+ ('version', 1)
+ ])),
+ ])
--- /dev/null
+def segments_to_ascii(segments, counter=1):
+ ascii = ''
+
+ for segment in segments:
+ # do only set counter if is 0; see B.8
+ if segment.get_counter() == 0:
+ counter += 1
+ segment.set_counter(counter)
+
+ s = segment.to_ascii()
+ ascii += s + "'"
+
+ return counter, ascii