Protect and unprotect sensitive values¶
This operation wraps Windows DPAPI for any caller that needs to protect a plaintext value or recover a value that was previously protected. Inside this library it underpins the credential payload packed into the SAIFAUTH cookie, but the public DPAPI class exposes the same encrypt/decrypt workflow to any code that can reference this assembly.
| Field | Value |
|---|---|
| Operation ID | protect-and-unprotect-sensitive-values |
| Trigger | Caller invokes DPAPI.Encrypt or DPAPI.Decrypt. |
| Inputs | Plaintext/ciphertext strings or byte arrays, optional entropy, optional description, optional key scope |
| Outputs | Base64 ciphertext, plaintext string/bytes, or a wrapped exception |
| Primary components | DPAPI.Encrypt, DPAPI.Decrypt, DPAPI.InitBLOB, DPAPI.InitPrompt |
| Entry point | SAIF.Framework.SecurityModule\DAPIEncrypt.vb |
Steps¶
- Normalize nullable string inputs to empty strings in the string overloads, then convert string payloads to UTF-8 byte arrays before delegating to the byte-oriented overloads that do the actual work.
- Initialize unmanaged
DATA_BLOBstructures for the payload and any optional entropy by allocating unmanaged memory and copying the input bytes into it. - On failure: if allocation fails, raise an exception immediately, before any Win32 encryption or decryption call is attempted.
- Initialize a zeroed
CRYPTPROTECT_PROMPTSTRUCT, since the DPAPI Win32 functions require this structure even when interactive prompting is disabled. - On the encryption path, set
CRYPTPROTECT_UI_FORBIDDEN, addCRYPTPROTECT_LOCAL_MACHINEwhen the caller selectedMachineKeyscope, and call Win32CryptProtectData. - If the API call fails, capture
Marshal.GetLastWin32Error()and wrap it in aWin32Exception, then a higher-levelException. - If the API call succeeds, copy the protected bytes out of the output BLOB and return them either as a raw byte array or as a Base64 string, depending on the overload called.
- On the decryption path, Base64-decode the input when the string overload was used, initialize ciphertext and entropy BLOBs, and call Win32
CryptUnprotectData. - If the API call fails, capture
Marshal.GetLastWin32Error()and wrap it in aWin32Exception, then a higher-levelException. - If the API call succeeds, copy the plaintext bytes out of the output BLOB and return them either as a raw byte array or as a UTF-8 string.
- In
Finally, free every unmanaged buffer that was allocated for plaintext, ciphertext, and entropy BLOBs, whether the call returns normally or rethrows.
Process flow¶
The following diagram visualizes this operation's control flow.
flowchart TD
Start([Encrypt or decrypt request]) --> S1[Normalize inputs]
S1 --> S2[Allocate DATA_BLOB structures]
S2 --> D1{Encrypt path?}
D1 -->|Yes| S3[Call CryptProtectData]
D1 -->|No| S4[Call CryptUnprotectData]
S3 --> S5[Copy output bytes]
S4 --> S5
S5 --> EndOk([Free buffers and return result])
Failure and edge-case behavior¶
- Null plaintext, entropy, and description values are normalized to empty strings rather than rejected, so callers do not need to guard against nulls before calling the string overloads.
- Both the encrypt and decrypt paths wrap Win32 failures in higher-level exceptions that preserve the underlying Win32 error code, so callers can still diagnose the root cause.
- Every unmanaged buffer allocated during the call is freed in
Finally, including when initialization or the DPAPI call itself fails — the operation does not leak unmanaged memory on the error path.
Related documentation¶
Sources¶
SAIF.Framework.SecurityModule\DAPIEncrypt.vb