---
title: "Sign and Verify with Asymmetric KMS Keys"
description: "Create asymmetric KMS keys, sign messages and verify signatures through the CLI or API."
url: https://community.exoscale.com/product/security/kms/how-to/sign-verify/
product: "Key Management Service (KMS)"
section: product
last_updated: 2026-10-07
---
> For AI agents: the documentation index is at https://community.exoscale.com/llms.txt. Every page is available as markdown at `<page URL>index.md` or with `Accept: text/markdown`.

# 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 Spec | Signing Algorithms |
| ------------------ | ---------------------------------------------------------------- |
| `RSA_3072` | `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512` |
| `RSA_4096` | `RSASSA_PSS_SHA_256`, `RSASSA_PSS_SHA_384`, `RSASSA_PSS_SHA_512` |
| `ECC_NIST_P256` | `ECDSA_SHA_256` |
| `ECC_NIST_P384` | `ECDSA_SHA_384` |
| `ECC_NIST_P521` | `ECDSA_SHA_512` |
| `ECC_EDWARDS25519` | `EDDSA_ED25519`, `ED25519_PH_SHA_512` |
| `ML_DSA_65` | `ML_DSA_SHAKE_256` |
| `ML_DSA_87` | `ML_DSA_SHAKE_256` |

For guidance on choosing a key specification, see the [Overview](https://community.exoscale.com/product/security/kms/overview/index.md#digital-signatures).

> [!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:

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

With the API:

```bash
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:

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

With the API:

```bash
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:

```json
{
  "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:

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

With the CLI:

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

With the API:

```bash
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:

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

With the API:

```bash
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:

```json
{
  "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:

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

With the API:

```bash
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):

```json
{
  "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:

```bash
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:

```bash
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):

```bash
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:

```bash
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`):

```bash
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.

