---
title: Encrypt a string with DPAPI
summary: Converts a plaintext string and optional entropy/description into Base64 DPAPI ciphertext.
status: draft
owner: Framework
last_reviewed: 2026-10-02
library: SAIF.NetCore.LegacyAuth
reference_type: package
operation_id: OP-03
trigger: Called by Issue the legacy auth cookie (step 4), or any direct consumer of DPAPI.
---

# Encrypt a string with DPAPI

This operation is the string-level entry point into `DPAPI`'s encrypt surface. It normalizes the overload chain's defaults and inputs, then delegates the actual Windows DPAPI call to the byte-array encrypt operation.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-03 |
| **Trigger** | Called by [Issue the legacy auth cookie](issue-legacy-auth-cookie.md) step 4, or any direct consumer of the public `DPAPI` class |
| **Inputs** | key scope, plaintext, optional entropy, optional description |
| **Outputs** | Base64 ciphertext string |
| **Primary components** | `DPAPI.Encrypt(string)`, `Encrypt(KeyType, string)`, `Encrypt(KeyType, string, string)`, `Encrypt(KeyType, string, string, string)` |
| **Entry point** | `src/SAIF.NetCore.LegacyAuth/src/SAIF.NetCore.LegacyAuth/SAIF.NetCore.LegacyAuth/DPAPI.cs` |

## Steps

1. Apply defaults through the overload chain: the instance `Encrypt(string)` uses `KeyType.UserKey`; the shorter static overloads pass `string.Empty` for entropy and description ([BR-07](../business-rules.md#br-07-instance-encrypt-defaults-to-user-key-scope))
2. Coerce `null` plaintext and entropy to `""` ([BR-08](../business-rules.md#br-08-null-inputs-coerced-to-empty-values))
3. UTF-8 encode the plaintext and entropy ([BR-09](../business-rules.md#br-09-utf-8-and-base64-encoding-for-string-encryption))
4. Encrypt the bytes ([BR-04](../business-rules.md#br-04-machine-scope-dpapi-encryption))
   - 4.1 Coerce `null` plaintext, entropy, and description to empty values ([BR-08](../business-rules.md#br-08-null-inputs-coerced-to-empty-values))
   - 4.2 Initialize three `DATA_BLOB`s and a `CRYPTPROTECT_PROMPTSTRUCT` with no prompt or window ([BR-11](../business-rules.md#br-11-no-dpapi-ui-prompts-permitted))
   - 4.3 Copy the plaintext and entropy into unmanaged memory.
   - 4.4 Compute flags: always `CRYPTPROTECT_UI_FORBIDDEN`; add `CRYPTPROTECT_LOCAL_MACHINE` 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))
   - 4.5 Call the native `crypt32!CryptProtectData` primitive and copy the resulting ciphertext bytes out.
   - On failure: a non-Windows host causes the native call to throw `DllNotFoundException`, but this is caught and rewrapped (not thrown directly) along with every other failure as `Exception("DPAPI was unable to encrypt data.", innerException)`, with the `DllNotFoundException` preserved 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)).

   > **Origin**: Steps 4.1–4.5 are implemented in [Encrypt bytes with CryptProtectData](encrypt-bytes-with-cryptprotectdata.md) and executed on behalf of this operation.
5. Base64-encode the ciphertext and return it ([BR-09](../business-rules.md#br-09-utf-8-and-base64-encoding-for-string-encryption))

## Process flow

The following diagram visualizes this operation's control flow, including the embedded byte-array encrypt sub-operation.

```mermaid
flowchart TD
    Start(["Trigger: Encrypt called"]) --> S1["Apply overload<br>chain defaults"]
    S1 --> S2["Coerce null plaintext<br>and entropy to empty"]
    S2 --> S3["UTF-8 encode<br>plaintext and entropy"]
    S3 --> S4["Encrypt bytes:<br>CryptProtectData"]
    S4 -->     D1{"Encryption failed?<br>incl. DllNotFoundException<br>on non-Windows hosts"}
    D1 -->|Yes| ErrEnc(["Exception:<br>unable to encrypt data<br>(inner exception preserved)"])
    D1 -->|No| S5["Base64-encode ciphertext<br>and return"]
    S5 --> EndOk([Ciphertext string returned])
```

## Failure and edge-case behavior
- Null plaintext or entropy arguments are coerced to empty strings rather than raising `ArgumentNullException` ([BR-08](../business-rules.md#br-08-null-inputs-coerced-to-empty-values)).
- The instance `Encrypt(string)` overload always uses the user key scope, differing from the machine-scope call the cookie service makes ([BR-07](../business-rules.md#br-07-instance-encrypt-defaults-to-user-key-scope)) — a consumer calling it directly gets a different binding than expected.
- Any failure raised inside the byte-array overload's try block — including a non-Windows host or non-default Windows installation directory, which causes the native call to throw `DllNotFoundException` — surfaces to the caller as the same generic wrapped `System.Exception("DPAPI was unable to encrypt data.")`, not as a specific exception type; the original cause is preserved only as the inner exception ([BR-12](../business-rules.md#br-12-generic-exception-wrapping-on-encryption-failure), [BR-13](../business-rules.md#br-13-windows-only-dpapi-dll-path)). This wrapping does not cover `InitPrompt` (called before that try block starts) or this operation's own `Encoding.UTF8.GetBytes`/`Convert.ToBase64String` calls, which run outside the byte-array overload's try/catch entirely and propagate any failure unwrapped.

## 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`
