Skip to content

KDBX 4.1 databases are written back labelled as KDBX 4.0 #114

Description

@jorabin

What happens

KeePassJava2 reads KDBX 3.1, 4 and 4.1, but only ever writes a file version of 0x00030001 (KDBX 3.1) or 0x00040000 (KDBX 4.0). There is no 4.1 constant:

// KdbxSerializer (2.x kdbx module; 3.1.0 kdbx-io, org.linguafranca.pwdb.format)
ledos.writeInt(kdbxHeader.getVersion() == 3 ? FILE_VERSION_32 : FILE_VERSION_4);
  • On read only the major version is kept (kdbxHeader.setVersion(fullVersion >> 16)), so a 4.1 file is treated as "4".
  • A 4.1 database written back is labelled 4.0, but the Jackson model still holds and writes its 4.1 content (group Tags and PreviousParentGroup; entry PreviousParentGroup and QualityCheck), so the file says 4.0 and contains 4.1 elements.
  • A new database saved with save/write(credentials, stream) is written as 4.0 (JacksonDatabase/KdbxDatabase default to new KdbxHeader(4)), while new KdbxHeader() and new KdbxStreamFormat() default to 3.1.

Affects 2.2.6 and 3.1.0 (same serializer code).

Fix

To be fixed in 3.1.1. v3-develop (3.2.0-SNAPSHOT) already has a fix to port: the KDBX minor version is kept on read and written back; a new KDBX 4 database is written as 4.1 only if it uses 4.1 features, otherwise 4.0, as KeePass does (KdbxHeader.isMinorVersionAutomatic(), KdbxHeaderOpts.V4_1_AES_ARGON_CHA_CHA); and content a version can't hold is left out of the file, kept in the database and reported through System.Logger. Whether 2.x also gets the fix is to be decided.

Documentation to update once fixed

The readmes are inaccurate about this now and are left as they are until the fix:

  • v3-master readme, Features: "Read and write KeePass 2.x format (KDBX file formats V3.1, V4 and V4.1)" overstates 3.1.0, which doesn't write 4.1. After the fix: "Read and write the KeePass 2.x format: KDBX 3.1, 4 and 4.1".
  • master readme, Features: "Read and write KeePass 2.x format (KDBX file formats V3 and V4)" doesn't say 4.1 is read; it should say what 2.x reads and writes (depending on whether 2.x gets the fix).
  • The GitHub repository description ("Read/Write 2.x (File versions 3 and 4), Read 1.x").

At the same time, use one way of naming versions throughout, since three numberings get mixed up: KeePass 1.x / 2.x for the application, KDB and KDBX 3.1 / 4 / 4.1 for file formats (no "V4", "format version 4"), and KeePassJava2 2.x / 3.x for this library (not "V2 code"/"V3"). Places to change:

  • v3-master readme: line 48 ("Upgrade to V3 requires minor changes to V2 code"), line 57 ("File format version 4 support"), lines 175–176 ("the KeePass V2 KDB format": KDB is the KeePass 1.x format, file version 3, e.g. 0x00030004; also mention the experimental, unpublished basic), line 299 ("KeePass KDB format" → "the KeePass 1.x KDB format (read only)"), and a short "Versions and formats" note near the top stating the convention.
  • master readme: lines 21 and 23 (as above), line 149 ("KDB, KDBX 3.1 and KDBX 4 (KeePass 2)" reads as if KDB were KeePass 2).

Activity

  1. added and removed on Oct 7, 2026
  2. jorabin commented on Oct 10, 2026

    @jorabin
    OwnerAuthor

    Also for 3.1.2:

    • Readme: say that the formats supported are KDBX (3.1, 4 and 4.1), KDB, and unencrypted XML.
    • Round-trip fidelity tests with KeePass XML: export XML from KeePassXC (keepassxc-cli export --format xml) or KeePass, read it into KeePassJava2 (KdbxDatabase.readXml / StreamFormat.None), write it, and compare; and import the XML KeePassJava2 writes back into KeePassXC/KeePass.
  3. jorabin commented on Oct 10, 2026

    @jorabin
    OwnerAuthor

    Will be fixed in 3.1.2. A database now keeps its KDBX minor version when it is written, so a 4.1 file is written back as 4.1. For KDBX 4 whose minor version isn't fixed, KeePassJava2 chooses 4.1 only if the content uses 4.1 features, and otherwise 4.0, as KeePass does. To always write 4.1, use KdbxHeaderOptions.V4_1_AES_ARGON_CHA_CHA. Content that the version written can't hold is left out of the file, kept in the database, and logged as a warning.

    3.1.2 also:

    • writes back the public custom data in the KDBX 4 header
    • keeps the protection of attachments: the protected flag of KDBX 4 attachments is no longer lost, KDBX 3.1 files with protected attachments can now be read (they couldn't before), and protected attachments are written as KeePass writes them
    • writes plain XML that KeePassXC can import: protected values are written as ProtectInMemory="True", as KeePass and KeePassXC do (KeePassXC's import crashed on what KeePassJava2 wrote before), and reading XML keeps the root group's UUID

    These fixes won't be made in 2.x, which from 2.2.7 gets security fixes only. If you need them, please upgrade to 3.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions