Tall-Ants LAB ยท Experiment

Encrypting a local database with the help of AI

An experiment in SQLCipher, key management, recovery, and AI-assisted verification, born during the development of a local desktop application.

Tall-Ants LAB

This content describes a real technical experiment. It is not a security certification, nor a universal guide to cryptographic design.

The goal is to show, transparently and for teaching purposes, how a real problem was tackled and how the AI model took part in the different phases of analysis, development, and verification.

Eleven stops, read in whatever order you like

โ†‘ Back to map

The basics: how "encrypting something" really works

Before getting into the actual experiment, a quick recap of the two models all of modern cryptography is built on. No complex formulas needed: the underlying ideas are enough.

Symmetric cryptography: a single key

In the symmetric model there's a single key, used both to encrypt and to decrypt. In simplified form:

C = E(K, P) and back again P = D(K, C)

where is the plaintext, is the ciphertext, is the shared key, and are the encryption and decryption functions.

Whoever holds K can do both. It's the same principle used by , just with a far more robust algorithm than the one in the next example.

TRY IT ยท keyed substitution cipher (teaching XOR)

A deliberately simple, insecure example, just to see the mechanism: each character of the message is combined with the key. The same key, applied a second time, brings back the original message.

Ciphertext (hex)โ€”
Decrypted textโ€”

Note: this is an XOR for teaching purposes, not a secure algorithm. AES-256-GCM fixes the weaknesses of such a simple scheme (too-short key, repeating patterns, no authentication) while still being a single-key cipher.

Public-key cryptography: two keys, one direction

In the asymmetric model there are two different keys, generated together and mathematically linked: a , which can be distributed to anyone, and a , which must stay secret.

C = E(K_pub, P) but only P = D(K_priv, C)

Whoever encrypts with the public key can't decrypt their own message: the matching private key is needed. It's the principle behind TLS/HTTPS, digital signatures, and encrypted mail (PGP). Later on this page you can try it with a real interactive example (RSA on small numbers).

The difference isn't "which of the two is more secure", but who has to share what with whom: symmetric assumes the parties already share a secret; asymmetric solves the problem of establishing one, but is much slower on large amounts of data.

A local database no one should be able to read just by copying it

A personal accounting application can hold particularly sensitive data: transactions, balances, spending categories, plans, and financial information. The starting requirement was simple to state:

Keep the database entirely local, but prevent the SQLite file from being read just by copying it or opening it in an ordinary viewer.

The question behind the experiment: how do you really encrypt a local SQLite database without turning the application into a system dependent on servers, cloud accounts, or an internet connection?

The implemented solution uses several layers, each with a different responsibility: , a random 256-bit master key, , a separate Recovery Key, and + for recovery.

The most important point: there's no single "database password". There's a small key-management architecture.

Encrypting the database isn't enough

It's easy to picture encryption like this: password → encrypted database. But one question remains: where is the password kept? If the key were stored right next to the database in plaintext, the encryption would lose most of its value.

Naive approach
fixed password in the code or a config file
โ†“
encrypted database
โ†“
key readable by anyone with access to the PC

The encryption is there, but it doesn't protect against an attacker with filesystem access: the key is right there, next to the file.

Approach taken
random 256-bit MASTER KEY
โ†“
database encrypted with SQLCipher
โ†“
master key protected by Windows DPAPI

The problem splits into two independent layers: encrypting the data, and separately protecting whoever can open that encryption.

Key management, layer by layer

Each layer has a precise responsibility and knows nothing about the implementation details of the others.

1

Random master key

A random sequence of 32 bytes (256 bits), generated with . It's not derived from a username, PC, Machine ID, or predictable password.

32 bytes256 bitssystem CSPRNG
โ†“ applied to
2

SQLCipher

It replaces SQLite's normal behavior with an encrypted database. In the production path the key is applied directly to the connection via sqlite3_key, not via PRAGMA key. A self-check follows with SELECT count(*) FROM sqlite_master;: if the key is wrong, the open fails.

sqlite3_key APIschema self-check
โ†“ held by
3

Windows DPAPI

The master key is protected with CryptProtectData, tied to the current Windows user, avoiding storing it in plaintext on the filesystem. Reopening the database uses CryptUnprotectData.

current-user scopeno password to remember
โ†“ alternate path
4

Recovery Key

It doesn't encrypt the database: it protects the master key itself, for cases where DPAPI is no longer available or access from a different environment is needed.

independent of DPAPIprotects the master key, not the data
โ†“ via
5

Argon2id + AES-256-GCM

The Recovery Key becomes a cryptographic key via (64 MiB, 3 iterations, parallelism 1, 16-byte salt, 32-byte output). That key encrypts the master key with , which also provides authentication: it detects whether the encrypted data has been tampered with.

64 MiB memory3 iterationsauthenticated (GCM)

The most interesting parts, line by line

Simplified excerpts from the code analyzed during the assessment, one for each step of the layered architecture seen above. They're rewritten for teaching clarity, not copied line by line from the repository.

1 ยท Generating the master key

32 random bytes from the operating system's randomness source, not from a third-party library.

rust
use windows::Win32::Security::Cryptography::BCryptGenRandom;

let mut master_key = [0u8; 32];
BCryptGenRandom(None, &mut master_key, BCRYPT_USE_SYSTEM_PREFERRED_RNG)?;
// master_key now holds 256 bits of operating-system entropy

The highlighted line is the only place the master key is created: from here on it's only ever protected, never regenerated.

2 ยท Applying the key to SQLCipher

The key is passed directly to the connection, not via an SQL command โ€” and is immediately verified with a self-check.

rust
ffi::sqlite3_key(
    connection.handle(),
    key.as_bytes().as_ptr().cast(),
    key.as_bytes().len() as i32,
);

connection.query_row("SELECT count(*) FROM sqlite_master", [], |r| r.get::<_, i64>(0))?;
// if the key is wrong, this query fails: that's the self-check

Without this self-check, a wrong key would still open a "valid" but unreadable connection โ€” the error would only surface much later, at a far less obvious point in the code.

3 ยท Protecting the master key with DPAPI

rust
use windows::Win32::Security::Cryptography::CryptProtectData;

let protected_blob = CryptProtectData(&master_key_blob, None, None, None, None, flags)?;
fs::write(vault_path, &protected_blob)?;
// the blob is only readable by the current Windows user

From here on, the file on disk never contains the master key in plaintext: it contains a blob that Windows can only open for the same account that created it.

4 ยท Deriving a key from the Recovery Key with Argon2id

rust
use argon2::{Argon2, Params};

let params = Params::new(64 * 1024, 3, 1, Some(32))?; // memory in KiB, iterations, parallelism
let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);

let mut derived_key = [0u8; 32];
argon2.hash_password_into(recovery_key.as_bytes(), &salt, &mut derived_key)?;

The highlighted parameters are exactly the ones you can move around in the simulator further down.

5 ยท Encrypting the master key for recovery, with AES-256-GCM

rust
use aes_gcm::{Aes256Gcm, Nonce, aead::Aead};

let cipher = Aes256Gcm::new_from_slice(&derived_key)?;
let encrypted_master_key = cipher.encrypt(nonce, master_key.as_ref())?;
// encrypted_master_key also includes the GCM authentication tag

If someone alters even a single byte of encrypted_master_key, the GCM authentication tag makes decryption fail instead of silently returning corrupted data.

Public-key cryptography: a real example with small numbers

This section is a deliberate detour from the database experiment: it's not about SQLCipher, but about the second major cryptography model mentioned above, so it can actually be compared instead of just described.

The classic scenario for understanding it: Alice wants to send a message to Bob over a channel Trudy can intercept. Bob has never met Alice before, so they can't already have agreed on a shared secret key โ€” the problem symmetric cryptography, on its own, doesn't solve. The message we'll try to transfer is simply a short word written by Alice; in real practice it would more often be a symmetric key to deliver securely, not a long message.

In this database experiment, this problem doesn't exist: there's no Alice and no Bob, no channel to cross. Master key and Recovery Key always stay on the same machine. That's why the architecture seen so far doesn't need public keys โ€” the final paragraph of this section explains why.

Where n, e and d come from โ€” step by step

It's not an arbitrary sequence: every step exists for a precise mathematical reason, and this is exactly where the interactive generator below comes in, to also show what happens when a step is broken.

1

Choose two numbers p and q, kept secret

Why primes, and not just any two numbers? Because the whole scheme rests on a task that's easy in one direction and hard in the other: multiplying two primes to get n is immediate; going back from n to the two original factors (factoring) is impractical if the numbers are large enough. With non-prime factors, this asymmetry weakens, or stops holding up the reasoning behind the later steps altogether.

โ†“
2

Compute n = p ร— q

n becomes the public modulus: the base on which all encryption and decryption arithmetic takes place. It can be known by anyone โ€” it's the secrecy of p and q taken separately, not of n, that protects the scheme.

โ†“
3

Compute ฯ†(n) = (pโˆ’1) ร— (qโˆ’1)

First, the version without jargon: is simply "how many numbers, counted from 1 up to nโˆ’1, share no common factor with n". Try a small example before reading the formula: pick n below and see which numbers stay green.

The green numbers are the ones coprime with n โ€” no shared factor. Count them: with n = 15 = 3 ร— 5 there are exactly 8, and (3โˆ’1)ร—(5โˆ’1) = 2ร—4 = 8. That's not a coincidence: when n is the product of two distinct primes, this count always breaks down into that product โ€” a proven property, not an empirical rule that just happens to work.

Why we need it: there's a theorem (Euler's) stating that raising a number coprime with n to the power ฯ†(n) always leaves remainder 1 when divided by n โ€” a sort of "full loop" in modular arithmetic. Applied twice, once in encryption and once in decryption, it's exactly this identity that brings back the original message. And computing ฯ†(n) is easy if you know p and q, but stays as hard as factoring n if you only know n: the same one-way door seen in step 1.

โ†“
4

Choose e, with ฯ†(n)

"Coprime" means e and ฯ†(n) share no common factor greater than 1 โ€” their greatest common divisor (GCD) is 1. This matters because in the next step we need to find the multiplicative inverse of e modulo ฯ†(n): such an inverse only exists if the two numbers are coprime.

In practice e is often chosen small and fixed โ€” 65537 is a common value in real RSA, here we use 17 to keep the numbers small โ€” because it makes encryption faster; it's then checked that it really is coprime with the resulting ฯ†(n). If it isn't, another e is tried: it doesn't need any special form, it just has to pass this check.

โ†“
5

Compute d, the inverse of e modulo ฯ†(n)

d is the unique number such that e ร— d, divided by ฯ†(n), leaves remainder 1 โ€” in symbols, e ร— d โ‰ก 1 (mod ฯ†(n)). It's computed with the , a variant of the GCD algorithm that also returns the coefficients expressing that GCD as a combination of e and ฯ†(n): the inverse is read off directly from there.

This is the step that makes d computable only by whoever knows ฯ†(n) โ€” so only by whoever knows p and q separately, not just their product n.

Full numeric example with p = 61, q = 53:

n = 61 ร— 53 = 3233 ฯ†(n) = 60 ร— 52 = 3120 e = 17  (gcd(17, 3120) = 1 โ†’ good) d = 2753  (the inverse of 17 modulo 3120)
Check: 17 ร— 2753 = 46,801, and 46,801 mod 3120 = 1. It's exactly this property โ€” not a convention โ€” that makes the key pair work.

What travels where: Bob publishes (e, n) โ€” anyone, including Trudy, can see them. (d, n) stays only with Bob. Alice uses (e, n) to encrypt; only (d, n) can decrypt.

Try it yourself: generate your own key pair

Change p, q and e below. First try two primes different from the example (for instance p = 7, q = 11), then deliberately try to break the scheme with p = 9, q = 11, e = 7 โ€” 9 is not prime โ€” and notice that the decrypted message no longer comes back readable, even using "the right key" generated from those numbers.

Honest note: with some non-prime number combinations, the message may still come back readable, by pure arithmetic coincidence โ€” not because the "you need two primes" rule is wrong, but because small numbers occasionally create special cases. With numbers the size real RSA uses, this coincidence is irrelevant: it's an effect visible only because the numbers here are deliberately small.

RSA GENERATOR ยท choose p, q, e โ€” then encrypt and decrypt with the resulting keys
n = 3233 ยท ฯ†(n) = 3120 ยท gcd(e, ฯ†(n)) = 1 โœ“ ยท d = 2753 โœ“
Bob's public key (e, n)
e = 17, n = 3233
Bob's private key (d, n)
d = 2753, n = 3233
What Trudy sees on the channel (one number per character)
โ€”
What the decrypting party reads
โ€”

If you choose a non-prime p or q, the generator still computes n and (pโˆ’1)(qโˆ’1) to show you the result โ€” but that number isn't the real ฯ†(n), so even with the "right" decryption, the message you get back won't be the original one: it's the practical demonstration of step 1. If you choose very small numbers, even with prime p and q the message can degrade because more characters end up sharing the same remainder modulo n โ€” a limitation of this toy scheme with the ASCII alphabet, not of RSA itself.

A problem RSA alone doesn't solve: key authenticity

There's a question the math above doesn't cover: when Bob sends (e, n) to Alice over that same insecure channel, how can Alice be sure that key really belongs to Bob and not to Trudy, who pretended to be Bob and sent her own pair instead?

RSA alone doesn't answer this question: it guarantees that only whoever holds d can decrypt, but it says nothing about who holds that d. If Trudy manages to convince Alice that her public key is Bob's, Alice will encrypt a message that Trudy can read without any trouble โ€” an attack called man-in-the-middle.

In practice the problem is solved by binding the public key to an identity through a trusted third party (a digital certificate, signed by a Certificate Authority โ€” the mechanism behind the browser's HTTPS padlock), or by verifying the key's fingerprint through a different, already-trusted channel.

This is exactly the kind of problem the database experiment never has to face: master key, DPAPI and Recovery Envelope stay on the same machine, never crossing a channel where someone else could impersonate a party. There's no need to authenticate a key that never leaves the device.

Why SQLCipher doesn't use this scheme. Setting the authenticity problem aside, RSA encrypts only a few bytes at a time and is orders of magnitude slower than AES on large amounts of data: encrypting an entire database row by row with RSA would be impractical. In real systems (TLS, PGP) the public/private key doesn't encrypt the data itself: it encrypts or securely exchanges a symmetric key, which then does the heavy lifting โ€” the same role that, in our architecture, is played by DPAPI and Argon2id: they protect the symmetric master key, they don't replace it with a key pair.

Symmetric โ€” used here

A single key. Very fast even across an entire database. It only needs a secure way to hold that key (DPAPI, Recovery Key) โ€” no channel, no authenticity problem.

Asymmetric โ€” not used here

Two mathematically linked keys. Useful for exchanging a secret between parties who don't already know each other (TLS, digital signatures), but only if the public key's authenticity is guaranteed by something else (a certificate). Too slow to encrypt the entire contents of a local database.

Where it's really used: your bank, your browser, certificates

The Alice/Bob scenario isn't abstract: it's literally what happens every time you open your bank's website. The browser (Alice) has to send sensitive data to the bank (Bob) over a network it treats as insecure by definition.

1
The bank presents a certificateit contains its public key (e, n) and its identity, signed by a Certificate Authority โ€” this is the "certificate exchange" the browser performs at the start of the HTTPS connection.
2
The browser verifies the signatureit checks that the certificate is signed by a CA it trusts, solving the authenticity problem seen above โ€” without this step, you'd be exposed to exactly the man-in-the-middle attack described earlier.
3
The browser generates a symmetric keyrandom, valid only for this session, and encrypts it with the bank's public key before sending it.
4
Only the bank can recover itwith its private key. From this point on, all traffic (balance, transfers, credentials) travels encrypted with that symmetric key โ€” the same "hybrid" scheme seen before: the public key only serves to securely exchange the key that will do the heavy lifting.

The key security property, stated precisely: it's not that whoever encrypts "has no secret at all" โ€” they still have to generate the session key without having it stolen before sending it. The real property is that the public key itself doesn't need to stay secret: the bank could print it on a billboard, anyone could see it, and that wouldn't help anyone decrypt anything. It's the opposite of symmetric cryptography, where whoever obtains the key can both encrypt and decrypt, and so both parties must protect it equally. With public-key cryptography, only one side โ€” the one holding the private key โ€” has to protect a secret.

The main threats to a public-key system

None of these threats are about "RSA's math itself being wrong" โ€” they're all about how keys are generated, distributed, or used.

Substituted public key (man-in-the-middle)

Seen above: if the public key's authenticity isn't verified (a fake or unchecked certificate), an intermediary can pass themselves off as the legitimate recipient.

Factoring the modulus n

If n is too short, an attacker can work back to p and q and reconstruct the private key on their own. How practical that is depends entirely on the size of n โ€” that's the topic of the next subsection.

Poorly generated prime numbers

If p and q aren't chosen with a truly unpredictable random number generator, or are too close to each other, mathematical shortcuts exist (like Fermat's factorization method) that are much faster than generic factoring.

Incorrect padding and implementation

Some famous historical attacks (like Bleichenbacher's) don't break RSA's math: they exploit errors in how an implementation handles malformed ciphertexts, to extract information one bit at a time.

Side-channel attacks

They measure hardware side effects โ€” computation time, power consumption, electromagnetic emissions โ€” while the private key is in use, to infer its bits without ever factoring anything.

Quantum computers (future threat)

Shor's algorithm, if run on a large enough quantum computer, would factor n quickly. No quantum computer that exists today comes close to this capability on real-size keys โ€” which is why work on alternative "post-quantum" schemes is already underway.

How hard it actually is to factor n

The simplest way to break RSA starting from just the public key (e, n) is trial division: try dividing n by 2, then by 3, then by 5... until one of the attempts leaves remainder zero. The first one that works is p; from there q = n / p comes for free. It's also the slowest method possible: real attacks use much smarter algorithms โ€” in particular the general number field sieve (GNFS) โ€” but they're still governed by the same underlying idea: the time explodes as the digits of n grow.

BRUTE-FORCE n ยท trial division starting from the public key
Choose an n and press "Factor".

The attempt stops on its own after a few million divisions or a few seconds, so it doesn't lock up the page โ€” that's already enough to feel the difference between "instant" and "already uncomfortable" while staying on tiny numbers.

What if multiple computers work together?

No real attacker would use a single processor on numbers this size. The space of possible divisors would be split across many machines, each responsible for a slice โ€” exactly like what really happened with RSA-768, distributed across hundreds of machines for almost two years. Here you can see it at a tiny scale: same principle, same type of n, but the search is split across several simulated "machines".

DISTRIBUTED ATTACK ยท split the search space across multiple machines
Choose how many machines to simulate and press "Start distributed attack".

The machines are simulated in turn inside a single browser (there's no real multi-core here), but each one only works through its own slice of the search space โ€” just like real independent machines would. That's why the time to find the factor drops as you increase the number of machines: try comparing the time with 1 machine and with 64.

Real RSA keys don't use moduli like these: theirs run to hundreds of decimal digits. Here's what the factorizations actually completed so far tell us, using the fastest algorithm known today โ€” not trial division:

1991

RSA-100 (100 decimal digits): factored by April 1, 1991, within weeks using the means available at the time.

1999

RSA-155 (512 bits): the factorization required about 4,000 MIPS-years of distributed computation, completed in a few months by coordinating multiple computing centers.

2010

RSA-768 (768 bits, 232 digits): the sieving step alone, on a single processor of the era, would have taken about fifteen hundred years; distributed across hundreds of machines, the entire effort took about two and a half years.

Never factored

RSA-1024: the same authors who factored RSA-768 estimated that factoring a 1024-bit modulus would be roughly a thousand times harder; to date, no public factorization of a number between RSA-704 and RSA-2048 has ever been completed.

Current standard

RSA-2048, today's recommended size: the difficulty grows by a factor enormously larger still than RSA-1024 โ€” well beyond what today's classical computers can tackle in any useful timeframe.

Note: quantum computers, if powerful enough, would change this picture thanks to Shor's algorithm mentioned above among the threats โ€” which is why research into "post-quantum" schemes is already active today, not in anticipation of some far-off problem.

How Argon2id's parameters change the security/wait trade-off

Argon2id turns the Recovery Key into a cryptographic key by making it "cost" time and memory: the more it costs you to open the database once, the more it costs an attacker to try billions of passwords. Move the controls to see the trade-off โ€” this is a teaching simulation, not a real security measurement.

SIMULATOR ยท Argon2id (illustrative parameters)
Perceived wait at opening~0.3s
โš  ILLUSTRATIVE EXAMPLE โ€” not a real hardware benchmark or a security audit. The numbers below are meant only to show the direction of the effect (the more it costs you, the more it costs an attacker), not a certified or measured value.
Estimated attempts per second for an attacker (a single dedicated machine) โ€”
Assumed attempt space: ~10 trillion plausible combinations 10,000,000,000,000
Estimated time to exhaust it, single machine โ€”
Same attack, parallelized across a cluster of 10,000 GPUs โ€”
With these parameters, opening is nearly instant, but an attacker with dedicated hardware can try many passwords per second against a dump of the Recovery Envelope.

Note: the curves used here are simplified for teaching purposes, to show the direction of the trade-off (higher memory and iterations โ†’ slower opening but a costlier attack). They don't represent a real Argon2id benchmark, nor an estimate calibrated against existing attack hardware.

Three paths, one master key

Click the steps to follow what happens in each of the three scenarios.

Generate random MASTER KEY

Migrating an existing database

The application already existed with an unencrypted SQLite database. The process doesn't crudely overwrite the original file.

1
Verified backupof the plaintext SQLite database, before any operation.
2
Temporary SQLCipher databasecreated alongside the original.
3
sqlcipher_export(...)copies the data into the new encrypted database.
4
Integrity checkscomparison of schema, version, tables, row counts, plus PRAGMA integrity_check.
5
File swapthe encrypted database takes the place of the plaintext one.
6
Old DB kept as rollbackretired only after a verified modern backup and a valid recovery envelope.
Security isn't just about the cryptographic algorithm โ€” it's also about how you migrate, verify, and roll back if something goes wrong.

Four separate roles, not just one

AI wasn't treated as a function that "just writes code". Separating roles reduces a risk typical of AI-assisted development: asking the same model to implement something and then automatically treating its own implementation as correct.

Co-designer

Helps break the problem down โ€” protecting the database, protecting the key, handling recovery and migration โ€” and compare architectural alternatives.

Coding agent

Implements narrowly scoped changes in the repository, in limited sprints: initial analysis โ†’ plan โ†’ limited patch โ†’ test โ†’ verification.

Reviewer

Rereads the code in read-only mode, without changing it, and reconstructs what the code actually does โ€” distinguishing observed from inferred.

Tutor

Turns the resulting code into an understandable explanation, like this very page.

Final control always comes back to the code, the tests, and the evidence โ€” never to the model's word about itself.

Verified, inferred, unverified

Besides writing the code, a separate session was run dedicated purely to verifying it: an AI model reread it in read-only mode โ€” without changing anything โ€” with the sole task of reconstructing what the program actually does. This is the "Reviewer" role seen in the previous section; we call this session the assessment.

To keep a session like this from turning an assumption into a fact, every statement that came out of it was classified into one of three categories, depending on how it was established: observed directly in the code, inferred from a library's known behavior, or unverifiable without actually running the program. Filter the entries below to see the difference.

Verified

BCryptGenRandom actually generates the master key's 32 bytes โ€” observed in the source.

Verified

The key is applied to the connection with sqlite3_key, not PRAGMA key โ€” observed in the source.

Verified

The self-check runs SELECT count(*) FROM sqlite_master; right after the key is applied.

Inferred

The way the 32 bytes are passed to sqlite3_key implies a specific internal SQLCipher behavior โ€” a conclusion based on the library's documented behavior, not observed directly.

Inferred

The level of protection DPAPI offers depends on the policies of the Windows account in use โ€” inferred from Microsoft documentation, not measured in this experiment.

Unverified

The exact version of SQLCipher actually loaded during a specific run requires a runtime measurement.

Unverified

DPAPI's behavior under a Windows account with particular corporate policies wasn't tested in this experiment.

Five principles

1
Encrypting data and managing keys are two different problems.SQLCipher protects the database, but you still need a strategy for holding the key.
2
Recovery has to be designed together with encryption.Perfect protection that makes it impossible to legitimately recover your own data can be unusable.
3
Migration is part of security.Backup, validation, rollback, and controlled deletion of the plaintext are all part of the overall model.
4
AI can speed up development, but it has to work with clear roles and boundaries.Design, coding, and review are different activities, with different outcomes to compare.
5
Documentation has to be verified against the code.Even a substantially correct description can differ from the real implementation in important details.
An AI model can take part in a complex technical process by taking on different roles, as long as every result is traced back to verifiable evidence in the code and the tests.
RustTauriSQLiteSQLCipherWindows DPAPIBCryptGenRandomArgon2idAES-256-GCM
The technical description on this page comes from a read-only assessment of the code. Behaviors that require runtime measurements aren't presented as verified.