From family tree to sealed archive
Each export follows the same five-stage contract on iOS, Android, and the web. The steps are ordered because every later guarantee depends on the earlier one.
-
1
Pack one portable archive
HERITG writes the tree, people, relationships, and media into a strict ZIP payload. A SHA-256 checksum list covers every payload entry before encryption.
manifest.json · tree.json · people.jsonl · relationships.jsonl · media/ -
2
Turn the optional password into a key
The password is normalized to Unicode NFC and processed with PBKDF2-HMAC-SHA256 for 600,000 iterations. An empty password is processed as an empty UTF-8 byte string. A new 16-byte random salt makes every export derive a different key.
optional password + random salt -> 256-bit key -
3
Encrypt and authenticate
AES-256-GCM encrypts the complete ZIP with a fresh 12-byte random nonce. The format header is authenticated too, so changing its version, algorithm, work factor, salt, or nonce breaks authentication.
ZIP + header -> AES-256-GCM -> sealed bytes -
4
Move one sealed file
The resulting
.heritgfile always contains encrypted bytes. A non-empty password keeps the records confidential and prevents a file holder from forging a valid replacement; an empty password does neither, because anyone can derive the same key.HTGENC01 · header · ciphertext · authentication tag -
5
Authenticate before import
The destination app first tries the empty password. If that authenticates, import continues without a prompt; otherwise the app asks for the password. It verifies every checksum and graph reference before committing any family data.
all checks pass → atomic import
The promise is in the source
The excerpt below is abridged from the actual path used for every web .heritg export, including an empty password. It asks the browser’s Web Crypto implementation to derive a non-extractable AES key and authenticate both the header and payload. The link opens the complete implementation.
// Password → key
const material = await crypto.subtle.importKey(
"raw", passwordBytes, "PBKDF2", false, ["deriveKey"]
);
const key = await crypto.subtle.deriveKey(
{ name: "PBKDF2", hash: "SHA-256", salt, iterations: 600_000 },
material,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
// Header + ZIP → authenticated ciphertext
const sealed = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce, additionalData: header, tagLength: 128 },
key,
zip
);
Implementation note: Android archive encryption uses Java Cryptography Architecture primitives and SecureRandom; the password-derived archive key is not stored in Android Keystore.
Prompt-free import path: Each app first authenticates with the empty password. These links show the exact decision that restores immediately or asks for a non-empty password.
The file says how to open it—never the password
The first 44 bytes are a versioned, authenticated envelope header. These values are safe to store beside the ciphertext; they describe how to derive the key but do not contain a password or key. An empty password remains publicly guessable by definition.
HTGENC01000101 01600000new/exportnew/exportciphertext + 16-byte tagAll 44 header bytes are authenticated as AES-GCM additional data. Any unsupported or changed parameter is rejected before import.
Two vectors, asserted on three platforms
A synthetic test archive fixes the date, salt, and nonce. All three source suites assert the same complete encrypted file hashes for both NFC-equivalent non-empty passwords and the empty-password path. This proves the compulsory envelope remains byte-compatible across iOS, web, and Android.
2806b437258da23ca3e0f1f57df81ae69467869ed9d9e8e0c84e00cb9bcd2780
bc8df41b6991455fdad8150c610e56f32d0146ee117bbb7cb2636d3732595440
What this protects—and what it cannot
Encryption is strongest when its boundary is clear. HERITG protects the backup file between trusted endpoints; it cannot make an unlocked or compromised endpoint safe.
Protected by the encrypted backup
- Family records and photos inside the exported archive when a non-empty password is used
- Detection of accidental or unauthenticated changes to the header, ciphertext, or tag
- Detection of changed, missing, extra, or corrupted payload files
Outside the encryption boundary
- An empty password, which provides no confidentiality or authenticity against someone holding the file
- A weak, reused, shared, or exposed password
- Malware or an attacker controlling an unlocked device or browser
- Readable GEDCOM, image, SVG, or legacy unencrypted archives
- Password loss—HERITG has no recovery key or password escrow
Leave the password empty when automatic import matters more than file secrecy. To keep the archive private, use at least 8 characters with an uppercase letter, a lowercase letter, and a number. A longer, unique password is safer because anyone holding the file can guess passwords offline. Store it safely; HERITG has no recovery key.