Signing Key Security Guide

Memory-Only Signing, Key Hygiene, and Language Recommendations
Version:
1.0
Protocol:
SWT3 v1.0
Updated:
April 30, 2026
Classification:
PUBLIC
SWT3 signing keys prove that a specific SDK instance produced a specific anchor. This guide covers how to handle signing keys securely at the application level: memory-only patterns, language-specific considerations, and the Rust SDK recommendation for high-assurance environments.

1. Memory-Only Signing

The signing key should exist in application memory only for the duration of the signing operation. It should never be:

1.1 What the SDK does automatically

Both the Python and TypeScript SDKs implement these protections:

1.2 What the developer must do

2. Language-Specific Considerations

2.1 Python

Python strings are immutable and garbage-collected non-deterministically. The signing key may persist in memory after the Witness is stopped until the garbage collector reclaims it. For most compliance scenarios, this is acceptable because:

For high-assurance environments: If you need deterministic memory clearing, use the Rust SDK via PyO3 bindings, or use a bytearray wrapper that zeros on deletion. The standard Python SDK is suitable for all environments up to and including FedRAMP High.

2.2 TypeScript / Node.js

Node.js strings are immutable and managed by V8's garbage collector. Similar to Python, the key may persist in the V8 heap until GC. The crypto.createHmac() function operates on native buffers, not JavaScript strings.

For Node.js specifically:

2.3 Rust (Recommended for High-Assurance)

Rust is the recommended SDK for high-assurance and classified environments. The Rust type system provides guarantees that garbage-collected languages cannot:

The Rust SDK (swt3-ai crate) offers:

# Cargo.toml
[dependencies]
swt3-ai = "0.3.6"

# The crate uses zeroize internally for key material
use swt3_ai::{fingerprint, sign_payload};

let key = "your-signing-key";
let fp = fingerprint("TENANT", "AI-INF.1", 1, 1, 0, timestamp_ms);
let sig = sign_payload(key, &fp, Some("agent-id"));
// key is dropped when it goes out of scope; memory is zeroed

2.4 C# / .NET

The .NET SDK uses System.Security.Cryptography.HMACSHA256 which operates on byte arrays. The SecureString type is deprecated in .NET Core; use byte[] with explicit zeroing via Array.Clear() when done.

2.5 Ruby

Ruby strings are mutable. After signing, you can overwrite the key variable: signing_key.replace("\0" * signing_key.length). This is a best-effort mitigation since the GC may have copied the string internally.

3. Signing Security Tiers

Organizations can choose the appropriate signing security level based on their regulatory environment:

Tier Mode Key Material Suitable For
TIER 1 Unsigned None Development, POC, non-regulated workloads
TIER 2 Static HMAC Shared secret via env var or secret store SOC 2, CMMC Level 1, internal governance, most production AI
TIER 3 OIDC Ephemeral Short-lived identity token (no shared secret) FedRAMP, CMMC Level 2+, cloud-native enterprise
TIER 4 HSM-Backed Hardware security module (FIPS 140-2 Level 3+) Classified, defense, sovereign deployments
Current availability: Tier 1 and Tier 2 are fully implemented. Tier 3 (OIDC) is in design (see OIDC Ephemeral Signing Design). Tier 4 (HSM) is planned for the Sovereign tier when customer demand requires it.

4. Server-Side Key Protection

When a tenant registers a signing key via POST /api/v1/signing-keys, the server never stores the raw key. Instead:

  1. The key is encrypted with AES-256-GCM using a server-side envelope key (SIGNING_KEY_MASTER).
  2. The ciphertext, IV, and authentication tag are stored separately in an encrypted key store.
  3. Row-level security restricts access to the service role only (no anon or authenticated access).
  4. The raw key is never returned by any API endpoint after registration.
  5. The key is decrypted in memory only during signature validation, then discarded.

If the database is compromised, the attacker gets encrypted ciphertext that is useless without the SIGNING_KEY_MASTER environment variable from the server.

5. Key Generation Best Practices

Generate signing keys using cryptographically secure random number generators:

# Python
python3 -c "import secrets; print(secrets.token_hex(32))"

# Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# OpenSSL
openssl rand -hex 32

# Rust
use rand::Rng;
let key: String = rand::thread_rng()
    .sample_iter(&rand::distributions::Alphanumeric)
    .take(64)
    .map(char::from)
    .collect();
Minimum key length: 16 characters (enforced by the server registration endpoint). Recommended: 32+ bytes of hex-encoded randomness (64 characters). Do not use dictionary words, predictable patterns, or keys derived from passwords.

6. When to Use Rust

Use the Rust SDK when any of these apply:

For all other cases, the Python and TypeScript SDKs provide equivalent cryptographic guarantees at the protocol level. The signing algorithm (HMAC-SHA256), fingerprint formula, and clearing engine are identical across all five language implementations.