Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

shadone/KDBXKit

Open more actions menu

Repository files navigation

KDBXKit

Read and write KeePass 2 (KDBX 4) databases from Swift.
Page-locked secrets, KeePassXC-tested round-trips, and streaming
attachments that keep peak memory bounded regardless of vault size.

CI Swift 6.1+ Platforms License: BSD 2-Clause


What is KDBXKit?

KDBXKit is a Swift library for reading and writing KeePass 2 / KDBX 4 password-manager databases — the format used by KeePass, KeePassXC, Strongbox, and similar clients. It handles the full file format (cryptographic envelope, KDF, inner XML, attachments) and exposes a typed Swift API with secure-memory primitives so consumers don't have to roll their own.

import KDBXKit

// Open
let data = try Data(contentsOf: url)
let unlock = UnlockData(masterPassword: "hunter2")
let content = try KDBXReader.parse(data, unlockData: unlock)

// Walk
content.database.visitEntries(in: content.database.root.group) { entry in
    print(entry.uuid, entry.strings.map(\.key))
}

// Save
let bytes = try KDBXWriter().write(content, unlockData: unlock)
try bytes.write(to: url)

Features

Memory hygiene SecureBytes (mlock + secure-zero on deinit), ProtectedString.withRevealedString — no plaintext through Swift.String
Streaming attachments lazy reader + streaming writer keep binaries off the heap; peak save memory is one attachment plus pipeline buffers, regardless of vault size
Interop round-trip tested against KeePassXC keepassxc-cli (gated suite); malformed-input fuzz tests guard against crash-on-bad-data
Concurrency Swift 6 strict-concurrency clean, all public types Sendable
Supported versions KDBX 4.1, 4.0, KDBX 3.1 (read-only)
Ciphers AES-256-CBC, ChaCha20
KDFs AES-KDF, Argon2d, Argon2id
Inner stream Salsa20, ChaCha20
Compression gzip (system zlib)
Key sources master password, key file, raw 32-byte pre-hash (e.g. biometric-unlocked Keychain)

Requirements

  • Swift: 6.1+
  • macOS: 15+
  • iOS: 18+
  • Linux: Swift 6.1 toolchain + zlib

Installation

Add KDBXKit to your Package.swift:

dependencies: [
    .package(url: "https://github.com/shadone/KDBXKit.git", from: "1.0.0"),
],
targets: [
    .target(
        name: "MyApp",
        dependencies: [
            .product(name: "KDBXKit", package: "KDBXKit"),
        ]
    ),
]

On Linux you also need zlib development headers:

sudo apt-get install zlib1g-dev    # Debian/Ubuntu
sudo dnf install zlib-devel        # Fedora

Library usage

The basic open / walk / save flow is in the intro snippet above. A runnable end-to-end example lives in Examples/HelloKDBX/ — open it with swift run HelloKDBX for a working .kdbx reader in ~70 lines. The sections below cover the less obvious bits.

Header-only inspection (no credentials)

let header = try KDBXReader.parseHeader(data)
print(header.formatVersion)        // .v4_1
print(header.kdfParameters)        // .argon2id(...)
print(header.encryptionAlgorithm)  // .AES256CBC / .ChaCha20

Key files and biometric rehydration

// Password + key file
let unlock = UnlockData(masterPassword: "p", keyFile: keyFileBytes)

// Key file alone
let unlock = UnlockData(keyFile: keyFileBytes)

// Rehydrate from a Keychain-stored 32-byte pre-hash (biometric unlock)
let unlock = UnlockData(rawKeyData: keychainBytes)

The 32-byte pre-hash is the same authority as the password — protect it with appropriate access control on the storage side.

Streaming attachments (large vaults)

For vaults whose attachments shouldn't sit resident from unlock to lock, open metadata-only and stream binaries on demand. openMetadataOnly runs the full decrypt + inner-header parse but drops the binary bytes; the returned LazyKDBXContent keeps a handle to the source so individual binaries can be re-streamed without holding all of them in memory.

let lazy = try KDBXReader.openMetadataOnly(from: .file(url), unlockData: unlock)

// Stream a specific binary into the destination of your choice.
var sink = try URLSink(writingTo: destination)  // or DataSink() / SecureBytesSink()
try KDBXReader.streamBinary(from: lazy, at: index, into: &sink)

To save without ever materializing all binaries in process memory, drive the streaming writer with LazyBinarySource (re-streamed from the source vault) and/or DataBinarySource (newly-added attachments still on the heap):

let content = KDBXContent(database: lazy.database, header: lazy.header, innerHeader: lazy.innerHeader)
let binaries: [any BinarySource] = lazy.binaries.indices.map { LazyBinarySource(lazy, at: $0) }
try KDBXWriter.streamingWrite(to: destinationURL, content: content, binaries: binaries, unlockData: unlock)

Peak save memory is one attachment plus the pipeline buffers (~64 KB gzip + ≤16 B AES + 1 MB HMAC block) — independent of total attachment bytes.

Secure memory

Any cleartext key material crossing the library boundary is in SecureBytes (page-locked via mlock, zeroed via memset_s / explicit_bzero on deinit). Protected entry fields use a scoped-reveal pattern instead of returning raw strings:

// Find the entry's Password field — strings is [ProtectedString].
if let password = entry.strings.first(where: { $0.key == "Password" })?.value {
    password.withRevealedString { plaintext in
        // plaintext is a String that's about to leave scope; copy into the
        // platform secret store and return.
        Keychain.set(plaintext, for: entry.uuid)
    }
}

Error handling

KDBXReader.Error and KDBXWriter.Error are typed enums with exhaustive cases — caller-facing doc comments on each case explain when it fires:

do {
    _ = try KDBXReader.parse(data, unlockData: unlock)
} catch KDBXReader.Error.wrongCredentials {
    // bad password / key file
} catch KDBXReader.Error.unsupportedFormatVersion(let major, let minor) {
    // file is KDBX \(major).\(minor) — outside our supported range
} catch KDBXReader.Error.corruptedHMAC(let reason) {
    // file failed integrity check
}

Dependencies

External (all versioned, Apple-blessed, cross-platform):

Vendored (in-tree, no network fetch):

  • P-H-C reference Argon2 in Sources/CArgon2/ (pin: upstream commit f57e61e). CC0 / Apache 2.0 dual-licensed. See Sources/CArgon2/UPSTREAM.md.

System:

  • zlib (linked via -lz, exposed to Swift via the Sources/CZlib/ module). Ships with macOS; install zlib1g-dev or equivalent on Linux.

Companion CLI: kdbx

The repo also ships a kdbx executable — a small debugging and scripting tool, also useful as a worked example of using the library.

swift run kdbx db info     <file.kdbx>                       # header-only, no creds needed
swift run kdbx entry ls    <file.kdbx> --password-stdin
swift run kdbx entry show  <file.kdbx> /Banking/Chase --password-stdin
swift run kdbx attach extract <file.kdbx> /Banking/Chase logo.png --password-stdin

Credentials never come from argv. Resolution order: --password-stdinKDBX_PASSWORD env → --key-file <path> → no-echo TTY prompt. See Sources/KDBXCLICore/ for the full subcommand tree and how the credential plumbing is structured.

Development

Build / test

swift build
swift test
swift test --filter HeaderTests

Lint / format

mint runs the Swift CLI tools listed in Mintfile.

brew install mint
mint bootstrap
mint run swiftformat --lint .   # check
mint run swiftformat .          # apply

Testing the Linux build locally

You don't need a Linux machine — scripts/test-linux.sh runs the build and tests inside the same container CI uses. Requires Docker (Docker Desktop, Colima, or OrbStack).

./scripts/test-linux.sh                         # full build + test, Linux/arm64
./scripts/test-linux.sh --filter Header         # forwards args to `swift test`
SWIFT=6.2 ./scripts/test-linux.sh               # different toolchain
PLATFORM=linux/amd64 ./scripts/test-linux.sh    # x86_64 under qemu (slow)

Linux build artifacts land in .build-linux/ (gitignored) so they don't collide with your macOS .build/; you can interleave runs freely.

Closer-to-CI alternative with act:

brew install act
act -j linux            # run the workflow's Linux job locally

Acknowledgements

  • Dominik Reichl and the KeePass project for the KDBX 4 format specification and the canonical XML schema (Sources/KDBXKit/Database/KDBX_XML.xsd).
  • Alex Biryukov, Daniel Dinu, Dmitry Khovratovich for the Argon2 algorithm (winner of the Password Hashing Competition), and Dinu / Khovratovich / Aumasson / Neves for the reference C implementation we vendor in Sources/CArgon2/.
  • The KeePassXC project — both for being a high-quality client we test interop against, and for the keepassxc-cli tool that makes round-trip testing tractable.
  • Apple's Swift Crypto team for swift-crypto, which carries all of our SHA / HMAC / AES work cross-platform.

License

BSD 2-Clause. See LICENSE.

About

Swift library for reading and writing KeePass 2 / KDBX 4 databases. Page-locked secrets, KeePassXC-tested interop, streaming attachments.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.