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.
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 | base64With 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.pemVerify with OpenSSL
Decode the signature returned by the sign operation:
echo "<base64-encoded-signature>" | base64 -d > signature.binThen 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.txtRSA-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.txtEd25519 (EDDSA_ED25519):
openssl pkeyutl -verify -pubin -inkey public-key.pem -rawin -in message.txt -sigfile signature.binEach command prints Verified OK or Signature Verified Successfully when the signature is valid.