---
title: Encrypt bytes with CryptProtectData
summary: Calls the native Windows DPAPI primitive to encrypt a byte array under the chosen key scope.
status: draft
owner: Framework
last_reviewed: 2026-10-02
library: SAIF.NetCore.LegacyAuth
reference_type: package
operation_id: OP-04
trigger: Called by Encrypt a string with DPAPI (step 4).
---

# Encrypt bytes with CryptProtectData

This is the lowest-level encrypt operation: it marshals plaintext and entropy byte arrays into unmanaged memory, invokes the native `crypt32!CryptProtectData` DPAPI primitive, and copies the resulting ciphertext back into managed memory, always freeing the unmanaged buffers regardless of outcome.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-04 |
| **Trigger** | Called by [Encrypt a string with DPAPI](encrypt-string-with-dpapi.md) step 4 |
| **Inputs** | key scope, plaintext bytes, entropy bytes, description string |
| **Outputs** | Ciphertext byte array |
| **Primary components** | `DPAPI.Encrypt(KeyType, byte[], byte[], string)` |
| **Entry point** | `src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs` |

## Steps

1. Coerce `null` plaintext, entropy, and description arguments to empty values ([BR-08](../business-rules.md#br-08-null-inputs-coerced-to-empty-values))
2. Initialize three `DATA_BLOB`s and a `CRYPTPROTECT_PROMPTSTRUCT` with no prompt text and no window handle ([BR-11](../business-rules.md#br-11-no-dpapi-ui-prompts-permitted))
3. Copy the plaintext into unmanaged memory using `Marshal.AllocHGlobal` and `Marshal.Copy`.
   - On failure: throws `Exception("Cannot initialize plaintext BLOB.")`, rewrapped by step 9.
4. Copy the entropy into unmanaged memory the same way.
   - On failure: throws `Exception("Cannot initialize entropy BLOB.")`, rewrapped by step 9.
5. Compute the native flags: always `CRYPTPROTECT_UI_FORBIDDEN (1)`; add `CRYPTPROTECT_LOCAL_MACHINE (4)` when the key scope is `MachineKey` ([BR-04](../business-rules.md#br-04-machine-scope-dpapi-encryption), [BR-11](../business-rules.md#br-11-no-dpapi-ui-prompts-permitted))
   - `UserKey` produces flags `1`; `MachineKey` produces flags `5`.
6. Call the native `crypt32!CryptProtectData` primitive.
   - On failure: on a non-Windows host, or a Windows host without `crypt32.dll` at `c:\windows\system32\crypt32.dll`, the native call throws `DllNotFoundException`. This call is not inside a nested `try`/`catch` of its own, so the exception propagates directly to the outer `catch` in step 9, which rewraps it as `Exception("DPAPI was unable to encrypt data.", innerException)` with the `DllNotFoundException` as the inner cause ([BR-13](../business-rules.md#br-13-windows-only-dpapi-dll-path), [BR-12](../business-rules.md#br-12-generic-exception-wrapping-on-encryption-failure)).
7. If the call returns `false`, read `GetLastWin32Error` and throw `Exception("CryptProtectData fialed.", Win32Exception)` ([BR-12](../business-rules.md#br-12-generic-exception-wrapping-on-encryption-failure))
   - Rewrapped by step 9.
8. Copy the resulting ciphertext bytes out to a managed array and return them.
9. On any exception from steps 3–8, throw `Exception("DPAPI was unable to encrypt data.", innerException)` ([BR-12](../business-rules.md#br-12-generic-exception-wrapping-on-encryption-failure))
   - All failures surface to the caller as a generic `System.Exception`.
10. Always free the three unmanaged buffers in a `finally` block when their handles are non-zero.

## Process flow

The following diagram visualizes this operation's control flow, including its always-run cleanup path.

```mermaid
flowchart TD
    Start(["Trigger: Encrypt bytes called"]) --> S1["Coerce null args<br>to empty values"]
    S1 --> S2["Initialize DATA_BLOBs<br>and prompt struct"]
    S2 --> S3["Copy plaintext to<br>unmanaged memory"]
    S3 --> D1{Copy failed?}
    D1 -->|Yes| ErrWrap
    D1 -->|No| S4["Copy entropy to<br>unmanaged memory"]
    S4 --> D2{Copy failed?}
    D2 -->|Yes| ErrWrap
    D2 -->|No| S5["Compute flags:<br>UI_FORBIDDEN + LOCAL_MACHINE if applicable"]
    S5 --> S6["Call CryptProtectData<br>(native)"]
    S6 --> D3{Non-Windows host or<br>DLL not found?}
    D3 -->|Yes: DllNotFoundException| ErrWrap
    D3 -->|No| D4{Returned false?}
    D4 -->|Yes| ErrWrap
    D4 -->|No| S8["Copy ciphertext bytes<br>to managed array"]
    S8 --> Cleanup
    ErrWrap["Throw wrapped Exception:<br>unable to encrypt data<br>(inner exception preserved)"] --> Cleanup["Free unmanaged<br>buffers in finally"]
    Cleanup --> D5{Exception pending?}
    D5 -->|Yes| EndErr(["Exception propagated"])
    D5 -->|No| EndOk([Ciphertext bytes returned])
```

## Failure and edge-case behavior
- A non-Windows host, or a Windows host without `crypt32.dll` at `c:\windows\system32\crypt32.dll`, causes the native call to throw `DllNotFoundException`; this is caught by the outer `catch` and rewrapped as the same generic `System.Exception("DPAPI was unable to encrypt data.")` as every other failure path, with the `DllNotFoundException` preserved as the inner exception — it is not thrown directly to the caller ([BR-13](../business-rules.md#br-13-windows-only-dpapi-dll-path)).
- Within this operation's try block (steps 3–8), every failure path — unmanaged memory initialization, a native DLL load failure, or a `false` return from `CryptProtectData` — is rewrapped as a single generic `System.Exception("DPAPI was unable to encrypt data.")`, so callers cannot catch a specific exception type; the original cause (including the Win32 error code for a `false` return) is only available in the inner exception ([BR-12](../business-rules.md#br-12-generic-exception-wrapping-on-encryption-failure)). Null-argument coercion (step 1) and struct initialization (step 2), which run before the try block, cannot fail and are therefore outside this guarantee's scope.
- Unmanaged buffers are always freed in a `finally` block regardless of success or failure, preventing memory leaks across repeated calls.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs`
