# Cryptography coded automation APIs

> Coded automation APIs for the Cryptography activities package, covering symmetric encryption, keyed hashing, and PGP.

`UiPath.Cryptography.Activities`

Coded workflow API for symmetric encryption and decryption, keyed hashing, and PGP encryption, decryption, signing, clear-signing, verification, and key generation. Use these APIs when you design coded automations. Visit [Coded Automations](https://docs.uipath.com/studio/standalone/2023.10/user-guide/coded-automations-introduction) to learn about coded automations and how to design them using APIs.

- **Service accessor:** `cryptography` (type `ICryptographyService`)
- **Required package:** `"UiPath.Cryptography.Activities": "*"` in the `project.json` dependencies.

## Auto-imported namespaces

These namespaces are automatically available in coded workflows when this package is installed:

```text
System
System.IO
System.Text
UiPath.Cryptography
UiPath.Cryptography.Activities
UiPath.Cryptography.Activities.API
UiPath.Cryptography.Enums
```

## Service overview

The `cryptography` service exposes all operations as direct method calls. There is no connection, handle, or scope to open. It is registered as a stateless singleton, so the accessor is shared across the workflow and its methods are safe to call concurrently. Call methods on the service accessor directly:

```csharp
var key = PasswordKey.FromPassword("mykey", Encoding.UTF8);
var ciphertext = cryptography.EncryptText("secret", EncryptionAlgorithm.AESGCM, SymmetricEncryptOptions.Classic(key));
```

### Bytes, Text, and File forms

Every logical operation exposes three input/output forms. Pick the one that matches the data you already have:

| Form | Suffix | Input → Output | When to use |
|------|--------|---------------|-------------|
| **Bytes** | (base) | `byte[]` → `byte[]` | Binary or already-loaded data. |
| **Text** | `...Text` | `string` → `string` (Base64 or ASCII-armored) | Data arriving as text (HTTP, config, environment). |
| **File** | `...File` | file path → file path | Data that lives on disk. |

## Key material

Symmetric and keyed-hash operations take key material as one of two concrete `CryptoKey` subtypes. The class you pick determines which wire formats the key can be used with, and the type system enforces that at compile time through the format factories.

### `PasswordKey`

Password material to be PBKDF2-stretched into a cipher key. Used with the `Classic`, `Owasp2026`, and `OpenSslEnc` wire formats.

| Factory | Purpose |
|---------|---------|
| `PasswordKey.FromPassword(string password, Encoding encoding)` | Password or passphrase as a `string`. |
| `PasswordKey.FromPassword(SecureString password, Encoding encoding)` | Same, sourced from a secret store or user input. |

`PasswordKey` stores the password internally as a `SecureString` (the `string` factory copies the input characters into one) and materializes the cipher-key bytes just-in-time on each operation. The bytes live only on the operation's stack frame and are never pinned to the `PasswordKey` instance; the intermediate buffers are zeroed after each materialization.

`PasswordKey` is `IDisposable`: calling `Dispose()` eagerly zeroes the protected buffer, and subsequent `KeyBytes` access throws `ObjectDisposedException`. Disposing is recommended for long-lived workflows.

### `RawKey`

A literal cipher key of the algorithm's exact required length (for example, 32 bytes for AES-256). Used with the `Raw` wire format. No KDF is applied. `RawKey` is `IDisposable`: calling `Dispose()` zeroes the held key bytes in place.

| Factory | Purpose |
|---------|---------|
| `RawKey.FromBytes(byte[] keyBytes)` | A key already loaded as bytes. |
| `RawKey.FromHex(string hex)` | A key encoded as hex. |
| `RawKey.FromBase64(string base64)` | A key encoded as Base64. |

The key length is not validated by the factories. It is checked when you call the encrypt or decrypt method, and an illegal length for the chosen algorithm throws `ArgumentException` listing the legal byte lengths (for example, 16, 24, or 32 for AES). Construct the key with exactly the algorithm's required key size.

Keyed-hash methods accept either subtype, because they take `CryptoKey` directly and have no wire-format axis.

## Symmetric options

`SymmetricEncryptOptions` and `SymmetricDecryptOptions` bundle the key, the wire format, and any format-specific values (the IV for `Raw`, the KDF iterations for `Owasp2026` and `OpenSslEnc`). Construct them through a format factory. The factory's key-parameter type enforces the key-kind and wire-format pairing at compile time, so a `PasswordKey` cannot be passed to `Raw(...)` and a `RawKey` cannot be passed to `Classic(...)`.

| Factory | Format | Key type | Notes |
|---------|--------|----------|-------|
| `SymmetricEncryptOptions.Classic(PasswordKey key, Encoding encoding = null)` | `Classic` | `PasswordKey` | Default. Frozen wire format for back-compatibility (PBKDF2-HMAC-SHA1 at 10,000 iterations). |
| `SymmetricEncryptOptions.Owasp2026(PasswordKey key, int kdfIterations = 1_300_000, Encoding encoding = null)` | `Owasp2026` | `PasswordKey` | Same layout as Classic, with PBKDF2-HMAC-SHA1 at OWASP's 2026 recommended iteration count. |
| `SymmetricEncryptOptions.Raw(RawKey key, byte[] iv = null, Encoding encoding = null)` | `Raw` | `RawKey` | Caller-supplied key and IV, for third-party interoperability. |
| `SymmetricEncryptOptions.OpenSslEnc(PasswordKey key, int kdfIterations = 600_000, Encoding encoding = null, AesKeySize aesKeySize = AesKeySize.Aes256)` | `OpenSslEnc` | `PasswordKey` | `openssl enc`-compatible (`Salted__` magic with PBKDF2-HMAC-SHA256). `aesKeySize` selects AES-128, -192, or -256 to match the peer's `openssl enc -aes-N-cbc`. |

The `SymmetricDecryptOptions` factories take the same shape, except there is no IV on the decrypt side because the IV is read from the ciphertext stream automatically. The optional `encoding:` parameter sets the text encoding on the options (defaulting to UTF-8) and is consulted only by `EncryptText` and `DecryptText`.

A constructed options object is read-only but introspectable: `CryptoOptions` exposes the getters `Key`, `Format`, `KdfIterations`, and `TextEncoding`; `SymmetricEncryptOptions` additionally exposes `IV` and `AesKeySize`; and `SymmetricDecryptOptions` exposes `AesKeySize`.

### IV and salt strategy

All symmetric encrypt methods are non-deterministic by default: a fresh random 8-byte salt (where applicable) and IV or nonce are generated on every call and embedded in the ciphertext stream. Encrypting the same plaintext twice always produces different ciphertext, and the matching decrypt method reconstructs the salt and IV from the same stream automatically.

* CBC-family algorithms (`AES`, `Rijndael`, `DES`, `TripleDES`, `RC2`) use PKCS7 padding, CBC mode, and a random IV.
* `AESGCM` is Authenticated Encryption with Associated Data (AEAD), with a random 96-bit nonce and a 128-bit authentication tag. Recommended for new workflows.
* `ChaCha20Poly1305` is an AEAD alternative to AES-GCM.

For `Raw`, you can supply an explicit IV through `SymmetricEncryptOptions.Raw(key, iv)`. Pass `null` (the factory default) to let the cipher generate one.

## PGP key material

PGP methods take strongly-typed key handles. Construct them once and reuse them across calls.

| Factory | Purpose |
|---------|---------|
| `PgpPublicKey.FromBytes(byte[] keyBytes)` | Public key from in-memory bytes (ASCII-armored or binary). |
| `PgpPublicKey.FromFilePath(string path)` | Public key loaded from a `.asc` or `.gpg` file. |
| `PgpPrivateKey.FromBytes(byte[] keyBytes, string passphrase)` | Private key and passphrase, bound together. |
| `PgpPrivateKey.FromBytes(byte[] keyBytes, SecureString passphrase)` | Same, with a `SecureString` passphrase. |
| `PgpPrivateKey.FromFilePath(string path, string passphrase)` | Private key from a file. |
| `PgpPrivateKey.FromFilePath(string path, SecureString passphrase)` | Private key from a file, with a `SecureString` passphrase. |

Each key handle also exposes two instance methods:

| Member | Purpose |
|--------|---------|
| `byte[] ToBytes()` | Returns a copy of the key bytes as loaded: ASCII-armored if the key was created from armored input, binary if loaded from binary. Keys returned by `PgpGenerateKeys` are ASCII-armored. |
| `void Save(string filePath, bool overwrite = false)` | Writes the key to disk. Throws `InvalidOperationException` if the file exists and `overwrite` is `false`. |

`PgpKeyPair` (returned by `PgpGenerateKeys`, or constructed directly with `new PgpKeyPair(publicKey, privateKey)`) holds a matched public/private pair. Use `pair.PublicKey` and `pair.PrivateKey`, or deconstruct with `var (pub, priv) = pair;`. Persist with `pair.PublicKey.Save(path)` and `pair.PrivateKey.Save(path)` when you need files on disk.

Passing a `PgpPrivateKey` to an encrypt method implies signing, and passing a `PgpPublicKey` to a decrypt method implies signature verification. Separate `bool sign` or `bool verifySignature` flags are not used.

`PgpPrivateKey` stores its passphrase as a `SecureString` for the lifetime of the instance. It is materialized to a managed `string` only for the duration of each cryptographic operation, because the underlying BouncyCastle library requires a plain `string`. `PgpPrivateKey` is `IDisposable`: calling `Dispose()` eagerly zeroes the protected buffer. `PgpPublicKey` holds no secret material and is not `IDisposable`.

## Relationship to the activities

The coded API and the [Cryptography XAML activities](https://docs.uipath.com/activities/other/latest/developer/cryptography-activities) run on the same cryptographic core, so they are at full parity on:

- Algorithms and keyed-hash algorithms
- Wire formats (`Classic`, `Owasp2026`, `Raw`, `OpenSslEnc`)
- IV, KDF iterations, AES key size, and encoding options
- Every PGP operation

Anything you can compute with an activity, you can compute with this service. The differences are about input/output shape and key-input form, not capability.

The coded API does more than the activities:

| Coded-only capability | Detail |
|-----------------------|--------|
| **Bytes input/output on every operation** | `EncryptBytes` / `DecryptBytes`, `KeyedHashBytes`, `PgpEncryptBytes` / `PgpDecryptBytes`, `PgpSignBytes` / `PgpClearSignBytes`, `PgpVerifyBytes` / `PgpVerifyClearSignedBytes`. No activity has a `byte[]` form. |
| **Raw `byte[]` keys** | `RawKey.FromBytes(byte[])` for symmetric `Raw` and keyed hash. Activities can only supply raw keys as hex or Base64 strings. |
| **In-memory PGP key material** | `PgpPublicKey.FromBytes` and `PgpPrivateKey.FromBytes` let you operate without key files on disk. The activities require key files. |
| **Text and Bytes sign / clearsign / verify** | `PgpSignText` / `PgpClearSignText`, `PgpVerifyText` / `PgpVerifyClearSignedText`, and their Bytes forms. The activities expose only file-based sign, clearsign, and verify. |
| **In-memory `PgpKeyPair`** | `PgpGenerateKeys` returns a usable key pair without forcing file output. |

The activities do two things the coded API does not, neither of which is a cryptographic capability:

* **UiPath resource handles** (`IResource` / `ILocalResource`) as inputs and outputs. The coded API takes plain `string` file paths and `byte[]`.
* **Continue On Error** swallow-and-continue behavior. In a coded workflow you use ordinary `try`/`catch` instead, because every method throws on failure.

:::note
The Text activities split key encoding and plaintext encoding into two properties. In the coded API, the password encoding is set on `PasswordKey.FromPassword(..., encoding)` and the plaintext encoding through the options `encoding:` parameter. Both axes remain independently controllable.
:::

## Migrating from the prior coded API

The coded API surface introduced in the prior release has been consolidated in this version. The old call shapes (separate `string` / `SecureString` / `byte[]` key overloads with an `Encoding` parameter, plus a path-based `PgpGenerateKeys`) are replaced by a single options-based shape per operation, so the key-kind and wire-format pairing is enforced at compile time.

There are no `[Obsolete]` shims: code written against the prior API must be updated to compile against this package.

| Before | After |
|--------|-------|
| `EncryptBytes(input, algo, string key, Encoding enc)` | `EncryptBytes(input, algo, SymmetricEncryptOptions.Classic(PasswordKey.FromPassword(key, enc)))` |
| `EncryptBytes(input, algo, SecureString key, Encoding enc)` | `EncryptBytes(input, algo, SymmetricEncryptOptions.Classic(PasswordKey.FromPassword(key, enc)))` |
| `EncryptBytes(input, algo, byte[] keyBytes)` | `EncryptBytes(input, algo, SymmetricEncryptOptions.Raw(RawKey.FromBytes(keyBytes)))` |
| `EncryptText(input, algo, key, enc)` | `EncryptText(input, algo, SymmetricEncryptOptions.Classic(PasswordKey.FromPassword(key, enc), enc))` |
| `EncryptFile(in, out, algo, key, enc, overwrite)` | `EncryptFile(in, out, algo, SymmetricEncryptOptions.Classic(PasswordKey.FromPassword(key, enc)), overwrite)` |
| `DecryptBytes` / `DecryptText` / `DecryptFile` | Same shape, with `SymmetricDecryptOptions.<Format>(...)`. |
| `KeyedHashBytes(input, algo, string key, Encoding enc)` | `KeyedHashBytes(input, algo, PasswordKey.FromPassword(key, enc))` |
| `KeyedHashBytes(input, algo, byte[] keyBytes)` | `KeyedHashBytes(input, algo, RawKey.FromBytes(keyBytes))` |
| `KeyedHashText(input, algo, string key, Encoding enc)` | `KeyedHashText(input, algo, PasswordKey.FromPassword(key, enc), enc)` |
| `KeyedHashFile(inputPath, algo, string key, Encoding enc)` | `KeyedHashFile(inputPath, algo, PasswordKey.FromPassword(key, enc))` |
| `PgpGenerateKeys(publicKeyPath, privateKeyPath, userId, passphrase, keySize)` | `var pair = PgpGenerateKeys(userId, passphrase, keySize); pair.PublicKey.Save(publicKeyPath); pair.PrivateKey.Save(privateKeyPath);` |

Because every key and options type is new, code that references the old overloads fails to compile rather than silently picking up a different overload. The default text encoding remains UTF-8, so code that relied on the prior UTF-8 default needs no behavioral change beyond the options refactor.

## Symmetric encryption

### `byte[] EncryptBytes(byte[] input, EncryptionAlgorithm algorithm, SymmetricEncryptOptions options)`

Encrypts arbitrary bytes. The `options` parameter carries the key and wire format. Returns the ciphertext per the chosen wire format.

### `string EncryptText(string input, EncryptionAlgorithm algorithm, SymmetricEncryptOptions options)`

Encrypts a string and returns the result as Base64-encoded ciphertext. The plaintext encoding is read from `options.TextEncoding`, defaulting to UTF-8.

### `void EncryptFile(string inputPath, string outputPath, EncryptionAlgorithm algorithm, SymmetricEncryptOptions options, bool overwrite = false)`

Reads a file, encrypts it, and writes the result. Throws `InvalidOperationException` if `outputPath` exists and `overwrite` is `false`.

## Symmetric decryption

### `byte[] DecryptBytes(byte[] input, EncryptionAlgorithm algorithm, SymmetricDecryptOptions options)`

Decrypts ciphertext produced by `EncryptBytes`. `options.Format` must match the format used at encrypt time. Returns the plaintext bytes.

### `string DecryptText(string input, EncryptionAlgorithm algorithm, SymmetricDecryptOptions options)`

Decrypts a Base64-encoded ciphertext produced by `EncryptText` and returns the plaintext. The plaintext encoding is read from `options.TextEncoding`, must match the encoding used at encrypt time, and defaults to UTF-8.

### `void DecryptFile(string inputPath, string outputPath, EncryptionAlgorithm algorithm, SymmetricDecryptOptions options, bool overwrite = false)`

Reads an encrypted file and writes the plaintext. Throws `InvalidOperationException` if `outputPath` exists and `overwrite` is `false`.

## Keyed hashing

Keyed-hash methods compute an HMAC (or a plain hash for non-HMAC algorithms) and return the result as an uppercase hex string. The operation is one-way and has no inverse.

### `string KeyedHashBytes(byte[] input, KeyedHashAlgorithms algorithm, CryptoKey key)`
### `string KeyedHashText(string input, KeyedHashAlgorithms algorithm, CryptoKey key, Encoding encoding = null)`
### `string KeyedHashFile(string inputPath, KeyedHashAlgorithms algorithm, CryptoKey key)`

The optional `encoding` parameter on `KeyedHashText` controls how the input string is transcoded to bytes before hashing, and defaults to UTF-8. `KeyedHashBytes` operates on raw bytes, and `KeyedHashFile` hashes the file contents byte-for-byte, so neither has an encoding axis.

## PGP encryption

If a `signer` is supplied, the encrypted payload is also signed with that private key.

### `byte[] PgpEncryptBytes(byte[] input, PgpPublicKey recipient, PgpPrivateKey signer = null)`
### `string PgpEncryptText(string input, PgpPublicKey recipient, PgpPrivateKey signer = null)`
### `void PgpEncryptFile(string inputPath, string outputPath, PgpPublicKey recipient, PgpPrivateKey signer = null, bool overwrite = false)`

## PGP decryption

If a `verifier` is supplied, the embedded signature is verified during decryption.

### `byte[] PgpDecryptBytes(byte[] input, PgpPrivateKey recipient, PgpPublicKey verifier = null)`
### `string PgpDecryptText(string input, PgpPrivateKey recipient, PgpPublicKey verifier = null)`
### `void PgpDecryptFile(string inputPath, string outputPath, PgpPrivateKey recipient, PgpPublicKey verifier = null, bool overwrite = false)`

## PGP signing (binary signature)

Produces a binary-signed payload. Verify it with the `PgpVerify*` methods.

### `byte[] PgpSignBytes(byte[] input, PgpPrivateKey signer)`
### `string PgpSignText(string input, PgpPrivateKey signer)`
### `void PgpSignFile(string inputPath, string outputPath, PgpPrivateKey signer, bool overwrite = false)`

## PGP clear-signing

Clear-signatures keep the original content human-readable with the signature appended. Verify them with the `PgpVerifyClearSigned*` methods.

### `byte[] PgpClearSignBytes(byte[] input, PgpPrivateKey signer)`
### `string PgpClearSignText(string input, PgpPrivateKey signer)`
### `void PgpClearSignFile(string inputPath, string outputPath, PgpPrivateKey signer, bool overwrite = false)`

## PGP verification

### Binary signatures

Verify payloads produced by the `PgpSign*` methods (or by `PgpEncrypt*` with a signer).

| Method | Signature |
|--------|-----------|
| Bytes | `bool PgpVerifyBytes(byte[] input, PgpPublicKey verifier)` |
| Text | `bool PgpVerifyText(string input, PgpPublicKey verifier)` |
| File | `bool PgpVerifyFile(string inputPath, PgpPublicKey verifier)` |

Returns `true` when the signature is valid, and `false` otherwise.

### Clear-signatures

Verify payloads produced by the `PgpClearSign*` methods.

| Method | Signature |
|--------|-----------|
| Bytes | `bool PgpVerifyClearSignedBytes(byte[] input, PgpPublicKey verifier)` |
| Text | `bool PgpVerifyClearSignedText(string input, PgpPublicKey verifier)` |
| File | `bool PgpVerifyClearSignedFile(string inputPath, PgpPublicKey verifier)` |

### Public-key well-formedness

Confirms that a `PgpPublicKey` instance parses as a well-formed OpenPGP public key. This mirrors the **PGP Verify** activity's **Validate Public Key** verification type.

`bool PgpVerifyPublicKey(PgpPublicKey key)`

Returns `true` when the key is valid.

## PGP key-pair generation

Generates an OpenPGP RSA key pair in memory and returns both halves as a matched `PgpKeyPair`. Persist by calling `Save(path)` on each half.

### `PgpKeyPair PgpGenerateKeys(string userId, string passphrase, RsaKeySize keySize = RsaKeySize.Rsa4096)`
### `PgpKeyPair PgpGenerateKeys(string userId, SecureString passphrase, RsaKeySize keySize = RsaKeySize.Rsa4096)`

**Parameters:**
* `userId` (`string`) - The OpenPGP User ID; conventionally an RFC 2822 mailbox such as `Alice Doe <alice@example.com>`.
* `passphrase` - The passphrase that protects the generated private key. Bound to the returned `PgpPrivateKey`.
* `keySize` (`RsaKeySize`) - The RSA key size. Defaults to `Rsa4096`. `Rsa3072` and `Rsa2048` are accepted for interoperability with legacy systems.

Returns a `PgpKeyPair` exposing `pair.PublicKey` and `pair.PrivateKey`.

## Enum reference

### `EncryptionAlgorithm`

Used by the symmetric encrypt and decrypt methods.

| Value | Notes |
|-------|-------|
| `AESGCM` | AES-GCM with a 96-bit nonce and a 128-bit authentication tag. AEAD. Recommended for new workflows. |
| `ChaCha20Poly1305` | ChaCha20-Poly1305 AEAD. Non-FIPS. An alternative to AES-GCM. |
| `AES` | AES in CBC mode. |
| `Rijndael` | Rijndael in CBC mode. Obsolete and weak; avoid. |
| `DES` | DES in CBC mode. Obsolete and weak; avoid. |
| `TripleDES` | 3DES in CBC mode. Obsolete and weak; avoid. |
| `RC2` | RC2 in CBC mode. Obsolete and weak; avoid. |
| `PGP` | Reserved. Use the dedicated `PgpEncrypt*` and `PgpDecrypt*` methods instead. |

### `SymmetricWireFormat`

Used by `SymmetricEncryptOptions.Format` and `SymmetricDecryptOptions.Format`.

| Value | Notes |
|-------|-------|
| `Classic` | UiPath's byte-stable layout, PBKDF2-HMAC-SHA1 at 10,000 iterations. Default. Frozen for back-compatibility. |
| `Owasp2026` | Classic layout with the OWASP-recommended iteration count (1,300,000). The caller can override it through `kdfIterations`. |
| `Raw` | `IV ‖ ciphertext [‖ tag]`. The caller supplies the literal key (and optionally the IV). For third-party interoperability. |
| `OpenSslEnc` | `Salted__ ‖ salt(8) ‖ ciphertext [‖ tag]`, PBKDF2-HMAC-SHA256 at 600,000 iterations by default. Compatible with `openssl enc -pbkdf2`. |

### `KeyedHashAlgorithms`

Used by the keyed-hash methods.

| Value | Type | Notes |
|-------|------|-------|
| `HMACSHA256` | Keyed HMAC | Recommended for MAC and integrity verification. |
| `HMACSHA384` | Keyed HMAC | |
| `HMACSHA512` | Keyed HMAC | |
| `SHA256` | Unkeyed hash | The key is ignored; equivalent to a plain SHA hash. |
| `SHA384` | Unkeyed hash | The key is ignored. |
| `SHA512` | Unkeyed hash | The key is ignored. |
| `HMACSHA1` | Keyed HMAC | Obsolete. SHA-1 is deprecated by NIST; prefer SHA256 or higher. |
| `HMACMD5` | Keyed HMAC | Obsolete. MD5 is broken; avoid for any security-sensitive use. |
| `SHA1` | Unkeyed hash | Obsolete. Collision attacks are demonstrated; do not use. |

### `RsaKeySize`

Used by `PgpGenerateKeys`.

| Value | Bits |
|-------|------|
| `Rsa2048` | 2048 |
| `Rsa3072` | 3072 |
| `Rsa4096` | 4096 (default) |

## Common patterns

### Encrypt and decrypt a string with AES-GCM (Classic, the default)

```csharp
[Workflow]
public void Execute()
{
    var key = PasswordKey.FromPassword("MySecretKey123!", Encoding.UTF8);

    var ciphertext = cryptography.EncryptText("Sensitive data", EncryptionAlgorithm.AESGCM, SymmetricEncryptOptions.Classic(key));
    Log($"Encrypted: {ciphertext}");

    var plaintext = cryptography.DecryptText(ciphertext, EncryptionAlgorithm.AESGCM, SymmetricDecryptOptions.Classic(key));
    Log($"Decrypted: {plaintext}");
}
```

### Encrypt with a caller-supplied raw key and IV (third-party interoperability)

```csharp
[Workflow]
public void Execute()
{
    // 32 bytes for AES-256
    byte[] rawKeyBytes = Convert.FromBase64String("your-base64-encoded-32-byte-key==");
    byte[] iv          = Convert.FromHexString("a3f1b2c4d5e6f70819a0b1c2d3e4f506");

    var key = RawKey.FromBytes(rawKeyBytes);

    byte[] cipher = cryptography.EncryptBytes(
        Encoding.UTF8.GetBytes("payload"),
        EncryptionAlgorithm.AESGCM,
        SymmetricEncryptOptions.Raw(key, iv));

    // Decrypt. The IV is read from the ciphertext stream prefix, so there is no need to pass it again.
    byte[] plain = cryptography.DecryptBytes(
        cipher,
        EncryptionAlgorithm.AESGCM,
        SymmetricDecryptOptions.Raw(key));
}
```

### Decrypt a file produced by `openssl enc`

```csharp
[Workflow]
public void Execute()
{
    // openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -md sha256 -salt -k password -in plain.txt -out cipher.bin
    var key = PasswordKey.FromPassword("password", Encoding.UTF8);

    cryptography.DecryptFile(
        inputPath:  @"C:\Documents\cipher.bin",
        outputPath: @"C:\Documents\plain.txt",
        algorithm:  EncryptionAlgorithm.AES,
        options:    SymmetricDecryptOptions.OpenSslEnc(key),
        overwrite:  true);
}
```

### Use a stronger KDF iteration count (`Owasp2026`)

```csharp
[Workflow]
public void Execute()
{
    var key = PasswordKey.FromPassword("MySecretKey", Encoding.UTF8);

    // Owasp2026(key) defaults to kdfIterations = 1_300_000 (the OWASP 2026 recommendation).
    var ciphertext = cryptography.EncryptBytes(
        Encoding.UTF8.GetBytes("payload"),
        EncryptionAlgorithm.AESGCM,
        SymmetricEncryptOptions.Owasp2026(key));

    // Decrypt must use the same iteration count. Owasp2026 does not store it in the wire format.
    byte[] plain = cryptography.DecryptBytes(
        ciphertext,
        EncryptionAlgorithm.AESGCM,
        SymmetricDecryptOptions.Owasp2026(key));
}
```

### Compute an HMAC-SHA256 for data integrity verification

```csharp
[Workflow]
public void Execute()
{
    byte[] hmacKey = Convert.FromBase64String("your-base64-hmac-key==");
    var key = RawKey.FromBytes(hmacKey);

    // Keyed-hash methods take a CryptoKey directly. There is no options object, because there is no wire-format axis.
    var digest = cryptography.KeyedHashText("payload to verify", KeyedHashAlgorithms.HMACSHA256, key);
    Log($"HMAC-SHA256: {digest}");
}
```

### PGP encrypt and sign, then decrypt and verify

```csharp
[Workflow]
public void Execute()
{
    var recipientPublic = PgpPublicKey.FromFilePath(@"C:\Keys\recipient_public.asc");
    var senderPrivate   = PgpPrivateKey.FromFilePath(@"C:\Keys\sender_private.asc", "senderPassphrase");

    // Passing a signer to PgpEncrypt* implies sign-and-encrypt.
    byte[] encrypted = cryptography.PgpEncryptBytes(
        Encoding.UTF8.GetBytes("Signed and encrypted message"),
        recipientPublic,
        signer: senderPrivate);

    var recipientPrivate = PgpPrivateKey.FromFilePath(@"C:\Keys\recipient_private.asc", "recipientPassphrase");
    var senderPublic     = PgpPublicKey.FromFilePath(@"C:\Keys\sender_public.asc");

    // Passing a verifier to PgpDecrypt* implies verify-while-decrypting.
    byte[] decrypted = cryptography.PgpDecryptBytes(
        encrypted,
        recipientPrivate,
        verifier: senderPublic);

    Log(Encoding.UTF8.GetString(decrypted));
}
```

### Generate a new PGP key pair

```csharp
[Workflow]
public void Execute()
{
    PgpKeyPair pair = cryptography.PgpGenerateKeys(
        userId:     "Alice <alice@example.com>",
        passphrase: "StrongPassphrase!",
        keySize:    RsaKeySize.Rsa4096);

    pair.PublicKey.Save(@"C:\Keys\my_public.asc");
    pair.PrivateKey.Save(@"C:\Keys\my_private.asc");

    Log("Key pair generated.");
}
```

### Validate an inbound public key before storing it

```csharp
[Workflow]
public void Execute()
{
    // Armored public key arriving as text from an HTTP response or config.
    string armoredPublicKey = LoadFromInbox();
    var candidate = PgpPublicKey.FromBytes(Encoding.UTF8.GetBytes(armoredPublicKey));

    if (!cryptography.PgpVerifyPublicKey(candidate))
    {
        throw new InvalidOperationException("Supplied content is not a valid OpenPGP public key.");
    }

    candidate.Save(@"C:\Keys\trusted_public.asc");
}
```
