rusty_paseto 0.10.0

A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens.
Documentation
export const metadata = {
  title: 'Key Management',
  description:
    'Learn how to generate, store, and manage cryptographic keys for PASETO tokens.',
}

# Key Management

Different PASETO versions and purposes require different key types. This guide covers key generation, storage, and best practices. {{ className: 'lead' }}

## Key Types Overview

| Version | Local (Symmetric) | Public (Asymmetric) |
|---------|-------------------|---------------------|
| V4 | 32-byte key | Ed25519 keypair |
| V3 | 32-byte key | P-384 ECDSA keypair |
| V2 | 32-byte key | Ed25519 keypair |
| V1 | 32-byte key | RSA keypair |

---

## Symmetric Keys (Local Purpose)

### Generating Random Keys

```rust
use rusty_paseto::prelude::*;

// Generate a random 32-byte key
let key = PasetoSymmetricKey::<V4, Local>::from(
    Key::try_new_random()?
);
```

### From Existing Bytes

```rust
use rusty_paseto::prelude::*;

// From a 32-byte array
let bytes: [u8; 32] = [0u8; 32]; // Your key bytes
let key = PasetoSymmetricKey::<V4, Local>::from(
    Key::<32>::from(bytes)
);

// From a slice (must be exactly 32 bytes)
let slice: &[u8] = &[0u8; 32];
let key = PasetoSymmetricKey::<V4, Local>::from(
    Key::<32>::try_from(slice)?
);
```

### From Hex String

```rust
use rusty_paseto::prelude::*;

let hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
let key = PasetoSymmetricKey::<V4, Local>::from(
    Key::<32>::try_from(hex)?
);
```

---

## Asymmetric Keys (Public Purpose)

### V4/V2 (Ed25519)

```rust
use rusty_paseto::prelude::*;

// Private key is 64 bytes (32-byte seed + 32-byte public key)
let private_bytes: [u8; 64] = /* your private key */;
let private_key = PasetoAsymmetricPrivateKey::<V4, Public>::from(
    &Key::<64>::from(private_bytes)
);

// Public key is 32 bytes
let public_bytes: [u8; 32] = /* your public key */;
let public_key = PasetoAsymmetricPublicKey::<V4, Public>::from(
    &Key::<32>::from(public_bytes)
);
```

### V3 (P-384 ECDSA)

```rust
use rusty_paseto::prelude::*;

// Private key is 48 bytes
let private_key = PasetoAsymmetricPrivateKey::<V3, Public>::from(
    &Key::<48>::try_from(private_hex)?
);

// Public key is 49 bytes (compressed) or 97 bytes (uncompressed)
let public_key = PasetoAsymmetricPublicKey::<V3, Public>::from(
    &Key::<49>::try_from(public_hex)?
);
```

---

## Key Generation Best Practices

### Use Cryptographically Secure Random

```rust
use rusty_paseto::prelude::*;

// Good - uses OS-level CSPRNG
let key = Key::<32>::try_new_random()?;

// Bad - don't use weak random sources
// let key = [rand::random::<u8>(); 32];  // Not recommended
```

### Generate Keys Once, Store Securely

```rust
// Generate once
let key = Key::<32>::try_new_random()?;
let hex = key.to_hex();  // Convert to hex for storage

// Store hex string in secure location:
// - Environment variable
// - Secret manager (AWS Secrets, HashiCorp Vault)
// - Hardware security module (HSM)
```

---

## Key Storage Patterns

### Environment Variables

```rust
use rusty_paseto::prelude::*;
use std::env;

fn get_key() -> Result<PasetoSymmetricKey<V4, Local>, Box<dyn std::error::Error>> {
    let hex = env::var("PASETO_KEY")?;
    let key = PasetoSymmetricKey::<V4, Local>::from(
        Key::<32>::try_from(hex.as_str())?
    );
    Ok(key)
}
```

### Configuration Files

```rust
use rusty_paseto::prelude::*;
use serde::Deserialize;

#[derive(Deserialize)]
struct Config {
    paseto_key_hex: String,
}

fn load_key(config: &Config) -> Result<PasetoSymmetricKey<V4, Local>, Box<dyn std::error::Error>> {
    let key = PasetoSymmetricKey::<V4, Local>::from(
        Key::<32>::try_from(config.paseto_key_hex.as_str())?
    );
    Ok(key)
}
```

---

## Key Rotation

Use footers to identify which key was used:

```rust
use rusty_paseto::prelude::*;
use std::collections::HashMap;

struct KeyManager {
    keys: HashMap<String, PasetoSymmetricKey<V4, Local>>,
    current_key_id: String,
}

impl KeyManager {
    fn create_token(&self, claims: &str) -> Result<String, Box<dyn std::error::Error>> {
        let key = self.keys.get(&self.current_key_id).unwrap();
        let footer = format!(r#"{{"kid":"{}"}}"#, self.current_key_id);

        let token = PasetoBuilder::<V4, Local>::default()
            .set_claim(SubjectClaim::from(claims))
            .set_footer(Footer::from(footer.as_str()))
            .build(key)?;

        Ok(token)
    }

    fn verify_token(&self, token: &str) -> Result<String, Box<dyn std::error::Error>> {
        // Extract key ID from footer
        let footer = Footer::try_from_token(token)?
            .ok_or("missing footer")?;
        let footer_json: serde_json::Value = serde_json::from_str(&footer)?;
        let kid = footer_json["kid"].as_str().ok_or("missing kid")?;

        let key = self.keys.get(kid).ok_or("unknown key")?;

        let payload = PasetoParser::<V4, Local>::default()
            .set_footer(Footer::from(footer.as_str()))
            .parse(token, key)?;

        Ok(payload)
    }

    fn rotate_key(&mut self, new_key_id: String, new_key: PasetoSymmetricKey<V4, Local>) {
        self.keys.insert(new_key_id.clone(), new_key);
        self.current_key_id = new_key_id;
        // Old keys remain for verifying existing tokens
    }
}
```

---

## Security Recommendations

<Note>
  Never commit keys to version control. Use `.gitignore` to exclude key files
  and environment files containing keys.
</Note>

<Properties>
  <Property name="Key Length">
    Always use the full key length (32 bytes for symmetric keys)
  </Property>
  <Property name="Key Derivation">
    If deriving keys from passwords, use a proper KDF (Argon2, scrypt, PBKDF2)
  </Property>
  <Property name="Key Separation">
    Use different keys for different purposes (auth tokens vs. refresh tokens)
  </Property>
  <Property name="Regular Rotation">
    Rotate keys periodically, keeping old keys for token verification
  </Property>
</Properties>