For AI agents: the complete documentation index is available at https://docs.ovhcloud.com/en/llms.txt, the full documentation bundle is available at https://docs.ovhcloud.com/en/llms-full.txt, and this page is available as Markdown at https://docs.ovhcloud.com/en/guides/manage-and-operate/kms/import-export-keys-byok.md.
Skip to content

Import and export keys on OVHcloud KMS using BYOK

View as Markdown

Find out how to securely import and export encrypted key material with OVHcloud KMS using asymmetric RSA key wrapping (BYOK)

Objective

Bring Your Own Key (BYOK) lets you import and export cryptographic key material with OVHcloud Key Management Service (KMS) without sending it in plaintext. An RSA transport key pair protects the material in transit: the public key wraps it, and only the holder of the private key can unwrap it.

This guide explains how to import and export a key with OVHcloud KMS using asymmetric RSA key wrapping (BYOK).

Info

KMIP BYOK is not available yet. This guide covers only the regional REST API, the OKMS CLI and the Go SDK.

Keys with the HSM protection level follow additional rules, both as transport keys and as the key being imported or exported. See Use BYOK with HSM keys.

Requirements

Instructions

Understand the RSA wrapping ceremony

BYOK RSA key import flow from source to destination KMS

The ceremony involves two environments and an operator:

  • Destination (Dst): your OKMS domain, where the imported key will be stored.
  • Source (Src): the system that currently holds the key material (another KMS, an application, or a tool such as OpenSSL).
  • Operator: the entity with the permissions to access both the OKMS domain and the source of the key to import. This can be a human using the OKMS CLI, or a service acting through the API.
  1. Generate an RSA transport key pair on the OVHcloud KMS with the wrapKey and unwrapKey operations.
  2. Export the public transport key from the OVHcloud KMS.
  3. Import the public transport key in the source environment.
  4. On the source, export the key material wrapped (encrypted) with the transport key using either RSA-OAEP or RSA-OAEP-256.
  5. On the destination, import the wrapped key with POST /api/{okmsId}/v1/servicekey and a wrappedKeys payload. The KMS unwraps the ciphertext with the private transport key and stores the resulting service key.

Keys imported this way have never_extractable set to false, because the material existed outside the KMS before import. To extract a key later in wrapped form, import it with extractable set to true (it cannot be enabled afterwards) and follow Export a wrapped key. See Service key sensitivity attributes for the full attribute list.

Info

You need two values from your OKMS domain to follow this guide: its regional endpoint and its okmsId. The REST API calls carry both in the URL, while the OKMS CLI and the Go SDK take them from their configuration. Both are returned by the following API call:

GET/okms/resource

They are also displayed in the General information tab of your .

Tip

Prefer this ceremony over plaintext key import whenever the key must remain trusted during transit.

Configure IAM for BYOK

Apply a policy to your OKMS domain resource. Its URN is displayed in the General information tab of your . Replace <identity_urn> and <okms_urn> with your values. Create policies via the OVHcloud Control Panel or the OVHcloud API.

BYOK import — read, create or import service keys, and unwrap with a transport key:

{
  "name": "okms-byok-import",
  "description": "Import wrapped keys into an OKMS domain using RSA BYOK",
  "identities": ["<identity_urn>"],
  "resources": [{ "urn": "<okms_urn>" }],
  "action": [
    "okms:apikms:serviceKey/get",
    "okms:apikms:serviceKey/create",
    "okms:apikms:serviceKey/unwrapKey",
    "okms:apikms:serviceKey/import"
  ]
}

BYOK export — import a public transport key, update extractability, and wrap a key for export:

{
  "name": "okms-byok-export",
  "description": "Export wrapped keys from an OKMS domain using RSA BYOK",
  "identities": ["<identity_urn>"],
  "resources": [{ "urn": "<okms_urn>" }],
  "action": [
    "okms:apikms:serviceKey/get",
    "okms:apikms:serviceKey/create",
    "okms:apikms:serviceKey/import",
    "okms:apikms:serviceKey/update",
    "okms:apikms:serviceKey/wrapKey"
  ]
}
Info

For import, serviceKey/unwrapKey must be allowed on the RSA transport key referenced by wrappingKeyId. For export, import the destination public transport key first (create / import), then allow serviceKey/wrapKey on that transport key and serviceKey/get on the key being exported. serviceKey/update is required to set extractable to false after the export.

Create the RSA transport key on the destination KMS

Create an RSA key dedicated to wrapping. For the import ceremony, set both wrapKey and unwrapKey on this key; they are mutually exclusive with sign and verify.

Supported RSA key sizes: 2048, 3072 or 4096 bits.

Info

SOFTWARE is the default protection level, so you do not need to set it.

REST API
OKMS CLI
Go SDK
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "byok-transport-rsa",
    "type": "RSA",
    "size": 2048,
    "operations": ["wrapKey", "unwrapKey"]
  }'

Save the returned key id — you need it as wrappingKeyId during import.

Obtain the public transport key

Export the public part of the transport key and provide it to the source environment. Never share the private key — it remains inside the destination KMS.

REST API
OKMS CLI
Go SDK
curl -X GET "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey/<transport-key-id>?format=jwk" \
  -H "Authorization: Bearer <token>"

The response includes the public key material in the keys array (JWK).

Wrap the source key material

On the source environment, encrypt the key material with the destination public RSA key.

This step happens outside OVHcloud KMS: use a JOSE library on the source environment.

Supported wrapping algorithms (RFC 7518):

AlgorithmDescription
RSA-OAEPRSAES-OAEP with SHA-1
RSA-OAEP-256RSAES-OAEP with SHA-256 (recommended)

keyFormatType describes the format of the plaintext key material before wrapping and after unwrapping. The ciphertext is always a JWE Compact Serialization string (the wrapping algorithm is carried in the JWE header).

keyFormatTypeTypical use
RAWOVHcloud KMS internal key format for SOFTWARE keys, or raw bytes of the AES symmetric key when exporting HSM keys (AES is the only key type that can be exported from the HSM)
JWKJSON Web Key document (RFC 7517)
PKCS1RSA key material in PKCS#1 encoding
PKCS8Private key material in PKCS#8 encoding

After wrapping with the destination public key, send the resulting JWE Compact Serialization ciphertext in the import request. For a Go example of this wrapping step, see End-to-end example in Go.

Import the wrapped key

Call POST /api/{okmsId}/v1/servicekey with a wrappedKeys array instead of plaintext keys.

MethodPathDescription
POST/api/{okmsId}/v1/servicekeyCreate, import, or wrapped-import a service key
FieldRequiredDescription
nameYesDisplay name of the imported key (1–32 characters)
wrappedKeysYes (for BYOK)Array of wrapped key blocks
operationsYesIntended usages of the imported key
extractableNoWhether the key can later be exported (false by default; public-only keys default to true)
protectionLevelNoProtection level of the key: SOFTWARE (default) or HSM. For an import into the HSM, see Import a key into the HSM.
type / size / curveNo (except type for an import into the HSM)Inferred from the decrypted material when wrappedKeys is present. For an import into the HSM, type is required (see Import a key into the HSM).

Each wrappedKeys entry:

FieldRequiredDescription
keyFormatTypeYes (except for an import into the HSM, where it must be omitted)RAW, JWK, PKCS1, or PKCS8
wrappingKeyIdYesUUID of the destination RSA transport key
ciphertextYesEncrypted key material as JWE Compact Serialization. The JWE header must set alg to RSA-OAEP or RSA-OAEP-256 and enc to A256GCM.

You can import a public key on its own (class becomes PUBLIC_KEY) or a private key (the KMS reconstructs the public part; class becomes KEY_PAIR). Symmetric material is stored as SECRET_KEY.

IAM actions on this call: okms:apikms:serviceKey/create, okms:apikms:serviceKey/import, and okms:apikms:serviceKey/unwrapKey on the transport key.

REST API
OKMS CLI
Go SDK
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "imported-aes-byok",
    "operations": ["encrypt", "decrypt"],
    "wrappedKeys": [
      {
        "keyFormatType": "JWK",
        "wrappingKeyId": "<transport-key-id>",
        "ciphertext": "<jwe-compact-serialization>"
      }
    ]
  }'

keyFormatType values: RAW, JWK, PKCS1, or PKCS8.

Verify the imported key

Confirm that the key exists and matches the expected type and operations.

REST API
OKMS CLI
Go SDK
curl -X GET "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey/<imported-key-id>" \
  -H "Authorization: Bearer <token>"

Check that:

  • the key state is active — activate it if your workflow requires it.
  • type, size, and operations match what you intended.
  • sensitivity attributes under attributes reflect an imported key (never_extractable is false).

You can then use the key for encrypt, decrypt, sign, or verify as documented in Using OVHcloud Key Management Service (KMS).

Export a wrapped key

Secure export is import in reverse: OVHcloud KMS encrypts the key material with an RSA transport key and returns only a JWE ciphertext. The private transport key must live in the destination environment — here the external system receiving the key, not OVHcloud KMS — so only that environment can unwrap the material.

Understand the export ceremony

  1. Generate an RSA transport key pair on the destination environment (another KMS or tooling that will receive the key).
  2. Import the destination public transport key into OVHcloud KMS with the wrapKey operation. This key is the wrappingKeyId used at export time.
  3. On OVHcloud KMS, export the service key wrapped by that transport key. The key must have been created or imported with extractable set to true. Set extractable to false afterwards when possible.
  4. Transfer the ciphertext to the destination and unwrap it with the destination private transport key.
Info

Do not generate the export transport key pair on OVHcloud KMS. If the private transport key stayed in OVHcloud KMS, the destination could not unwrap the ciphertext. Only the public half is imported into OVHcloud KMS for wrapping.

The transport key referenced by wrappingKeyId must be an RSA key in the same OKMS domain with the wrapKey usage. To export a key stored in the HSM, see Use BYOK with HSM keys. Sensitive keys (AES/oct and private RSA/EC material) can only be extracted after wrapping; plaintext extraction is not supported for them.

Import the destination public transport key into OVHcloud KMS

Register the destination public key so OVHcloud KMS can wrap the exported key with it. Import it as a public-only RSA key with wrapKey (no private material).

REST API
OKMS CLI
Go SDK
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "byok-export-transport-pub",
    "type": "RSA",
    "operations": ["wrapKey"],
    "keys": [
      {
        "kty": "RSA",
        "n": "<base64url-modulus>",
        "e": "<base64url-exponent>",
        "key_ops": ["wrapKey"]
      }
    ]
  }'

Save the returned key id as wrappingKeyId.

Enable extraction

The key you export must be extractable, and you decide this when you create or import the key. Afterwards, extractable can only be changed from true to false: once set to false, it can never be set back to true. A key created or imported without extractable set to true can therefore never be exported. This rule applies to SOFTWARE and HSM keys alike.

Warning

Enabling extraction reduces the key's protection boundary, and a key created with extractable set to true has never_extractable set to false permanently. Only create an extractable key if you plan to migrate it.

Set extractable to true when creating the key:

REST API
OKMS CLI
Go SDK
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "aes-to-migrate",
    "type": "oct",
    "size": 256,
    "operations": ["encrypt", "decrypt"],
    "extractable": true
  }'

For an import, set "extractable": true in the request body (REST API), add --extractable (OKMS CLI) or pass okms.WithExtractable(true) (Go SDK).

Once the key no longer needs to be exported, for example after a successful wrapped export (see Export the key wrapped by the transport key), disable extraction. This cannot be undone. IAM action: okms:apikms:serviceKey/update.

REST API
OKMS CLI
Go SDK
curl -X PATCH "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey/<key-id>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "extractable": false
  }'

Export the key wrapped by the transport key

Call GET /api/{okmsId}/v1/servicekey/{keyId} with the wrapping query parameters below. When these parameters are present, the API returns wrappedKeys instead of plaintext keys.

Query parameterRequiredDescription
wrappingKeyIdYesUUID of the destination public transport key imported into OVHcloud KMS
wrappedKeyFormatYesRAW, JWK, PKCS1, or PKCS8 — plaintext format before wrapping
wrappingAlgorithmYesRSA-OAEP or RSA-OAEP-256
MethodPathDescription
GET/api/{okmsId}/v1/servicekey/{keyId}Get metadata, or export key material in wrapped form

IAM actions: okms:apikms:serviceKey/get on the exported key, and okms:apikms:serviceKey/wrapKey on the transport key.

REST API
OKMS CLI
Go SDK
curl -X GET "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey/<key-id>?wrappingKeyId=<wrapping-key-id>&wrappedKeyFormat=JWK&wrappingAlgorithm=RSA-OAEP-256" \
  -H "Authorization: Bearer <token>"

Example response shape:

{
  "id": "<key-id>",
  "name": "imported-aes-byok",
  "type": "oct",
  "class": "SECRET_KEY",
  "size": 256,
  "operations": ["encrypt", "decrypt"],
  "wrappedKeys": [
    {
      "keyFormatType": "JWK",
      "wrappingKeyId": "<wrapping-key-id>",
      "ciphertext": "<jwe-compact-serialization>"
    }
  ]
}

Unwrap the ciphertext on the destination

Transfer the ciphertext to the destination environment. Unwrap it with the destination private transport key (the counterpart of the public key you imported into OVHcloud KMS).

  • If the destination is another OKMS domain, import the wrapped material there with wrappedKeys as in Import the wrapped key, using that domain's transport key ID as wrappingKeyId.
  • If the destination is an external system, use its native unwrap / import API with the private transport key and the same keyFormatType / wrapping algorithm you used at export.

Use BYOK with HSM keys

With the HSM protection level, the key material is wrapped and unwrapped inside the HSM: it never appears in plaintext in OVHcloud KMS. The JWE format is the same as for SOFTWARE keys (RSA-OAEP or RSA-OAEP-256 with A256GCM).

Info

The HSM protection level is not available in every region. To check its availability in the region of your OKMS domain, see the protectionLevel list returned for that region by the following API call:

GET/okms/reference/serviceKey

The transport key must be stored in the HSM whenever the imported or exported key is stored there, as summarised below:

OperationTransport keyKey being imported or exported
Import into the HSMHSM RSA key with unwrapKeySymmetric (oct) key or RSA/EC private key
Export from the HSMHSM RSA key with wrapKeySymmetric (oct) key only
Import or export a SOFTWARE keySOFTWARE or HSM RSA keyAny key type, as described in the previous sections

The imports into the HSM described below are only shown for the REST API.

Create an HSM transport key

To import a key into the HSM, create the transport key with the HSM protection level:

REST API
OKMS CLI
Go SDK
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "byok-transport-rsa-hsm",
    "type": "RSA",
    "size": 2048,
    "operations": ["wrapKey", "unwrapKey"],
    "protectionLevel": "HSM"
  }'

Then export its public key and wrap the key material on the source, as described in Obtain the public transport key and Wrap the source key material.

Import a key into the HSM

Because the HSM never exposes the unwrapped material, OVHcloud KMS cannot infer the key type. The import request differs from a SOFTWARE import:

  • Set protectionLevel to HSM. The transport key must also be an HSM key.
  • Declare type: oct, RSA or EC. Do not set size or curve for RSA and EC: the HSM reads them from the key. For oct, size is optional (128, 192 or 256).
  • Do not set keyFormatType in wrappedKeys. The plaintext wrapped in the JWE must be the raw key bytes for an oct key, and the private key in PKCS#8 (DER) for an RSA or EC key.
  • Supported keys: AES 128, 192 or 256 bits, RSA 2048, 3072 or 4096 bits, EC P-256, P-384 or P-521. A public key alone cannot be imported wrapped into the HSM; only plain import is possible.
curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "imported-aes-hsm",
    "type": "oct",
    "operations": ["encrypt", "decrypt"],
    "protectionLevel": "HSM",
    "wrappedKeys": [
      {
        "wrappingKeyId": "<hsm-transport-key-id>",
        "ciphertext": "<jwe-compact-serialization>"
      }
    ]
  }'

An HSM transport key can also unwrap a SOFTWARE key: in that case, follow Import the wrapped key with keyFormatType.

Export a key from the HSM

Only symmetric (oct) keys can be exported from the HSM: the HSM never releases RSA or EC private keys. As with a SOFTWARE key, the key must have been created or imported with extractable set to true, which cannot be enabled afterwards (see Enable extraction).

The transport key must be stored in the HSM. Import the destination public transport key with the HSM protection level:

curl -X POST "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "byok-export-transport-pub-hsm",
    "type": "RSA",
    "operations": ["wrapKey"],
    "protectionLevel": "HSM",
    "keys": [
      {
        "kty": "RSA",
        "n": "<base64url-modulus>",
        "e": "<base64url-exponent>",
        "key_ops": ["wrapKey"]
      }
    ]
  }'

Then export the key with wrappedKeyFormat set to RAW: the HSM always wraps the raw key bytes, so the decrypted JWE payload is the AES key itself (32 bytes for a 256-bit key).

REST API
OKMS CLI
Go SDK
curl -X GET "https://<region>.okms.ovh.net/api/<okmsId>/v1/servicekey/<hsm-key-id>?wrappingKeyId=<hsm-wrapping-key-id>&wrappedKeyFormat=RAW&wrappingAlgorithm=RSA-OAEP-256" \
  -H "Authorization: Bearer <token>"

On the destination, decrypt the JWE with the private transport key as described in Unwrap the ciphertext on the destination.

End-to-end example in Go

The following examples run both ceremonies on an RSA key pair, using the Go SDK for the OVHcloud KMS calls and go-jose for the JWE operations outside OVHcloud KMS. Both examples encode the key material in PKCS#8 (DER), which matches the keyFormatType value PKCS8.

Import a locally generated key

The program generates the key with the Go standard library, wraps it locally with the public transport key, then imports it. The key never reaches OVHcloud KMS in plaintext.

// 1. Create the transport key on OVHcloud KMS and fetch its public part.
transportKey, err := okmsClient.GenerateRSAKeyPair(
  ctx,
  okmsId,
  types.N2048,
  "byok-transport-rsa",
  types.SOFTWARE,
  "",
  []types.CryptographicUsages{types.WrapKey, types.UnwrapKey},
)
if err != nil {
  return err
}
transportPub, err := okmsClient.ExportPublicKey(ctx, okmsId, transportKey.Id)
if err != nil {
  return err
}

// 2. Generate the key to import, and encode it in PKCS#8 (DER).
srcKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
  return err
}
plaintext, err := x509.MarshalPKCS8PrivateKey(srcKey)
if err != nil {
  return err
}

// 3. Wrap the key material as a JWE: RSA-OAEP-256 key wrapping, A256GCM content encryption.
encrypter, err := jose.NewEncrypter(
  jose.A256GCM,
  jose.Recipient{Algorithm: jose.RSA_OAEP_256, Key: transportPub},
  nil,
)
if err != nil {
  return err
}
jwe, err := encrypter.Encrypt(plaintext)
if err != nil {
  return err
}
ciphertext, err := jwe.CompactSerialize()
if err != nil {
  return err
}

// 4. Import the wrapped key. The KMS unwraps it and infers the type and size from the material.
// It is imported as extractable so that the next example can export it: this cannot be enabled later.
imported, err := okmsClient.ImportWrappedServiceKey(
  ctx,
  okmsId,
  transportKey.Id,
  ciphertext,
  types.PKCS8,
  "imported-rsa-byok",
  "",
  []types.CryptographicUsages{types.Sign, types.Verify},
  okms.WithExtractable(true),
)
if err != nil {
  return err
}
keyID := imported.Id

The JWE content encryption algorithm (enc) must be A256GCM.

Export a key and decrypt the JWE

The destination (here, the local program) generates the transport key pair, imports only its public part into OVHcloud KMS, then decrypts the exported JWE with the private part.

// ID of the service key to export, for example the key imported in the previous example.
keyID := uuid.MustParse("<key-id>")

// 1. Generate the transport key pair on the destination.
destKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
  return err
}

// 2. Import only its public part into OVHcloud KMS, with the wrapKey usage.
wrappingKey, err := okmsClient.ImportKey(
  ctx,
  okmsId,
  &destKey.PublicKey,
  "byok-export-transport-pub",
  "",
  []types.CryptographicUsages{types.WrapKey},
)
if err != nil {
  return err
}

// 3. Export the key wrapped by the destination public key. The key must have been created or
// imported as extractable. Extraction is disabled once the export is done, which cannot be undone.
defer func() {
  extractable := false
  _, _ = okmsClient.UpdateServiceKey(ctx, okmsId, keyID, types.PatchServiceKeyRequest{Extractable: &extractable})
}()
wrapped, err := okmsClient.GetWrappedServiceKey(
  ctx,
  okmsId,
  keyID,
  wrappingKey.Id,
  types.PKCS8,
  types.RSAOAEP256,
)
if err != nil {
  return err
}

// 4. Decrypt the JWE with the destination private key, and parse the PKCS#8 material.
jwe, err := jose.ParseEncrypted(
  wrapped[0].Ciphertext,
  []jose.KeyAlgorithm{jose.RSA_OAEP_256},
  []jose.ContentEncryption{jose.A256GCM},
)
if err != nil {
  return err
}
plaintext, err := jwe.Decrypt(destKey)
if err != nil {
  return err
}

exportedKey, err := x509.ParsePKCS8PrivateKey(plaintext)
if err != nil {
  return err
}
// exportedKey holds the exported key material, used in the rest of the program.

Go further

Using OVHcloud Key Management Service (KMS)

OKMS authentication methods

Getting started with OVHcloud Key Management Service (KMS)

Join our community of users.

Was this page helpful?