]> git.ipfire.org Git - thirdparty/python-fints.git/commitdiff
Add Comdirect (#194) master
authorrhn <gihu.rhn@porcupinefactory.org>
Tue, 2 Sep 2025 12:11:36 +0000 (14:11 +0200)
committerGitHub <noreply@github.com>
Tue, 2 Sep 2025 12:11:36 +0000 (14:11 +0200)
* add util function to decode photoTAN images

* Add comdirect to tested banks

---------

Co-authored-by: Morre <morre@mor.re>
docs/tans.rst
docs/tested.rst
fints/utils.py
tests/test_utils.py [new file with mode: 0644]

index 432bac889cb02ef6e5d93ca05d988a6ffbd18001..da3e34ab4013c527d650bf3f8b448ac75280d461 100644 (file)
@@ -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
 ---------------
 
index 4b695d26c493478d498119d73e7630d9a743433d..6ca09cea196435cf86b460628f367fc4da6c68a2 100644 (file)
@@ -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
index c071c1e14efb43d7751422ae3a1c4c6f23c98cb9..00f695a0556dbf8ad8e3cc19325d7a5e0867992c 100644 (file)
@@ -351,3 +351,34 @@ try:
 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
+    }
+
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644 (file)
index 0000000..01fe2bd
--- /dev/null
@@ -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'