Skip to content

Commit 526678e

Browse files
committed
Take inspiration from fints4fun
0 parents  commit 526678e

9 files changed

Lines changed: 249 additions & 0 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__pycache__/
2+
env
3+
.idea/
4+
test*.py
5+

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Inspiration from https://github.com/barnslig/fints4fun
2+
3+
Only FinTS 3.0 supported at the moment, only reading data and only PIN/TAN

fints/__init__.py

Whitespace-only changes.

fints/protocol/__init__.py

Whitespace-only changes.

fints/protocol/fints3/__init__.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from .segments.crypto import FinTS3CryptoHeader, FinTS3CryptoBody
2+
from .segments.message import FinTS3Footer, FinTS3Header
3+
from .utils import segments_to_ascii
4+
5+
6+
class FinTS3:
7+
version = 300
8+
9+
def __init__(self):
10+
self.segments = []
11+
12+
# global segment counter; used for "sub-segments" in the crypto body
13+
self.i = 1
14+
15+
# these segments are special and have to be controlled by this class
16+
self.header = FinTS3Header(self.version)
17+
self.footer = FinTS3Footer()
18+
19+
def append(self, segment):
20+
self.segments.append(segment)
21+
22+
def to_ascii(self):
23+
self.i, ascii = segments_to_ascii(self.segments)
24+
size = len(ascii)
25+
26+
# footer stuff for correct size calculation
27+
self.footer.set_counter(self.i + 1)
28+
footer = self.footer.to_ascii() + "'"
29+
size += len(footer)
30+
31+
# prepend header with correct size
32+
self.header.set_counter(1)
33+
self.header.set_size(size + 1)
34+
ascii = self.header.to_ascii() + "'" + ascii
35+
36+
# append footer
37+
ascii += footer
38+
39+
return ascii
40+
41+
42+
class FinTS3PinTan(FinTS3):
43+
def __init__(self, customer, blz, server=None):
44+
self.server = server
45+
46+
super().__init__()
47+
48+
self.crypto_header = FinTS3CryptoHeader(customer, blz)
49+
self.crypto_body = FinTS3CryptoBody()
50+
self.append(self.crypto_header)
51+
self.append(self.crypto_body)
52+
53+
def append(self, segment):
54+
self.segments.append(segment)
55+
56+
def to_ascii(self):
57+
# create the crypto body
58+
self.i, ascii = segments_to_ascii(self.segments, self.i)
59+
self.crypto_body.set_data(ascii)
60+
61+
return super().to_ascii()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from collections import OrderedDict
2+
3+
4+
class FinTS3Segment:
5+
def __init__(self):
6+
raise NotImplementedError()
7+
8+
def get_counter(self):
9+
return self.elements['head']['counter']
10+
11+
def set_counter(self, counter):
12+
self.elements['head']['counter'] = counter
13+
14+
# Parse/encode binary dataelements while ignoring the size
15+
def __parse_binary(self, DE):
16+
if DE[0:1] == '@':
17+
bdata = DE.split('@')
18+
return bytes(bdata[2], 'ISO-8859-2')
19+
return DE
20+
21+
def __encode_binary(self, DE):
22+
if type(DE) is bytes:
23+
return str('@{0}@{1}'.format(len(DE), str(DE, 'ISO-8859-2')))
24+
return str(DE)
25+
26+
def from_ascii(self, ascii):
27+
for i, DG in enumerate(ascii.split('+')):
28+
key = list(self.elements.keys())[i]
29+
30+
if type(self.elements[key]) is OrderedDict:
31+
32+
for ii, DE in enumerate(DG.split(':')):
33+
kkey = list(self.elements[key].keys())[ii]
34+
self.elements[key][kkey] = self.__parse_binary(DE)
35+
36+
else:
37+
self.elements[key] = self.__parse_binary(DG)
38+
39+
def to_ascii(self):
40+
ascii = ''
41+
42+
for key in self.elements.keys():
43+
if type(self.elements[key]) is OrderedDict:
44+
for element in self.elements[key]:
45+
ascii += self.__encode_binary(self.elements[key][element]) + ':'
46+
ascii = ascii[:-1]
47+
else:
48+
ascii += self.__encode_binary(self.elements[key])
49+
ascii += '+'
50+
ascii = ascii[:-1]
51+
52+
return ascii
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf
2+
import time
3+
from collections import OrderedDict
4+
5+
from . import FinTS3Segment
6+
7+
8+
# See B.5.3
9+
# Currently PINTAN only
10+
class FinTS3CryptoHeader(FinTS3Segment):
11+
def __init__(self, customer=None, blz=None):
12+
self.elements = OrderedDict([
13+
('head', OrderedDict([
14+
('identifier', 'HNVSK'),
15+
('counter', 998), # See B.8
16+
('version', 3)
17+
])),
18+
('profile', OrderedDict([
19+
('mode', 'PIN'),
20+
('version', 1)
21+
])),
22+
('function', 998), # kinda magic, but this is according to the example E.3.1
23+
('role', '1'),
24+
('identification', OrderedDict([
25+
('identifier', 1),
26+
('cid', ''), # according to the documentation we can ignore this
27+
('part', 0) # according to the documentation we can ignore this
28+
])),
29+
('datetime', OrderedDict([
30+
('identifier', 1), # Sicherheitszeitstempel
31+
('date', time.strftime('%Y%m%d')), # YYYYMMDD
32+
('time', time.strftime('%H%M%S')) # HHMMSS
33+
])),
34+
# p. 191
35+
('encryption', OrderedDict([
36+
('usage', 2), # Owner Symmetric (OSY)
37+
('mode', 2), # Cipher Block Chaining (CBC)
38+
('algorithm', 13), # 2-Key-Triple-DES
39+
('algparams', b'NOKEY'), # Do we care?
40+
('algkeyidentifier', 6), # Must be right, according to aqBanking!
41+
('algparamidentifier', 1) # Only possible value --> Clear text (IVC)
42+
])),
43+
# p. 184
44+
('keyname', OrderedDict([
45+
('foo', 280),
46+
('bank', blz), # so-called Bankleitzahl
47+
('customer', customer), # Username, in Germany often the account number
48+
('keytype', 'V'), # Chiffrierschlüssel
49+
('keynumber', 1),
50+
('keyversion', 1)
51+
])),
52+
('compression', 0) # No compression
53+
])
54+
55+
56+
# See B.5.4
57+
class FinTS3CryptoBody(FinTS3Segment):
58+
def __init__(self, data=None):
59+
self.elements = OrderedDict([
60+
('head', OrderedDict([
61+
('identifier', 'HNVSD'),
62+
('counter', 999), # See B.8
63+
('version', 1)
64+
])),
65+
('data', b'')
66+
])
67+
68+
def set_data(self, data):
69+
self.elements['data'] = bytes(data, 'ISO-8859-2')
70+
71+
def get_data(self, data):
72+
return self.elements['data']
73+
74+
# See B.5.1
75+
# class FinTS3SignatureHeader(FinTS3Segment):
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# See FinTS_3.0_Security_Sicherheitsverfahren_HBCI_Rel_20130718_final_version.pdf
2+
from collections import OrderedDict
3+
4+
from . import FinTS3Segment
5+
6+
7+
# See B.5.2
8+
class FinTS3Header(FinTS3Segment):
9+
def __init__(self, version=300):
10+
self.elements = OrderedDict([
11+
('head', OrderedDict([
12+
('identifier', 'HNHBK'),
13+
('counter', 0),
14+
('version', 3)
15+
])),
16+
('size', 0),
17+
('version', version),
18+
('dialog', 0),
19+
('msg', 0)
20+
])
21+
22+
def set_size(self, size):
23+
selfSize = len(self.to_ascii()) + 1
24+
self.elements['size'] = size + selfSize
25+
26+
def to_ascii(self):
27+
self.elements['size'] = str(self.elements['size']).zfill(12)
28+
return super(FinTS3Header, self).to_ascii()
29+
30+
31+
# See B.5.3
32+
class FinTS3Footer(FinTS3Segment):
33+
def __init__(self):
34+
self.elements = OrderedDict([
35+
('head', OrderedDict([
36+
('identifier', 'HNHBS'),
37+
('counter', 0),
38+
('version', 1)
39+
])),
40+
])

fints/protocol/fints3/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def segments_to_ascii(segments, counter=1):
2+
ascii = ''
3+
4+
for segment in segments:
5+
# do only set counter if is 0; see B.8
6+
if segment.get_counter() == 0:
7+
counter += 1
8+
segment.set_counter(counter)
9+
10+
s = segment.to_ascii()
11+
ascii += s + "'"
12+
13+
return counter, ascii

0 commit comments

Comments
 (0)