Key Management

httpsig separates key management from the signing/verification logic through the SigningKey, VerifyingKey, and KeyProvider interfaces. This makes it straightforward to plug in different key storage backends.

Key Interfaces

All languages define the same two core interfaces:

SigningKey holds a private key and can produce signatures:

// Go
type SigningKey interface {
    KeyID() string
    Algorithm() Algorithm
    Sign(data []byte) ([]byte, error)
}
// TypeScript
interface SigningKey {
    keyId: string;
    algorithm: Algorithm;
    sign(data: Uint8Array): Promise<Uint8Array>;
}
// Java
public interface SigningKey {
    String keyId();
    Algorithm algorithm();
    byte[] sign(byte[] data) throws HttpSigException;
}

VerifyingKey holds a public key (or shared secret) and can verify signatures:

// Go
type VerifyingKey interface {
    KeyID() string
    Algorithm() Algorithm
    Verify(data, signature []byte) (bool, error)
}

KeyPair

A KeyPair bundles a SigningKey and VerifyingKey that share the same key ID and algorithm. This is the recommended way to manage keys when you need both sides (e.g., a client that signs requests and verifies responses).

// Auto-detect algorithm from key type, derive public key
kp, err := httpsig.NewKeyPair("my-key-id", privateKey)

// HMAC (symmetric)
kp := httpsig.NewHMACKeyPair("my-key-id", secret)
// Auto-detect algorithm, derive public key
const kp = newKeyPair('my-key-id', privateKeyObject);

// HMAC (symmetric)
const kp = newHMACKeyPair('my-key-id', secret);
// From java.security.KeyPair (auto-detects algorithm)
var kp = Keys.keyPair("my-key-id", jcaKeyPair);

// HMAC (symmetric)
var kp = Keys.hmacKeyPair("my-key-id", secret);
// Static factories per algorithm
let kp = KeyPair.ed25519(keyId: "my-key", privateKey: privKey)
let kp = KeyPair.hmacSHA256(keyId: "my-key", secret: secret)
// From java.security.KeyPair (auto-detects algorithm)
val kp = Keys.keyPair("my-key-id", jcaKeyPair)

// HMAC (symmetric)
val kp = Keys.hmacKeyPair("my-key-id", secret)

Auto-Detection

Instead of choosing an algorithm-specific constructor, you can pass any standard private or public key and let the library detect the algorithm:

signingKey, err := httpsig.NewSigningKeyFromSigner("my-key", signer)
verifyingKey, err := httpsig.NewVerifyingKeyFromPublic("my-key", pubKey)
const signingKey = newSigningKey('my-key', privateKeyObject);
const verifyingKey = newVerifyingKey('my-key', publicKeyObject);
var signingKey = Keys.signingKey("my-key", privateKey);
var verifyingKey = Keys.verifyingKey("my-key", publicKey);
val signingKey = Keys.signingKey("my-key", privateKey)
val verifyingKey = Keys.verifyingKey("my-key", publicKey)

Swift uses per-algorithm KeyPair factories rather than runtime auto-detection, since CryptoKit types are statically typed.

In-Memory Keys

The explicit-algorithm approach. Every language provides factory functions for each algorithm:

Algorithm Go TypeScript Java Swift Kotlin
Ed25519 (sign) NewEd25519SigningKey() newEd25519SigningKey() Keys.ed25519SigningKey() Ed25519SigningKey() Keys.ed25519SigningKey()
Ed25519 (verify) NewEd25519VerifyingKey() newEd25519VerifyingKey() Keys.ed25519VerifyingKey() Ed25519VerifyingKey() Keys.ed25519VerifyingKey()
ECDSA (sign) NewECDSAP256SigningKey() newECDSAP256SigningKey() Keys.ecdsaP256SigningKey() ECDSAP256SigningKey() Keys.ecdsaP256SigningKey()
RSA-PSS (sign) NewRSAPSSSigningKey() newRSAPSSSigningKey() Keys.rsaPSSSigningKey() RSAPSSSigningKey() Keys.rsaPSSSigningKey()
HMAC (both) NewHMACSHA256Key() newHMACSHA256Key() Keys.hmacSHA256Key() HMACSHA256Key() Keys.hmacSHA256Key()

HMAC keys implement both SigningKey and VerifyingKey since the same secret is used for both operations.

HSM and PKCS#11 (Go)

Go's crypto.Signer interface is implemented by most HSM and PKCS#11 libraries. NewSigningKeyFromSigner auto-detects the algorithm from the signer's public key:

// Auto-detect algorithm from the signer's public key type
key, err := httpsig.NewSigningKeyFromSigner("hsm-key-id", hsmSigner)

// Or specify the algorithm explicitly
key, err := httpsig.NewSignerKey("hsm-key-id", httpsig.AlgorithmEd25519, hsmSigner)

This works with any Go library that provides crypto.Signer, including:

Apple Secure Enclave (Swift)

On Apple platforms, the Secure Enclave provides hardware-backed P-256 key storage. Use SecureEnclaveSigningKey for a streamlined API that automatically derives the verifying key:

import HTTPSig
import CryptoKit

let seKey = SecureEnclave.P256.Signing.PrivateKey()
let signingKey = SecureEnclaveSigningKey(keyId: "se-key", privateKey: seKey)
// signingKey.verifyingKey is derived automatically

For lower-level SecKey-based access, you can also use KeyPair.rsaPSS(keyId:secKey:) or the explicit constructors directly.

Android Keystore (Kotlin/Java)

On Android, keys can be stored in the hardware-backed Keystore. Load the PrivateKey from the Keystore and wrap it using the Keys factory:

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator
import java.security.KeyStore
import io.zrz.httpsig.Keys

// Generate a key in the Android Keystore
val keyGen = KeyPairGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"
)
keyGen.initialize(
    KeyGenParameterSpec.Builder("my-key-id", KeyProperties.PURPOSE_SIGN)
        .setDigests(KeyProperties.DIGEST_SHA256)
        .build()
)
keyGen.generateKeyPair()

// Load and use
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val privateKey = keyStore.getKey("my-key-id", null) as java.security.PrivateKey
val signingKey = Keys.signingKey("my-key-id", privateKey) // auto-detects ECDSA P-256

Web Crypto API (TypeScript)

TypeScript's sign/verify operations are async specifically to support the Web Crypto API. Built-in adapters wrap CryptoKey instances:

import { newWebCryptoSigningKey, newWebCryptoVerifyingKey } from '@zourzouvillys/httpsig';

const signingKey = newWebCryptoSigningKey('my-key', cryptoKey, 'ed25519');
const verifyingKey = newWebCryptoVerifyingKey('my-key', cryptoKey, 'ed25519');

The algorithm must be specified explicitly since CryptoKey does not expose a standard type field that maps directly to RFC 9421 algorithm identifiers.

KeyProvider

The KeyProvider is used during verification to resolve a keyId (from the signature metadata) to a VerifyingKey. Implementations can look up keys from a database, JWKS endpoint, file system, or any other source:

// Go: KeyProvider is a function type
provider := func(keyID string, alg httpsig.Algorithm) (httpsig.VerifyingKey, error) {
    key, ok := keyRegistry[keyID]
    if !ok {
        return nil, fmt.Errorf("unknown key: %s", keyID)
    }
    return key, nil
}
// Java: KeyProvider is a @FunctionalInterface
KeyProvider provider = (keyId, algorithm) -> keyRegistry.get(keyId);
// Kotlin: KeyProvider is a fun interface (SAM)
val provider = KeyProvider { keyId, algorithm -> keyRegistry[keyId] }