Skip to content
Sign and Verify with Asymmetric KMS Keys

Sign and Verify with Asymmetric KMS Keys

This guide explains how to create an asymmetric KMS key, sign messages with it, and verify the resulting signatures, either through Exoscale KMS or offline with the exported public key.

How Signing Works

When you create a KMS key with the sign-verify usage, Exoscale KMS generates a public/private key pair. The private key never leaves the KMS unencrypted: all signing operations are performed within the KMS secure boundary. The public key is not secret and can be exported to anyone who needs to verify your signatures.

Every signing key has a key-spec that defines its algorithm, and every sign or verify request must provide a signing-algorithm that matches it:

Key SpecSigning Algorithms
RSA_3072RSASSA_PSS_SHA_256, RSASSA_PSS_SHA_384, RSASSA_PSS_SHA_512
RSA_4096RSASSA_PSS_SHA_256, RSASSA_PSS_SHA_384, RSASSA_PSS_SHA_512
ECC_NIST_P256ECDSA_SHA_256
ECC_NIST_P384ECDSA_SHA_384
ECC_NIST_P521ECDSA_SHA_512
ECC_EDWARDS25519EDDSA_ED25519, ED25519_PH_SHA_512
ML_DSA_65ML_DSA_SHAKE_256
ML_DSA_87ML_DSA_SHAKE_256

For guidance on choosing a key specification, see the Overview.

Note

Asymmetric KMS keys cannot be rotated. To move to a new key pair, create a new KMS key and distribute its public key to your verifiers.

Create a Signing Key

The usage and key-spec are set at creation time and cannot be changed afterwards.

With the CLI:

exo kms key create my-signing-key --usage sign-verify --key-spec ECC_NIST_P256 --zone <zone-name>

With the API:

exo api create-kms-key --zone <zone-name> <<< '{
  "name": "my-signing-key",
  "usage": "sign-verify",
  "key-spec": "ECC_NIST_P256"
}'

The command returns the key’s UUID, which you reference in all subsequent operations.

Sign a Message

Messages must be Base64-encoded.

With the CLI:

exo kms crypto sign <key-uuid> "<base64-encoded-message>" \
  --signing-algorithm ECDSA_SHA_256 \
  --zone <zone-name>

With the API:

exo api sign <key-uuid> --zone <zone-name> <<< '{
  "message": "<base64-encoded-message>",
  "signing-algorithm": "ECDSA_SHA_256"
}'

The response contains the Base64-encoded signature, along with the key specification and signing algorithm used:

{
  "signature": "<base64-encoded-signature>",
  "key-spec": "ECC_NIST_P256",
  "signing-algorithm": "ECDSA_SHA_256"
}

Sign a Digest

To sign larger data, or to avoid sending the original data to the KMS, hash it locally and send only the digest with the message-type set to digest. The hash function must match the signing algorithm (e.g., SHA-256 for ECDSA_SHA_256).

Note

Digests are supported for RSA and ECDSA keys, and for Ed25519 keys with ED25519_PH_SHA_512. EDDSA_ED25519 and ML-DSA hash the message internally and only accept raw messages.

Compute the Base64-encoded SHA-256 digest of a file:

openssl dgst -sha256 -binary my-file.tar.gz | base64

With the CLI:

exo kms crypto sign <key-uuid> "<base64-encoded-digest>" \
  --signing-algorithm ECDSA_SHA_256 \
  --message-type digest \
  --zone <zone-name>

With the API:

exo api sign <key-uuid> --zone <zone-name> <<< '{
  "message": "<base64-encoded-digest>",
  "message-type": "digest",
  "signing-algorithm": "ECDSA_SHA_256"
}'

Verify a Signature with the KMS

Verifying through the KMS saves you from fetching and parsing the public key yourself. The request takes the same message, message-type and signing-algorithm as the sign request, along with the signature to verify.

With the CLI:

exo kms crypto verify <key-uuid> "<base64-encoded-message>" "<base64-encoded-signature>" \
  --signing-algorithm ECDSA_SHA_256 \
  --zone <zone-name>

With the API:

exo api verify <key-uuid> --zone <zone-name> <<< '{
  "message": "<base64-encoded-message>",
  "signature": "<base64-encoded-signature>",
  "signing-algorithm": "ECDSA_SHA_256"
}'

If the signature is valid, the response confirms it:

{
  "signature-valid": true,
  "key-id": "<key-uuid>",
  "key-spec": "ECC_NIST_P256"
}

If the signature does not match the message, the request fails with an invalid signature error.

Verify a Signature Offline

Third parties without access to your Exoscale organization can verify signatures with the exported public key, using standard tooling such as openssl.

Export the Public Key

With the CLI:

exo kms key get-public-key <key-uuid> --zone <zone-name>

With the API:

exo api get-public-key <key-uuid> --zone <zone-name>

The response contains the Base64-encoded public key, DER-encoded as X.509 SubjectPublicKeyInfo (SPKI):

{
  "key-id": "<key-uuid>",
  "key-spec": "ECC_NIST_P256",
  "public-key": "<base64-encoded-public-key>"
}

Convert it to the PEM format expected by most tools:

echo "<base64-encoded-public-key>" | base64 -d > public-key.der
openssl pkey -pubin -inform DER -in public-key.der -out public-key.pem

Verify with OpenSSL

Decode the signature returned by the sign operation:

echo "<base64-encoded-signature>" | base64 -d > signature.bin

Then verify it against the original message, using the command matching your key specification.

ECDSA (ECDSA_SHA_256, ECDSA_SHA_384 or ECDSA_SHA_512, with the matching -sha256, -sha384 or -sha512 flag):

openssl dgst -sha256 -verify public-key.pem -signature signature.bin message.txt

RSA-PSS (RSASSA_PSS_SHA_256, RSASSA_PSS_SHA_384 or RSASSA_PSS_SHA_512, with the matching hash flag). The salt length is equal to the hash output length:

openssl dgst -sha384 \
  -sigopt rsa_padding_mode:pss \
  -sigopt rsa_pss_saltlen:digest \
  -verify public-key.pem -signature signature.bin message.txt

Ed25519 (EDDSA_ED25519):

openssl pkeyutl -verify -pubin -inkey public-key.pem -rawin -in message.txt -sigfile signature.bin

Each command prints Verified OK or Signature Verified Successfully when the signature is valid.

Last updated on