Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/tans.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@ a file:
writer.write(result.challenge_matrix[1])
writer.close()

Some banks, like Comdirect, may provide the challenge within the ``challenge_hhduc`` attribute. We provide a helper function to decode the challenge. Pass the ``challenge_hhduc`` value to this method:

.. autofunction:: fints.hhd.utils.decode_phototan_image

This returns a dictionary with a ``mime_type`` and an ``image`` field. The ``image`` field contains the binary data
of the image itself and can e.g. be written to a file

.. code-block:: python
from fints.utils import decode_phototan_image

data = decode_phototan_image(challenge_hhduc)
writer = open("tan.png", "wb")
writer.write(data["image"])
writer.close()

Sending the TAN
---------------

Expand Down
2 changes: 1 addition & 1 deletion docs/tested.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Bank Transactions Holdings Transfer Debits
Postbank Yes
BBBank eG Yes Yes
Sparkasse Heidelberg Yes
comdirect Yes
comdirect Yes Yes
======================================== ============ ======== ======== ======

Tested security functions
Expand Down
31 changes: 31 additions & 0 deletions fints/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,34 @@ def changed(reduced=False):
except ImportError:
def doc_enum(an_enum: EnumType) -> EnumType:
return an_enum


def decode_phototan_image(data):
"""
This decodes photoTAN data sent as challenge_hhduc into its mime type and the actual image data.

:returns: a dictionary with two values, 'mime_type' and 'image'
:rtype: dict

The markup of the data is taken from https://github.com/hbci4j/hbci4java/blob/c8eabe6809e8d0271f944ea28a59ed6b736af56e/src/main/java/org/kapott/hbci/manager/MatrixCode.java#L61-L97
The encoding is taken from https://github.com/hbci4j/hbci4java/blob/c8eabe6809e8d0271f944ea28a59ed6b736af56e/src/main/java/org/kapott/hbci/comm/Comm.java#L46
"""
# Mime type length is the first two bytes of data
mime_type_length = int.from_bytes(data[:2], byteorder='big')

# The mime type follows from byte three to (mime_type_length - 1)
mime_type = data[2:2 + mime_type_length].decode("iso-8859-1")

# The image length is coded in the next two bytes
image_length_start = 2 + mime_type_length
image_length = int.from_bytes(data[image_length_start:2 + image_length_start], byteorder='big')

# The actual image data is everything that follows.
# To be compatible with possible future extensions, we still slice the data
image = data[2 + image_length_start: 2 + image_length_start + image_length]

return {
"mime_type": mime_type,
"image": image
}

27 changes: 27 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest
from fints.utils import decode_phototan_image


# HITAN3:
# 'challenge' contains a HHD 1.3 code embedded in the normal text payload
# field.
# Example: 'CHLGUC 00312908881344731012345678900515,00CHLGTEXT0292Sie haben eine ...'
# The code in NeedTANResponse._parse_tan_challenge extracts
# '2908881344731012345678900515,00'
# from this, as a version 1.3 code. parse() should accept it
# (start code: '88134473', IBAN: '1234567890', amount: '15,00')

# HITAN6:
# 'challenge' contains 4 fields:
# 2 bytes: mime type length
# x bytes (see above): mime type
# 2 bytes: data length
# y bytes: image data

CHALLENGE = b'\x00\timage/png\x0e\x82\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc8\x00\x00\x00\xc8\x08\x06\x00\x00\x00\xadX\xae\x9e\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02MIDATx\x9c\xed\xdd\xb1\r\xc40\x0c\x04A\xea\xe1\xfe[\xf6w\xb0\x89\x023\x98\xa9@ \xb0Pxgf\xdeY\xec}W?o\xce9_?!\xb9\xdf\x9d\xdf\xd7\x0f\x80\xcd\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x1e;\xdaw\xdc\xef\xce\xf6\xfb\xf9A \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x9c\x99Y=T\xbd}G\xdb\x0e\xf9\x9d\xed\xf7\xf3\x83@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@x\xech\xdfq\xbf;\xdb\xef\xe7\x07\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81pff\xf5P\xf5\xf6\x1dm;\xe4w\xb6\xdf\xcf\x0f\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02\xe1\x0fp-(\x89A#\xd8\xc6\x00\x00\x00\x00IEND\xaeB`\x82'

def test_decode_phototan_image():
data = decode_phototan_image(CHALLENGE)

assert data["mime_type"] == "image/png"
assert data["image"] == b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xc8\x00\x00\x00\xc8\x08\x06\x00\x00\x00\xadX\xae\x9e\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x02MIDATx\x9c\xed\xdd\xb1\r\xc40\x0c\x04A\xea\xe1\xfe[\xf6w\xb0\x89\x023\x98\xa9@ \xb0Pxgf\xdeY\xec}W?o\xce9_?!\xb9\xdf\x9d\xdf\xd7\x0f\x80\xcd\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x1e;\xdaw\xdc\xef\xce\xf6\xfb\xf9A \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x08\x04\x82@ \x9c\x99Y=T\xbd}G\xdb\x0e\xf9\x9d\xed\xf7\xf3\x83@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@\x10\x08\x04\x81@x\xech\xdfq\xbf;\xdb\xef\xe7\x07\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81 \x10\x08\x02\x81pff\xf5P\xf5\xf6\x1dm;\xe4w\xb6\xdf\xcf\x0f\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02A \x10\x04\x02\xe1\x0fp-(\x89A#\xd8\xc6\x00\x00\x00\x00IEND\xaeB`\x82'