Skip to content

Generate Key Pairs ​

Bahn requires two P-256 (EC) key pairs per merchant:

Key pairusePurpose
SigningsigYou sign every request body with your private key; Bahn verifies it with your registered public key.
EncryptionencBahn encrypts responses with your public key; you decrypt them with your private key.

This guide generates both pairs and writes them to JWK files using Go and the go-jose library.


1. Set Up ​

Create a new directory and install dependencies:

bash
mkdir keygen && cd keygen
go mod init keygen
go get github.com/go-jose/go-jose/v4
go get github.com/google/uuid

2. Generate ​

Create main.go:

go
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"encoding/json"
	"log"
	"os"

	"github.com/go-jose/go-jose/v4"
	"github.com/google/uuid"
)

func main() {
	// sig keypair
	sigKID := uuid.NewString()
	sigPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		log.Fatalf("failed to generate sig key: %v", err)
	}

	// enc keypair
	encKID := uuid.NewString()
	encPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		log.Fatalf("failed to generate enc key: %v", err)
	}

	// private JWKS
	privSet := jose.JSONWebKeySet{
		Keys: []jose.JSONWebKey{
			{Key: sigPriv, KeyID: sigKID, Algorithm: string(jose.ES256), Use: "sig"},
			{Key: encPriv, KeyID: encKID, Algorithm: string(jose.ES256), Use: "enc"},
		},
	}

	// public JWKS
	sigPub := sigPriv.PublicKey
	encPub := encPriv.PublicKey
	pubSet := jose.JSONWebKeySet{
		Keys: []jose.JSONWebKey{
			{Key: &sigPub, KeyID: sigKID, Algorithm: string(jose.ES256), Use: "sig"},
			{Key: &encPub, KeyID: encKID, Algorithm: string(jose.ES256), Use: "enc"},
		},
	}

	privBytes, err := json.MarshalIndent(privSet, "", "  ")
	if err != nil {
		log.Fatalf("failed to marshal private jwks: %v", err)
	}
	pubBytes, err := json.MarshalIndent(pubSet, "", "  ")
	if err != nil {
		log.Fatalf("failed to marshal public jwks: %v", err)
	}

	if err := os.WriteFile("private_key.json", privBytes, 0600); err != nil {
		log.Fatalf("failed to write private_key.json: %v", err)
	}
	if err := os.WriteFile("public_key.json", pubBytes, 0644); err != nil {
		log.Fatalf("failed to write public_key.json: %v", err)
	}

	log.Printf("generated sig kid=%s, enc kid=%s", sigKID, encKID)
}

Run it once:

bash
go run main.go
# generated sig kid=00b06c00-..., enc kid=acb9b4c7-...

3. Output ​

Two files are written to the current directory:

FileContentsPermission
private_key.jsonBoth private keys. Never share or commit this file.0600
public_key.jsonBoth public keys. Submit to Bahn during onboarding.0644

public_key.json

json
{
  "keys": [
    {
      "use": "sig",
      "kty": "EC",
      "kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
      "crv": "P-256",
      "alg": "ES256",
      "x": "S1sGGCWsPQW8PDLKSZn1zOUzWH0GDGaHXFjeTphY5WA",
      "y": "pf74XAcJ33zwp_w1O57qJylhlej3mBvFOzwe29s6Jsg"
    },
    {
      "use": "enc",
      "kty": "EC",
      "kid": "acb9b4c7-aa0c-450b-9ebf-a0dc10a5d4b4",
      "crv": "P-256",
      "alg": "ES256",
      "x": "ge6PdsBMu9y9x_bvMg1g4K8CJ8j6xR_2_jyb0xYn5ss",
      "y": "rxE1YRebjiKyPHBRCbLrNHjTei42b2nasfOvhnvvk78"
    }
  ]
}

private_key.json

json
{
  "keys": [
    {
      "use": "sig",
      "kty": "EC",
      "kid": "00b06c00-3278-4f09-bf9b-6825cf889131",
      "crv": "P-256",
      "alg": "ES256",
      "x": "S1sGGCWsPQW8PDLKSZn1zOUzWH0GDGaHXFjeTphY5WA",
      "y": "pf74XAcJ33zwp_w1O57qJylhlej3mBvFOzwe29s6Jsg",
      "d": "h6s7t1LWWSb_-P2H25dZ_T-EyxCkZdDSVSg-BhXYKNs"
    },
    {
      "use": "enc",
      "kty": "EC",
      "kid": "acb9b4c7-aa0c-450b-9ebf-a0dc10a5d4b4",
      "crv": "P-256",
      "alg": "ES256",
      "x": "ge6PdsBMu9y9x_bvMg1g4K8CJ8j6xR_2_jyb0xYn5ss",
      "y": "rxE1YRebjiKyPHBRCbLrNHjTei42b2nasfOvhnvvk78",
      "d": "Uv_5PRnWdrtQQzv_nzowArZ3m2y7pbpeOrc7abkaqbk"
    }
  ]
}

The d field is the private scalar — it is present only in private_key.json and is what allows you to sign and decrypt.

WARNING

private_key.json contains sensitive key material. Store it in a secrets manager or encrypted vault and add it to .gitignore. Never share it with Bahn or anyone else.


Next Steps ​

  • Submit public_key.json to Bahn and note both kid values — you will use them in every JWS and JWE header.
  • Continue to Make Your First Request to sign and send your first API call.

Developers