Skip to content
2 changes: 1 addition & 1 deletion code_samples/tdf/assertion_examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The system metadata assertion uses ID `"system-metadata"`, schema `"system-metad

### End-to-End Example

Create a signed assertion at encrypt time, then verify it at decrypt time.
Create a signed assertion at encrypt time, then verify it at decrypt time. Uses `LoadTDF` directly rather than the [Decrypt Helpers](/sdks/tdf#decrypt-helpers) (`DecryptBytes`/`DecryptTo`/`DecryptFile`), since this example reads `tdfReader.Manifest().Assertions` — the helpers return only the plaintext, with no access to manifest data.
Comment thread
marythought marked this conversation as resolved.

<Tabs>
<TabItem value="go" label="Go">
Expand Down
36 changes: 15 additions & 21 deletions docs/sdks/quickstart/go.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ package main

import (
"bytes"
"context"
"log"
"strings"

Expand Down Expand Up @@ -122,18 +123,13 @@ func main() {

// Decrypt data
log.Println("\n🔓 Decrypting TDF...")
tdfReader, err := client.LoadTDF(bytes.NewReader(encryptedBuffer.Bytes()))
decryptedBytes, err := client.DecryptBytes(context.Background(), encryptedBuffer.Bytes())
if err != nil {
log.Fatalf("❌ Decryption failed: %v", err)
}

var decryptedBuffer bytes.Buffer
if _, err = tdfReader.WriteTo(&decryptedBuffer); err != nil {
log.Fatalf("❌ Failed to read decrypted data: %v", err)
}

log.Println("✅ Data successfully decrypted")
log.Printf("📤 Decrypted content:\n\n%s\n", decryptedBuffer.String())
log.Printf("📤 Decrypted content:\n\n%s\n", string(decryptedBytes))

log.Println("\n🎉 Quickstart complete!")
}
Expand Down Expand Up @@ -405,23 +401,27 @@ In production applications, you'll often need to persist encrypted TDFs to disk
- **Archive encrypted data**: Store TDFs in backup systems or long-term storage with their access policies intact

```go
import "os"
import (
"context"
"os"
)

// After encryption
err = os.WriteFile("encrypted.tdf", encryptedBuffer.Bytes(), 0644)
if err != nil {
log.Fatalf("Failed to save TDF: %v", err)
}

// Later, load from file
tdfData, err := os.ReadFile("encrypted.tdf")
// Later, decrypt directly from the file — DecryptFile streams from disk
// rather than buffering the ciphertext or plaintext in memory.
err = client.DecryptFile(context.Background(), "encrypted.tdf", "decrypted.txt")
if err != nil {
log.Fatalf("Failed to read TDF: %v", err)
log.Fatalf("Failed to decrypt TDF: %v", err)
}

tdfReader, err := client.LoadTDF(bytes.NewReader(tdfData))
```

`DecryptFile` is one of three decrypt convenience helpers — see [Decrypt Helpers](/sdks/tdf#decrypt-helpers) for `DecryptBytes` and `DecryptTo`, which work directly on in-memory ciphertext instead of a file path.

### Handle Large Files with Streaming

For large files, use file I/O instead of in-memory buffers:
Expand Down Expand Up @@ -626,19 +626,13 @@ func main() {
log.Printf("✅ TDF loaded from encrypted.tdf")

// 7. Decrypt the data
tdfReader, err := client.LoadTDF(bytes.NewReader(tdfData))
if err != nil {
log.Fatalf("Failed to load TDF: %v", err)
}

var decryptedBuffer bytes.Buffer
_, err = tdfReader.WriteTo(&decryptedBuffer)
decryptedBytes, err := client.DecryptBytes(context.Background(), tdfData)
if err != nil {
log.Fatalf("Failed to decrypt: %v", err)
}

log.Printf("✅ Data successfully decrypted")
log.Printf("📤 Decrypted content: %s", decryptedBuffer.String())
log.Printf("📤 Decrypted content: %s", string(decryptedBytes))
}
```

Expand Down
99 changes: 99 additions & 0 deletions docs/sdks/tdf.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This page covers the core TDF operations:

- **[CreateTDF](#createtdf)** — encrypt a payload and write a TDF
- **[LoadTDF](#loadtdf)** — open a TDF and access the plaintext
- **[Decrypt Helpers](#decrypt-helpers)** — `DecryptBytes`/`DecryptTo`/`DecryptFile`: one-call convenience wrappers over `LoadTDF` + `WriteTo`
- **[IsValidTdf](#isvalidtdf)** — check whether a stream is a valid TDF without decrypting
- **[BulkDecrypt](#bulkdecrypt)** — decrypt multiple TDFs in one call
- **[TDF Reader](#tdf-reader)** — methods on the reader object returned by `LoadTDF`
Expand Down Expand Up @@ -496,6 +497,104 @@ Rejects with `Error` if the TDF is invalid, the KAS is unreachable, access is de

---

## Decrypt Helpers

Go-only convenience wrappers over `LoadTDF` + `Reader.WriteTo` for the common case of decrypting straight to plaintext. All three take a `ctx context.Context` that governs their KAS rewrap request, and accept the same `opts ...TDFReaderOption` as `LoadTDF` — see [Decrypt Options](#decrypt-options) for the full list.

:::tip Why use these instead of LoadTDF directly?
`LoadTDF` returns a reader — useful when you need to stream a very large payload or read manifest data (attributes, assertions) before writing out the payload. If you just want the plaintext, `DecryptBytes`, `DecryptTo`, and `DecryptFile` collapse `LoadTDF` + `WriteTo` into a single call.
:::

### DecryptBytes

Decrypts a TDF payload held in memory and returns the plaintext. Rejects payloads over 1 GiB up front, before buffering anything, since the full plaintext is held in memory — use `DecryptTo` or `DecryptFile` for larger payloads, since both stream to their destination instead of buffering.

**Signature**

<SdkVersion language="go" version="0.29.0" source="opentdf" />

```go
func (s SDK) DecryptBytes(ctx context.Context, ciphertext []byte, opts ...TDFReaderOption) ([]byte, error)
```

**Example**

```go
plaintext, err := client.DecryptBytes(ctx, ciphertext)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(plaintext))
```

### DecryptTo

Decrypts a TDF payload held in memory and writes the plaintext to any `io.Writer` — a file, `os.Stdout`, an HTTP response, etc.

**Signature**

<SdkVersion language="go" version="0.29.0" source="opentdf" />

```go
func (s SDK) DecryptTo(ctx context.Context, out io.Writer, ciphertext []byte, opts ...TDFReaderOption) error
```

**Example**

```go
err := client.DecryptTo(ctx, os.Stdout, ciphertext)
if err != nil {
log.Fatal(err)
}
```

### DecryptFile

Decrypts the TDF at `inputPath` and writes the plaintext to `outputPath`. Streams directly from disk rather than buffering the whole ciphertext or plaintext in memory.

**Signature**

<SdkVersion language="go" version="0.29.0" source="opentdf" />

```go
func (s SDK) DecryptFile(ctx context.Context, inputPath, outputPath string, opts ...TDFReaderOption) error
```

**Example**

```go
err := client.DecryptFile(ctx, "secret.tdf", "secret.txt")
if err != nil {
log.Fatal(err)
}
```

Plaintext is written to a temp file next to `outputPath` and only renamed into place once decryption fully succeeds — a pre-existing `outputPath` is never touched, truncated, or deleted if decryption fails partway. If `outputPath` already exists, it's moved aside and restored automatically if the final rename fails, so a failed decrypt never destroys a file that was already there. `inputPath` and `outputPath` must not refer to the same file (including via a hard link); `DecryptFile` rejects that case up front.

**Errors**

`DecryptBytes`, `DecryptTo`, and `DecryptFile` all wrap failures in one of two sentinels, so callers can branch on which stage failed regardless of which helper they called:

| Error | Sentinel | Cause |
|-------|----------|-------|
| Pre-decrypt failure | `sdk.ErrTDFNotDecryptable` | The input itself isn't decryptable — corrupt/invalid TDF bytes, a rejected `TDFReaderOption`, or a schema validation failure. Retrying the same input won't help. |
| Payload too large (`DecryptBytes` only) | `sdk.ErrTDFNotDecryptable` | Plaintext exceeds the 1 GiB in-memory limit. Use `DecryptTo`, `DecryptFile`, or `LoadTDF` directly instead. |
| Decrypt-time failure | `sdk.ErrTDFDecryptFailed` | Failed during decryption itself — most commonly a KAS rewrap failure (not entitled), but also writer errors or payload integrity errors. |

Both are testable with `errors.Is`, and the underlying `LoadTDF`/`WriteTo` error stays reachable through the same chain via `errors.As`. Every failure from all three helpers wraps one of these two sentinels:

```go
plaintext, err := client.DecryptBytes(ctx, ciphertext)
switch {
case errors.Is(err, sdk.ErrTDFNotDecryptable):
// Pre-decrypt: the input itself isn't decryptable. Retrying won't help.
case errors.Is(err, sdk.ErrTDFDecryptFailed):
// Decrypt-time: most commonly a KAS rewrap failure (not entitled), but also writer errors for DecryptTo/DecryptFile.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

---

## IsValidTdf

Checks whether a byte stream contains a valid TDF without decrypting it. Specifically, it validates the ZIP container structure and parses the embedded manifest JSON against the TDF schema. No key access, HMAC verification, or payload decryption is performed. The stream position is restored after the check.
Expand Down
Loading