Skip to content

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

  1. 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.
  2. Initialize unmanaged DATA_BLOB structures for the payload and any optional entropy by allocating unmanaged memory and copying the input bytes into it.
  3. On failure: if allocation fails, raise an exception immediately, before any Win32 encryption or decryption call is attempted.
  4. Initialize a zeroed CRYPTPROTECT_PROMPTSTRUCT, since the DPAPI Win32 functions require this structure even when interactive prompting is disabled.
  5. On the encryption path, set CRYPTPROTECT_UI_FORBIDDEN, add CRYPTPROTECT_LOCAL_MACHINE when the caller selected MachineKey scope, and call Win32 CryptProtectData.
  6. If the API call fails, capture Marshal.GetLastWin32Error() and wrap it in a Win32Exception, then a higher-level Exception.
  7. 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.
  8. On the decryption path, Base64-decode the input when the string overload was used, initialize ciphertext and entropy BLOBs, and call Win32 CryptUnprotectData.
  9. If the API call fails, capture Marshal.GetLastWin32Error() and wrap it in a Win32Exception, then a higher-level Exception.
  10. 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.
  11. 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.

Sources

  • SAIF.Framework.SecurityModule\DAPIEncrypt.vb