Skip to content

DKB: persist() produces truncated serialized output (HICAZSv1 mid-string), unserialize fails #564

@hajoeichler

Description

@hajoeichler

Summary

For DKB (BLZ 12030000, FinTS URL https://fints.dkb.de/fints, TAN mode 940), FinTs::persist() produces a deterministically truncated PHP serialize output. The byte stream ends mid-string within an s:136:"..." declaration of an HICAZSv1 segment, with no closing quote, no semicolon, no closing braces. Calling unserialize() on the result fails:

Error at offset 877 of 923 bytes in vendor/nemiah/php-fints/src/FinTs.php line 213

Reproducibility: 100% deterministic — same DKB account, multiple successful interactive runs, every captured persistence blob is bit-identical (sha256 the same, length 1231 base64 chars, decoded 923 bytes ending at the same offset).

Repro

  1. Connect to DKB with a fresh FinTs (empty $persistedInstance).
  2. Login with TAN mode 940 + pushTAN approve.
  3. Call $fin_ts->persist() after successful login.

The returned string, when base64-decoded, ends:

...{i:0;s:136:"HICAZS:16:1:3+1+1+0+450:N:N:urn?:iso?:std
                                                       ^-- stream ends here, mid-string

s:136 declares a 136-byte string but only the first 40 bytes are emitted; no "; terminator, no closing braces for the parent Fhp\Segment\CAZ\HICAZSv1, Fhp\Protocol\BPD, or the outer a:9:{...}.

What I ruled out (in isolation)

  • serialize() of a fresh HICAZSv1 populated by Parser::parseSegment(<wire>, $seg) and then re-emitted via Serializer::serializeSegment($seg) round-trips correctly (137-byte input → 137-byte output → 186-byte PHP-serialize → unserialize OK).
  • Wrapping such a segment inside a synthetic BPD->parameters = ["HICAZS" => [1 => $seg]] and PHP-serializing the BPD also round-trips cleanly (359 bytes, ends with }, unserialize OK).
  • So the bug needs the production-time FinTs state assembled by a real DKB dialog. Reproducing only via the synthetic path didn't surface it.

Environment

  • nemiah/phpFinTS commit 4800d6a (HEAD of master, 2026-05-23)
  • PHP 8.4.19 inside the bnw/firefly-iii-fints-importer Docker image (php -S server)
  • Linux/arm64 (Raspberry Pi 5, 16 GB), rootless Docker

Why this matters

It defeats the persistence model: any caller that wants to reuse a 90-day SCA-exempt session has to retry the login (and consume a fresh TAN challenge) every time, because the saved blob is unloadable. For DKB specifically, this combines badly with their per-day TAN-challenge quota — failed cron jobs quickly trigger error 9040 "Authorization method is locked because too many challenges were requested," sometimes severe enough to invalidate the mobile-app session too.

Happy to help bisect

If you have a hypothesis for which segment property / element is producing the silent truncation, I can instrument my local copy and re-run against my DKB account (carefully — one attempt per day at most). Or if you can point me at the test harness pattern you'd use to replay a captured BPD response, I can capture mine and produce a synthetic repro.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions