Verifiable

Decrypt it yourself

Everything else on these pages is a claim. Here are the means to check it — with our tool, and more importantly without it.

Why this page exists

We write in several places that your data only ever leaves your device encrypted. You cannot see that. You can believe it — or we hand you the numbers and the steps to check.

That is what follows. The scheme is not homemade; it is standard parts that every browser and every serious programming language already ships. That is not incidental: a scheme that only works with our code would not be verifiable by you.

The second reason is practical. If a vault export is needed in an emergency, we may no longer be reachable. The instructions have to be enough.

The parameters, verbatim

These values are what the shipped code uses. Anyone reproducing it needs numbers, not paraphrase.

Key derivation
PBKDF2
Hash function
SHA-256
Rounds
1.000.000
Salt (random, per envelope)
16 Bytes
Cipher
AES-256-GCM
Initialisation vector (random, per message)
12 Bytes
Authentication tag
128 Bit
Encoding of all fields
Base64
Purpose binding (AAD)
finilog-aad-v1|<Zweck>

The authentication tag is appended to the ciphertext — that is what the Web Crypto API does, and what the common libraries expect. The salt is not a secret: it prevents anyone precomputing one table that works against all users at once.

The quick route: our tool

On the tool page you can encrypt a text and get back exactly what a real vault entry would look like in our database. Take it with you and try to break it.

The tool calls the same file that encrypts every vault entry — no re-implementation. That only proves our code does what we say, though. That these are the same standard parts is shown by the route below.

Open the tool

The convincing route: without us

Take a result from our tool and decrypt it with something else. If your text comes out, you have shown two things: that we really do use these standard schemes, and that you can read your data without us.

It works the other way round too: encrypt something with the code below and feed it into our tool. If it opens, it is the same scheme.

In the browser, nothing to install

The shortest route. The Web Crypto API is in every browser; no code of ours runs here.

// In den Entwicklerwerkzeugen (F12) → Konsole einfügen.
// Läuft in jedem Browser, ohne finilog-Code.
const payload = { salt: "…", iv: "…", ciphertext: "…" };  // aus dem Werkzeug
const passwort = "…";

const b64 = s => Uint8Array.from(atob(s), c => c.charCodeAt(0));

const material = await crypto.subtle.importKey(
  "raw", new TextEncoder().encode(passwort), "PBKDF2", false, ["deriveKey"]
);
const schluessel = await crypto.subtle.deriveKey(
  { name: "PBKDF2", salt: b64(payload.salt), iterations: 1000000, hash: "SHA-256" },
  material,
  { name: "AES-GCM", length: 256 },
  false, ["decrypt"]
);
const klar = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv: b64(payload.iv) }, schluessel, b64(payload.ciphertext)
);
console.log(new TextDecoder().decode(klar));

In Python

If you would rather check independently of the browser, or process the result further.

# pip install cryptography
import base64, json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

payload = json.loads(open("chiffrat.json").read())
passwort = "…".encode()
b64 = base64.b64decode

schluessel = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,                      # AES-256
    salt=b64(payload["salt"]),
    iterations=1_000_000,
).derive(passwort)

# WebCrypto hängt den 16-Byte-Authentifizierungs-Tag hinten an das
# Chiffrat — genau so erwartet ihn AESGCM.decrypt() auch.
klar = AESGCM(schluessel).decrypt(b64(payload["iv"]), b64(payload["ciphertext"]), None)
print(klar.decode())

Opening a vault export

The export holds the ciphertext of your entries and describes itself: scheme, round count and key length are fields in the file. It needs no account, no login and no connection to us.

Two stages, and the order is the whole point of envelope encryption: your password only opens the envelope around the entry key — that key opens the data. Your password never touches the content.

# Ein Tresor-Export, ohne Konto und ohne uns.
# Zwei Stufen: Das Passwort öffnet nur den Umschlag um den
# Eintragsschlüssel — erst dieser öffnet die Daten.
#
# Beide Stufen sind an ihren Zweck gebunden: Der jeweilige Text geht als
# "additional authenticated data" in AES-GCM ein und muss beim Entschlüsseln
# mitgegeben werden. Er ist nicht geheim — er gehört zum Format und verhindert,
# dass ein Chiffrat als etwas anderes gelesen werden kann, als es geschrieben
# wurde. Exporte von vor September 2026 tragen keine Bindung; dort steht an
# beiden Stellen None.
AAD_UMSCHLAG = b"finilog-aad-v1|dek-password"
AAD_DATEN    = b"finilog-aad-v1|vault-entry"

export = json.loads(open("finilog-vault-export-….json").read())["finilog_vault_export"]

for eintrag in export["entries"]:
    dek_umschlag = eintrag["encrypted_dek_user"]
    schluessel = PBKDF2HMAC(
        algorithm=hashes.SHA256(), length=32,
        salt=b64(dek_umschlag["salt"]), iterations=export["kdf"]["iterations"],
    ).derive(passwort)

    dek = AESGCM(schluessel).decrypt(
        b64(dek_umschlag["iv"]), b64(dek_umschlag["ciphertext"]), AAD_UMSCHLAG
    )

    daten = eintrag["encrypted_data"]
    klar = AESGCM(dek).decrypt(b64(daten["iv"]), b64(daten["ciphertext"]), AAD_DATEN)

    umschlag = json.loads(klar)     # {version, title, category, beneficiaryIds, content}
    print(umschlag["title"], "—", umschlag["content"])

Checking which code actually runs

Published source proves nothing on its own — we could run something else in the browser. You can check that, at the moment where it counts: when an entry is saved.

  1. Open the developer tools (F12) and switch to the Network tab.
  2. Save a vault entry containing a text you will recognise.
  3. Look at the outgoing request. Your text is not in the body — salt, initialisation vector and ciphertext are.
  4. Search the Sources tab for the string PBKDF2. You will find the derivation with the round count from the table above.

Anyone who has done this once no longer needs our word for it. That is exactly why the instructions are here and not in the small print.

What this does not prove

This check shows the code is doing the right thing at the moment you look. It does not show that we will ship you the same file tomorrow. No provider that delivers code over the network can close that gap — we would rather name it than skirt it.

It also says nothing about the strength of your password. The best scheme fails against a password that appears in a word list. What is computed here makes guessing expensive, not impossible.

And it says nothing about who else has access to your device. Encryption protects data in transit and at rest — not from someone watching you type.