The claim
A secret that exactly one process needs should be readable by exactly that process. For the administrator passwords of access-control panels, that means: entered write-only, sealed under the connector host's public key, opened in the host's memory and nowhere else, backed up as ciphertext, with the private key exported once into an offline escrow file and never again. The two tools that come to hand in .NET on Windows, machine-scope DPAPI and the web runtime's Data Protection key ring, each answer the question "who may read this" with the wrong set of people.
Context
A panel administrator password reads more than configuration. The terminals probed for this record return person PINs in clear on read, and the vendor documents that fingerprint templates can be read back. Whoever holds a panel's password holds its PINs and templates; that is an inference, treated here as a fact for design. No ISAPI source reviewed for this record describes client-certificate authentication toward a panel, so a password used with Digest inside TLS remains, and ASVS 5.0.0's rule that service-to-service authentication must not use "unchanging credentials such as passwords" (13.2.1) is a recorded deviation for this one link, held by one process.
The site runtime this design replaces stores the passwords as plaintext JSON and hands them to several callers, including a periodic broadcast to every device service. One sentence is enough for that.
The decision
1. Write-only entry. The console takes a panel password and never returns it: no API, no screen, no log, no audit row holds it. The audit records that it was replaced, by whom and when, and that request's body is excluded from request logging.
2. Sealed to the assigned host. The site runtime encrypts the password with a fresh AES-GCM content key, wraps that key with RSA-OAEP using SHA-256 under the public key of the connector host assigned to the device, stores the sealed blob and the key id, and drops the plaintext at once. The device id and the key id are bound as associated data, so a blob cannot be moved to another device's row. The host's sealing key is registered with its principal at enrolment and bound to its device-plane certificate; the runtime seals only to the key registered for the principal assigned to the device, refuses a session that presents another key, and takes a new key only through a rotation signed by the current key, audited and alarmed, after which the host re-seals.
public static SealedSecret Seal(ReadOnlySpan<byte> secret, RSA hostKey, Guid deviceId, Guid keyId)
{
var contentKey = RandomNumberGenerator.GetBytes(32);
var nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
var ciphertext = new byte[secret.Length];
var tag = new byte[AesGcm.TagByteSizes.MaxSize];
using (var aes = new AesGcm(contentKey, tag.Length))
{
aes.Encrypt(nonce, secret, ciphertext, tag, AssociatedData(deviceId, keyId));
}
var wrappedKey = hostKey.Encrypt(contentKey, RSAEncryptionPadding.OaepSHA256);
CryptographicOperations.ZeroMemory(contentKey);
return new SealedSecret(keyId, wrappedKey, nonce, ciphertext, tag);
}
The sizes follow ASVS 5.0.0: "a minimum of 128-bits of security", which the standard itself maps for RSA, "RSA requires a 3072-bit key to achieve 128 bits of security" (11.2.3); no "weak padding schemes (e.g., PKCS#1 v1.5)" (11.3.1); "only approved ciphers and modes such as AES with GCM" (11.3.2). A fresh content key per secret means a nonce is never reused under a key, which is the one thing GCM cannot forgive.
3. Opened only by the host, in memory. The host receives sealed blobs for its assigned devices in its session answer, opens them for the device's lane, writes them to no disk and no log, and drops them when the assignment changes.
4. One escrow export, then a key nobody can export. The host's private key is generated at install in the Windows
key store under the host's service account, exported once into a password-protected escrow file, verified by opening
that file in a separate process, and from then on not exportable. The key store has a policy for exactly this:
CngExportPolicies.AllowArchiving, "The private key can be exported one time for archiving purposes." The customer
keeps the file and its passphrase offline, in two places. The same file carries the other keys a restore needs and
nothing else.
5. The TPM tier excludes escrow. Where a TPM 2.0 is present, the installer offers the Microsoft Platform Crypto Provider as a hardening tier. Its keys are stored so that they "cannot be extracted, even by malicious software", which is the point and the price: with the TPM tier, a hardware replacement means entering every panel password again. The choice and its consequence are written into the install record.
6. Backup and restore. The database backup carries ciphertext, key ids and the public key. The private key travels only in the escrow file, so the recovery unit is the backup set plus the escrow file. A restore to new hardware imports the escrow file or re-enters every password. Rotating the host key on the same machine re-seals inside the host, so only ciphertext ever crosses the device plane.
7. Deleted, with no data migration. The plaintext column, an unused "encrypted" column that was never written, the routes that hand passwords out, and the test that pins plaintext: all of it goes. Panel passwords are entered again on the rebuilt schema. Before the first release there is nothing to migrate; that rule has its own note in this log, "Pre-MVP: make it right, never comply".
Options not taken
| Option | Why not |
|---|---|
| Plaintext, as before | Any database read or backup discloses every panel password, and with it every PIN and template on those panels |
| DPAPI at machine scope | The documentation is exact: "Any process running on the computer can unprotect data" |
| The site runtime's Data Protection ring alone | The runtime, the most exposed process on the server, could open every panel password; and with an explicit key location the ring's keys "are no longer encrypted at rest" unless you add a key encryption mechanism |
| A credential store local to the host | A second store to back up, and the site could not tell which credential version is current |
| A sealing key published by the host at each session start | Whoever holds the host's certificate could substitute a key of their own and receive every password entered afterwards |
| The escrow file kept on the server | It defeats a non-exportable key: whoever owns the disk owns the keys |
| Escrow held by the vendor | The vendor would hold every customer's keys, a target and a transfer of control nobody asked for |
| No escrow by default | A restore to new hardware loses every panel password; offered only as the explicit TPM choice |
| Client certificates toward the panels | No evidence the panels support them; the password inside TLS stays, as a recorded deviation |
What it cost, and what remains
- A compromised host is a compromise of every password it held: its certificate is revoked, a new host is enrolled, and every password is changed on its panel and entered again. The design makes that the blast radius, no larger.
- A panel left on plain HTTP exposes its Digest exchange on the panel network (the terminals' digest algorithm is MD5 today), enough for an offline guess at the administrator password. Sealing at rest changes nothing there, so the console shows the panel's weak link, and over such a link the host writes cards only.
- Managed strings cannot be reliably zeroed, so a memory dump of the host can reveal a password it holds. The host runs under its own account and dumps are restricted; the residual is accepted and written down.
- When a customer changes a password on a panel, the gate shows
AuthRejectedwith no retry loop until the new password is entered, so the account is not locked. - Losing the server and the escrow file together means re-entering every panel password. That is the deal the customer is told at install, in writing.
What a team can take from this
Ask, for each secret, which process needs to read it, and build so that only that process can. Prefer a public key you can register and verify over a shared key anyone on the machine can use. Generate the private key where it will live, export it once for escrow with a policy that allows one export, and make the TPM a choice with its consequence named. Then delete the old storage; a migration of plaintext secrets is a second copy of them.
Principal software engineer, Belgrade
Designs and builds secure, multi-tenant .NET platforms. Questions about this entry, or a different view on it, are welcome by email.