### JWT Token Example Source: https://github.com/paseto-standard/paseto-spec/blob/master/README.md A standard JSON Web Token example illustrating the header, body, and signature structure. This highlights the 'algorithm agility' approach which can lead to security vulnerabilities. ```text eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ ``` -------------------------------- ### PASETO Token Examples Source: https://github.com/paseto-standard/paseto-spec/blob/master/README.md Examples of PASETO tokens demonstrating both local (shared-key authenticated encryption) and public (public-key digital signature) purposes. These tokens include versioning and optional footers to ensure integrity. ```text v2.local.QAxIpVe-ECVNI1z4xQbm_qQYomyT3h8FtV8bxkz8pBJWkT8f7HtlOpbroPDEZUKop_vaglyp76CzYy375cHmKCW8e1CCkV0Lflu4GTDyXMqQdpZMM1E6OaoQW27gaRSvWBrR3IgbFIa0AkuUFw.UGFyYWdvbiBJbml0aWF0aXZlIEVudGVycHJpc2Vz ``` ```text v2.public.eyJleHAiOiIyMDM5LTAxLTAxVDAwOjAwOjAwKzAwOjAwIiwiZGF0YSI6InRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSJ91gC7-jCWsN3mv4uJaZxZp0btLJgcyVwL-svJD7f4IHyGteKe3HTLjHYTGHI1MtCqJ-ESDLNoE7otkIzamFskCA ``` -------------------------------- ### PASETO Key Generation and Validation in C (Procedural) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/03-Algorithm-Lucidity.md Illustrates key generation and validation for PASETO in procedural languages using C. It defines key types using an enum and generates keys with embedded headers for version and purpose. Validation checks the header to ensure the key matches the expected token type. ```c #include "sodium.h" enum KeyHeaders { V3_LOCAL, V3_LOCAL, V3_PUBLIC }; unsigned char* paseto_v3_local_keygen() { unsigned char out[33]; out[0] = (unsigned char) V3_LOCAL & 0xff; randombytes_buf(out + 1, 32); return out; } unsigned char* paseto_v4_local_keygen() { unsigned char out[33]; out[0] = (unsigned char) V4_LOCAL & 0xff; randombytes_buf(out + 1, 32); return out; } unsigned char* paseto_v4_public_keygen() { unsigned char out[65]; unsigned char tmp[32]; out[0] = (unsigned char) V4_PUBLIC & 0xff; crypto_sign_keypair(tmp, out + 1); return out; } int paseto_v4_local_decrypt(unsigned char* out, const unsigned char* in, const unsigned char* key) { if (key[0] != V4_LOCAL) { return -1; /* Wrong version or purpose */ } } ``` -------------------------------- ### Derive Encryption and Authentication Keys using HKDF Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md Splits a master key into separate encryption and authentication keys using HKDF-SHA384. This process uses the leftmost 16 bytes of the nonce as a salt and specific info strings for key derivation. ```pseudocode Ek = hkdf_sha384( len = 32, ikm = k, info = "paseto-encryption-key", salt = n[0:16] ); Ak = hkdf_sha384( len = 32, ikm = k, info = "paseto-auth-key-for-aead", salt = n[0:16] ); ``` -------------------------------- ### Generate Paseto v4 Local and Public Keys (C) Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Implements functions in C to generate v4.local symmetric keys and v4.public keypairs. It prepends type identifiers to the generated key material for runtime algorithm clarity and includes a basic check for key header validation during decryption. ```c #include "sodium.h" // Key type headers enum KeyHeaders { V3_LOCAL = 0x01, V4_LOCAL = 0x02, V4_PUBLIC = 0x03 }; // Generate v4.local symmetric key (32 bytes + 1 byte header) unsigned char* paseto_v4_local_keygen() { unsigned char* out = malloc(33); out[0] = (unsigned char) V4_LOCAL & 0xff; randombytes_buf(out + 1, 32); return out; } // Generate v4.public keypair (64 bytes secret + 1 byte header) unsigned char* paseto_v4_public_keygen() { unsigned char* out = malloc(65); unsigned char pk[32]; out[0] = (unsigned char) V4_PUBLIC & 0xff; crypto_sign_keypair(pk, out + 1); return out; } // Verify key type before decryption int paseto_v4_local_decrypt(unsigned char* out, const unsigned char* token, const unsigned char* key) { // MUST check key header before proceeding if (key[0] != V4_LOCAL) { return -1; // Wrong version or purpose - reject immediately } // Proceed with decryption using key material at key+1 // ... decryption logic ... return 0; } ``` -------------------------------- ### PASETO Key Type Separation in Java (OOP) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/03-Algorithm-Lucidity.md Demonstrates strict type separation for PASETO keys in object-oriented languages like Java. It defines abstract Key class and concrete SymmetricKey, AsymmetricSecretKey, and AsymmetricPublicKey subclasses, each parametrized by key material and version, enforcing validation for specific purposes. ```java public enum Version { V1, V2, V3, V4 }; public enum Purpose { PURPOSE_LOCAL, PURPOSE_PUBLIC }; abstract class Key { protected byte[] material; protected Version version; public Key(byte[] keyMaterial, Version version) { } abstract public bool isKeyValidFor(Version v, Purpose p); /* ... */ } class SymmetricKey extends Key { public SymmetricKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public bool isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_LOCAL; } } class AsymmetricSecretKey extends Key { public SymmetricKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public bool isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_PUBLIC; } } class AsymmetricPublicKey extends Key { public SymmetricKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public bool isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_PUBLIC; } } ``` -------------------------------- ### Derive Encryption and Authentication Keys Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md Derives the encryption key (Ek), counter nonce (n2), and authentication key (Ak) from a master key and random nonce using BLAKE2b. ```pseudocode tmp = crypto_generichash( msg = "paseto-encryption-key" || n, key = key, length = 56 ); Ek = tmp[0:32] n2 = tmp[32:] Ak = crypto_generichash( msg = "paseto-auth-key-for-aead" || n, key = key, length = 32 ); ``` -------------------------------- ### Pre-Authentication Encoding (PAE) Definition and Implementation Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Common.md PAE is used to encode multi-part messages before cryptographic operations. It involves encoding the number of pieces and the length of each piece using LE64, then concatenating them. This prevents canonicalization attacks. ```javascript function LE64(n) { var str = ''; for (var i = 0; i < 8; ++i) { if (i === 7) { // Clear the MSB for interoperability n &= 127; } str += String.fromCharCode(n & 255); n = n >>> 8; } return str; } function PAE(pieces) { if (!Array.isArray(pieces)) { throw TypeError('Expected an array.'); } var count = pieces.length; var output = LE64(count); for (var i = 0; i < count; i++) { output += LE64(pieces[i].length); output += pieces[i]; } return output; } ``` -------------------------------- ### Key Derivation using HKDF-SHA384 Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md Derives encryption and authentication keys from a master key and a nonce using HKDF-SHA384. This process splits the master key into an encryption key (Ek), a counter nonce (n2), and an authentication key (Ak). ```pseudocode tmp = hkdf_sha384( len = 48, ikm = k, info = "paseto-encryption-key" || n, salt = NULL ); Ek = tmp[0:32] n2 = tmp[32:] Ak = hkdf_sha384( len = 48, ikm = k, info = "paseto-auth-key-for-aead" || n, salt = NULL ); ``` -------------------------------- ### Perform Symmetric Encryption and Decryption with Version 2 (Sodium) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/README.md Utilizes the libsodium AEAD XChaCha20-Poly1305 implementation for symmetric encryption. This version relies on a 192-bit nonce and 256-bit key, with the nonce derived from a BLAKE2b hash of the message. ```php // Encryption $ciphertext = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($message, $additionalData, $nonce, $key); // Decryption $plaintext = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additionalData, $nonce, $key); ``` -------------------------------- ### Encrypt and Decrypt using XChaCha20-Poly1305 Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md Demonstrates the AEAD encryption and decryption process for PASETO v2.local tokens. These operations require a 256-bit key and utilize a nonce derived from the message and a CSPRNG. ```pseudocode c = crypto_aead_xchacha20poly1305_encrypt( message = m, aad = preAuth, nonce = n, key = k ); ``` ```pseudocode p = crypto_aead_xchacha20poly1305_decrypt( ciphertext = c, aad = preAuth, nonce = n, key = k ); ``` -------------------------------- ### v4.local Encrypt using Python Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Encrypts a message using the PASETO v4.local protocol, which employs XChaCha20 for encryption and BLAKE2b-MAC for authentication. It requires a 32-byte symmetric key and supports an optional footer and implicit assertions. The output is a base64url-encoded token. ```python import nacl.bindings import json import os import base64 def v4_local_encrypt(message: dict, key: bytes, footer: str = "", implicit: str = "") -> str: """ Encrypt a message using PASETO v4.local Args: message: JSON-serializable payload key: 32-byte symmetric key footer: Optional authenticated footer (cleartext) implicit: Optional implicit assertion (not stored in token) """ assert len(key) == 32, "Key must be 256 bits (32 bytes)" h = b"v4.local." m = json.dumps(message).encode('utf-8') # Generate 32-byte random nonce n = os.urandom(32) # Derive encryption key (Ek) and nonce (n2) tmp = nacl.bindings.crypto_generichash_blake2b_salt_personal( b"paseto-encryption-key" + n, key=key, digest_size=56 ) Ek = tmp[:32] # Encryption key n2 = tmp[32:] # Counter nonce (24 bytes) # Derive authentication key (Ak) Ak = nacl.bindings.crypto_generichash_blake2b_salt_personal( b"paseto-auth-key-for-aead" + n, key=key, digest_size=32 ) # Encrypt with XChaCha20 c = nacl.bindings.crypto_stream_xchacha20_xor(m, n2, Ek) # Pre-Authentication Encoding (PAE) pre_auth = PAE([h, n, c, footer.encode(), implicit.encode()]) # Calculate BLAKE2b-MAC t = nacl.bindings.crypto_generichash_blake2b_salt_personal( pre_auth, key=Ak, digest_size=32 ) # Assemble token payload = base64url_encode(n + c + t) if footer: return f"{h.decode()}{payload}.{base64url_encode(footer.encode())}" return f"{h.decode()}{payload}" # Example usage key = bytes.fromhex('707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f') message = { "sub": "user123", "exp": "2039-01-01T00:00:00+00:00", "data": "sensitive payload" } token = v4_local_encrypt(message, key, footer='{"kid":"key-001"}') # Output: v4.local.BASE64URL_ENCODED_PAYLOAD.BASE64URL_ENCODED_FOOTER ``` -------------------------------- ### Calculate Authentication Tag Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md Computes the BLAKE2b-MAC of the pre-authenticated data (PAE) using the derived authentication key. ```pseudocode t = crypto_generichash( message = preAuth, key = Ak, length = 32 ); ``` -------------------------------- ### Pre-Authentication Encoding (PAE) in JavaScript Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Implements Pre-Authentication Encoding (PAE) to securely combine multiple message parts with length prefixes. This prevents canonicalization attacks by ensuring the authentication covers all components. It relies on a helper function LE64 for little-endian encoding of 64-bit unsigned integers. ```javascript /** * LE64 - Encode a 64-bit unsigned integer as little-endian bytes * MSB must be cleared for cross-platform compatibility */ function LE64(n) { var str = ''; for (var i = 0; i < 8; ++i) { if (i === 7) { n &= 127; // Clear MSB for interoperability } str += String.fromCharCode(n & 255); n = n >>> 8; } return str; } /** * PAE - Pre-Authentication Encoding * Combines multiple strings with length prefixes to prevent collision attacks */ function PAE(pieces) { if (!Array.isArray(pieces)) { throw TypeError('Expected an array.'); } var count = pieces.length; var output = LE64(count); for (var i = 0; i < count; i++) { output += LE64(pieces[i].length); output += pieces[i]; } return output; } // Example outputs: // PAE([]) => "\x00\x00\x00\x00\x00\x00\x00\x00" // PAE(['']) => "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // PAE(['test'])=> "\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00test" // Usage in token construction: const preAuth = PAE([ 'v4.local.', // Header nonce, // 32-byte nonce ciphertext, // Encrypted message footer, // Optional footer implicitAssertion // Optional implicit assertion ]); ``` -------------------------------- ### Sign Message using Ed25519 Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md Illustrates the signing process for PASETO v2.public tokens. The message is prepared using PAE before being signed with an Ed25519 secret key. ```pseudocode sig = crypto_sign_detached( message = m2, private_key = sk ); ``` -------------------------------- ### Sign Message with RSA PSS (v1.public) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md Signs a message using RSA PSS with a secret key for v1.public PASETO tokens. It requires a message, a 2048-bit RSA secret key, and an optional footer. The output is a signed token, potentially including the footer. ```pseudocode sig = crypto_sign_rsa( message = m2, private_key = sk, padding_mode = "pss", public_exponent = 65537, hash = "sha384" mgf = "mgf1+sha384" ); ``` -------------------------------- ### Encrypt Message with AES-256-CTR Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md Performs encryption on a message using the derived encryption key and the rightmost 16 bytes of the nonce as the initialization vector. ```pseudocode c = aes256ctr_encrypt( plaintext = m, nonce = n[16:], key = Ek ); ``` -------------------------------- ### Perform Asymmetric Signing and Verification with Version 2 and 4 (Ed25519) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/README.md Implements Ed25519 digital signatures for asymmetric authentication. These functions generate and verify detached signatures to ensure message integrity and authenticity. ```php // Signing $signature = sodium_crypto_sign_detached($message, $secretKey); // Verification $isValid = sodium_crypto_sign_verify_detached($signature, $message, $publicKey); ``` -------------------------------- ### Sign PASETO v4.public Token (Python) Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Creates a signed PASETO v4.public token using Ed25519 digital signatures. It serializes the message, calculates the signature over the pre-authenticated message, and constructs the token, optionally including a footer. Requires a 64-byte secret key. ```python import nacl.signing import json from base64 import urlsafe_b64encode as base64url_encode from paseto.helpers import PAE def v4_public_sign(message: dict, secret_key: bytes, footer: str = "", implicit: str = "") -> str: """ Sign a message using PASETO v4.public (Ed25519) Args: message: JSON-serializable payload secret_key: 64-byte Ed25519 secret key (seed + public key) footer: Optional authenticated footer implicit: Optional implicit assertion """ h = b"v4.public." m = json.dumps(message).encode('utf-8') # Pre-Authentication Encoding m2 = PAE([h, m, footer.encode(), implicit.encode()]) # Sign with Ed25519 signing_key = nacl.signing.SigningKey(secret_key[:32]) sig = signing_key.sign(m2).signature # Assemble token payload = base64url_encode(m + sig) if footer: return f"{h.decode()}{payload}.{base64url_encode(footer.encode())}" return f"{h.decode()}{payload}" # Example usage # signing_key = nacl.signing.SigningKey.generate() # message = { # "sub": "user456", # "iss": "auth.example.com", # "aud": "api.example.com", # "exp": "2039-01-01T00:00:00+00:00", # "iat": "2024-01-15T12:00:00Z", # "roles": ["admin", "user"] # } # public_token = v4_public_sign(message, bytes(signing_key)) # Output: v4.public.eyJzdWIiOiJ1c2VyNDU2Ii...SIGNATURE ``` -------------------------------- ### Verify Signature with RSA PSS (v1.public) Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md Verifies a signed message against an RSA public key for v1.public PASETO tokens. It takes the signed message, public key, and an optional footer. The function returns the original message if the signature is valid, otherwise throws an exception. ```pseudocode valid = crypto_sign_rsa_verify( signature = s, message = m2, public_key = pk, padding_mode = "pss", public_exponent = 65537, hash = "sha384" mgf = "mgf1+sha384" ); ``` -------------------------------- ### Algorithm Lucidity (Key Type Safety) in Java Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Implements an object-oriented approach in Java to ensure algorithm lucidity by enforcing strict type separation between different key types. This prevents algorithm confusion attacks by validating keys against specific PASETO versions and purposes. ```java // Object-oriented implementation for key type safety public enum Version { V1, V2, V3, V4 } public enum Purpose { PURPOSE_LOCAL, PURPOSE_PUBLIC } abstract class Key { protected byte[] material; protected Version version; public Key(byte[] keyMaterial, Version version) { this.material = keyMaterial; this.version = version; } abstract public boolean isKeyValidFor(Version v, Purpose p); } class SymmetricKey extends Key { public SymmetricKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public boolean isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_LOCAL; } } class AsymmetricSecretKey extends Key { public AsymmetricSecretKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public boolean isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_PUBLIC; } } class AsymmetricPublicKey extends Key { public AsymmetricPublicKey(byte[] keyMaterial, Version version) { super(keyMaterial, version); } public boolean isKeyValidFor(Version v, Purpose p) { return v == this.version && p == Purpose.PURPOSE_PUBLIC; } } // Usage in PASETO operations public String encrypt(String message, Key key) { if (!key.isKeyValidFor(Version.V4, Purpose.PURPOSE_LOCAL)) { throw new InvalidKeyException("Key not valid for v4.local"); } // Proceed with encryption... } ``` -------------------------------- ### Base64url Encoding and Decoding for PASETO Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Common.md PASETO tokens use Base64url encoding without padding for most components. Implementations must strictly enforce the absence of padding characters and handle trailing bits correctly during decoding. -------------------------------- ### Decrypt PASETO v4.local Token (Python) Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Decrypts and verifies a v4.local PASETO token using a symmetric key. It checks the BLAKE2b-MAC authentication tag before returning the plaintext JSON payload. Requires a 32-byte key and optionally accepts an implicit assertion. ```python import nacl.bindings import json from base64 import urlsafe_b64decode as base64url_decode from paseto.helpers import PAE, constant_time_compare def v4_local_decrypt(token: str, key: bytes, implicit: str = "") -> dict: """ Decrypt a PASETO v4.local token Args: token: The PASETO token string key: 32-byte symmetric key implicit: Optional implicit assertion for verification Returns: Decrypted JSON payload as dictionary Raises: ValueError: If token is invalid or authentication fails """ assert len(key) == 32, "Key must be 256 bits (32 bytes)" h = b"v4.local." # Verify header if not token.startswith("v4.local."): raise ValueError("Invalid token header") # Split token and extract footer parts = token[9:].split(".") payload = base64url_decode(parts[0]) footer = base64url_decode(parts[1]) if len(parts) > 1 else b"" # Extract components n = payload[:32] # Nonce t = payload[-32:] # Authentication tag c = payload[32:-32] # Ciphertext # Derive keys (same as encryption) tmp = nacl.bindings.crypto_generichash_blake2b_salt_personal( b"paseto-encryption-key" + n, key=key, digest_size=56 ) Ek = tmp[:32] n2 = tmp[32:] Ak = nacl.bindings.crypto_generichash_blake2b_salt_personal( b"paseto-auth-key-for-aead" + n, key=key, digest_size=32 ) # Verify authentication tag pre_auth = PAE([h, n, c, footer, implicit.encode()]) t2 = nacl.bindings.crypto_generichash_blake2b_salt_personal( pre_auth, key=Ak, digest_size=32 ) if not constant_time_compare(t, t2): raise ValueError("Authentication failed") # Decrypt plaintext = nacl.bindings.crypto_stream_xchacha20_xor(c, n2, Ek) return json.loads(plaintext.decode('utf-8')) # Example usage # decrypted = v4_local_decrypt(token, key) # Output: {"sub": "user123", "exp": "2039-01-01T00:00:00+00:00", "data": "sensitive payload"} ``` -------------------------------- ### AES-256-CTR Encryption Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md Encrypts the message using the derived encryption key (Ek) and the counter nonce (n2) extracted from the HKDF derivation. ```pseudocode c = aes256ctr_encrypt( plaintext = m, nonce = n2, key = Ek ); ``` -------------------------------- ### ECDSA Public Key Point Compression Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md Compresses an ECDSA public key (X, Y coordinates) into a 49-byte format suitable for PASETO. It determines the header byte based on the least significant bit of the Y coordinate. ```pseudocode lsb(y): return y[y.length - 1] & 1 pubKeyCompress(x, y): header = [0x02 + lsb(y)] return header.concat(x) ``` -------------------------------- ### Python PASETO V4 Local and Public Parsers Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Implements abstract base class PasetoParser with concrete V4LocalParser and V4PublicParser. These parsers handle token validation, including required claims and expiration, for specific PASETO versions and purposes. They require a key and optionally accept a default expiration time. ```python from abc import ABC, abstractmethod from datetime import datetime, timedelta import os # Assuming os is used for key generation in usage example # Mock functions for demonstration purposes def v4_local_decrypt(token, key): # In a real implementation, this would decrypt the token print(f"Decrypting token: {token} with key: {key}") return {"iss": "auth.example.com", "aud": "api.example.com", "exp": (datetime.now() + timedelta(hours=1)).isoformat() + "Z"} def v4_public_verify(token, key): # In a real implementation, this would verify the token signature print(f"Verifying token: {token} with key: {key}") return {"iss": "auth.example.com", "aud": "api.example.com", "exp": (datetime.now() + timedelta(hours=1)).isoformat() + "Z"} # Mock signing_key for demonstration purposes class MockSigningKey: def __init__(self): self.verify_key = b'mock_public_key_bytes' signing_key = MockSigningKey() class PasetoParser(ABC): """Base parser class - one instance per (version, purpose) tuple""" def __init__(self, key, default_expiration: timedelta = timedelta(hours=1)): self.key = key self.default_expiration = default_expiration self.required_claims = set() @abstractmethod def parse(self, token: str) -> dict: pass def require_claim(self, claim: str, value=None): """Add claim validation rule""" self.required_claims.add((claim, value)) return self class V4LocalParser(PasetoParser): """Parser for v4.local tokens only""" def parse(self, token: str) -> dict: if not token.startswith("v4.local."): raise ValueError("This parser only accepts v4.local tokens") payload = v4_local_decrypt(token, self.key) # Validate required claims for claim, expected in self.required_claims: if claim not in payload: raise ValueError(f"Missing required claim: {claim}") if expected and payload[claim] != expected: raise ValueError(f"Claim {claim} does not match expected value") # Validate expiration if 'exp' in payload: # Ensure correct timezone handling for ISO format strings ending with Z exp_str = payload['exp'].replace('Z', '+00:00') try: exp = datetime.fromisoformat(exp_str) # Ensure comparison is timezone-aware if possible now = datetime.now(exp.tzinfo if exp.tzinfo else None) if exp < now: raise ValueError("Token has expired") except ValueError as e: raise ValueError(f"Invalid expiration format: {payload['exp']} - {e}") return payload class V4PublicParser(PasetoParser): """Parser for v4.public tokens only""" def parse(self, token: str) -> dict: if not token.startswith("v4.public."): raise ValueError("This parser only accepts v4.public tokens") payload = v4_public_verify(token, self.key) # ... validation logic ... # Validate required claims (example) for claim, expected in self.required_claims: if claim not in payload: raise ValueError(f"Missing required claim: {claim}") if expected and payload[claim] != expected: raise ValueError(f"Claim {claim} does not match expected value") # Validate expiration (example) if 'exp' in payload: exp_str = payload['exp'].replace('Z', '+00:00') try: exp = datetime.fromisoformat(exp_str) now = datetime.now(exp.tzinfo if exp.tzinfo else None) if exp < now: raise ValueError("Token has expired") except ValueError as e: raise ValueError(f"Invalid expiration format: {payload['exp']} - {e}") return payload # Usage: Separate parsers for each token type local_key = os.urandom(32) public_key_bytes = bytes(signing_key.verify_key) local_parser = V4LocalParser(local_key) local_parser.require_claim("iss", "auth.example.com") local_parser.require_claim("aud", "api.example.com") public_parser = V4PublicParser(public_key_bytes) public_parser.require_claim("iss", "auth.example.com") # Mock tokens for demonstration incoming_local_token = "v4.local.ey..." incoming_public_token = "v4.public.ey..." # Parse tokens with appropriate parser try: claims = local_parser.parse(incoming_local_token) print("Local token parsed successfully:", claims) except ValueError as e: print(f"Local token validation failed: {e}") try: claims = public_parser.parse(incoming_public_token) print("Public token parsed successfully:", claims) except ValueError as e: print(f"Public token validation failed: {e}") ``` -------------------------------- ### Decrypt Message with AES-256-CTR Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md Decrypts the ciphertext using the derived encryption key and the rightmost 16 bytes of the nonce. This operation is performed after the authentication tag has been verified. ```pseudocode return aes256ctr_decrypt( cipherext = c, nonce = n[16:], key = Ek ); ``` -------------------------------- ### AES-256-CTR Decryption for PASETO v3 Local Tokens Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md Performs the final decryption of the ciphertext using the AES-256-CTR algorithm. This step requires the derived encryption key (Ek) and the nonce (n2) generated during key derivation. ```pseudocode return aes256ctr_decrypt( cipherext = c, nonce = n2 key = Ek ); ``` -------------------------------- ### Encrypt Message with XChaCha20 Source: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md Performs stream encryption on the message using the derived encryption key and counter nonce. ```pseudocode c = crypto_stream_xchacha20_xor( message = m, nonce = n2, key = Ek ); ``` -------------------------------- ### Verify PASETO v4.public Token (Python) Source: https://context7.com/paseto-standard/paseto-spec/llms.txt Verifies an Ed25519 signed PASETO v4.public token. It reconstructs the pre-authenticated message and compares the signature against the provided public key. Returns the verified JSON payload if the signature is valid. Requires a 32-byte public key. ```python import nacl.signing import nacl.exceptions import json from base64 import urlsafe_b64decode as base64url_decode from paseto.helpers import PAE def v4_public_verify(token: str, public_key: bytes, implicit: str = "") -> dict: """ Verify a PASETO v4.public token Args: token: The PASETO token string public_key: 32-byte Ed25519 public key implicit: Optional implicit assertion for verification Returns: Verified JSON payload as dictionary Raises: ValueError: If signature verification fails """ h = b"v4.public." if not token.startswith("v4.public."): raise ValueError("Invalid token header") parts = token[10:].split(".") payload = base64url_decode(parts[0]) footer = base64url_decode(parts[1]) if len(parts) > 1 else b"" # Extract message and signature s = payload[-64:] # 64-byte Ed25519 signature m = payload[:-64] # Message # Reconstruct signed message m2 = PAE([h, m, footer, implicit.encode()]) # Verify signature verify_key = nacl.signing.VerifyKey(public_key) try: verify_key.verify(m2, s) except nacl.exceptions.BadSignature: raise ValueError("Signature verification failed") return json.loads(m.decode('utf-8')) # Example usage # public_key = signing_key.verify_key # payload = v4_public_verify(public_token, bytes(public_key)) # Output: {"sub": "user456", "iss": "auth.example.com", ...} ```