Decrypt (internal only)¶
DPAPI.Decrypt mirrors the encrypt overloads in reverse, but all three overloads are declared internal and have no caller anywhere in this library, and there is no InternalsVisibleTo declaration, so this operation is dead code from any consumer's point of view. It is documented here because it is part of the compiled surface, and because the public CryptProtectData/CryptUnprotectData externs mean the underlying decrypt capability is still reachable outside this managed wrapper — see Security and authorization.
| Field | Value |
|---|---|
| Operation ID | OP-06 |
| Trigger | None — no member of the library calls Decrypt; recorded for completeness |
| Inputs | Base64 string or raw ciphertext bytes, and entropy (string or bytes) |
| Outputs | Decrypted plaintext as the return value (string for the string overloads, byte[] for the byte-array overload); the description ref parameter carries separate DPAPI description metadata, not the plaintext. Unreachable in practice. |
| Primary components | DPAPI.Decrypt(string, ref string), Decrypt(string, string, ref string), Decrypt(byte[], byte[], ref string) — all internal |
| Entry point | src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs |
Steps¶
- (String overload only) UTF-8-encode the entropy string into bytes — this happens before the ciphertext is Base64-decoded.
- (String overload only) Base64-decode the
cipherTextstring into raw ciphertext bytes viaConvert.FromBase64String. - On failure: invalid Base64 input throws
FormatExceptiondirectly fromConvert.FromBase64String, before the byte-array overload (and its exception wrapping) is ever reached — this failure is not wrapped asException("DPAPI was unable to decrypt data."). - Call the byte-array overload
Decrypt(byte[] cipherTextBytes, byte[] entropyBytes, ref string description), which first callsInitPromptto populate the prompt struct, then resets thedescriptionrefparameter to""— both before its try block starts. - Inside the try block, initialize the ciphertext and entropy
DATA_BLOBs (BR-11), then call the nativecrypt32!CryptUnprotectDataprimitive withCRYPTPROTECT_UI_FORBIDDENset, passingdescriptionby reference so the native call can populate it with the DPAPI-recovered description metadata. - On failure: wrapped as
Exception("DPAPI was unable to decrypt data."), mirroring the encrypt path's generic wrapping behavior (BR-12). - On success, copy the decrypted bytes into a managed
byte[]. Afinallyblock frees the unmanaged memory allocated for all threeDATA_BLOBs before the return value or the exception propagates to the caller. The string overloads additionally UTF-8-decode the returned bytes into the returned plaintextstring.
Process flow¶
The following diagram visualizes this operation's (unreachable) control flow, mirroring the encrypt path in reverse.
flowchart TD
Start(["Trigger: none<br>(no caller in this library)"]) --> S1["UTF-8-encode entropy<br>(string overload only)"]
S1 --> S2["Base64-decode ciphertext<br>(string overload only)"]
S2 --> D0{Invalid Base64?}
D0 -->|Yes| ErrFmt(["FormatException<br>(not wrapped)"])
D0 -->|No| S3["Byte-array overload:<br>InitPrompt, then reset description"]
S3 --> S4["Initialize ciphertext/entropy BLOBs;<br>call CryptUnprotectData<br>(native, UI forbidden)"]
S4 --> D1{Call failed?}
D1 -->|Yes| ErrDec["Exception:<br>unable to decrypt data"]
D1 -->|No| S5["Copy decrypted bytes<br>into managed byte[]"]
ErrDec --> Cleanup["finally (byte overload):<br>free unmanaged BLOB memory"]
S5 --> Cleanup
Cleanup --> D2{Exception pending?}
D2 -->|Yes| EndErr(["Exception propagates<br>to caller"])
D2 -->|No| S6["Byte overload returns byte[];<br>string overloads then<br>UTF-8-decode to plaintext"]
S6 --> EndOk(["Plaintext/byte[] returned;<br>description populated separately"])
Failure and edge-case behavior¶
- This operation has no reachable call path from any member of this library and no
InternalsVisibleTogrant, so it is unreachable from any consumer's point of view and has no test coverage*. - The
internalvisibility modifier onDecryptis not a security boundary: the underlyingCryptProtectData/CryptUnprotectDataexterns arepublic static extern, so any consumer can call raw DPAPI decryption directly with a captured cookie and the matching entropy, bypassing this unreachable wrapper entirely. See Security and authorization for the full risk discussion. - Invalid Base64 input to the string overload throws
FormatExceptiondirectly fromConvert.FromBase64String, before the byte-array overload's exception wrapping applies — this failure is not wrapped asException("DPAPI was unable to decrypt data."). - Any failure raised inside the byte-array overload's try block (including a native
CryptUnprotectDatafailure) is wrapped as a single genericSystem.Exception("DPAPI was unable to decrypt data."), matching the encrypt path's exception-wrapping behavior (BR-12). This wrapping does not cover the precedingInitPromptcall or thedescriptionreset to""(both run before the try block starts), the string overload's ownEncoding.UTF8.GetStringcall (which runs only after the byte overload has already returned successfully, outside its try/catch), or an exception thrown from thefinallycleanup block — failures in those regions propagate as their original, unwrapped exception type.
Related documentation¶
Note: Statements marked with
*were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.
Sources¶
src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs