Skip to main content

Data Encryption with Keyring

Data encryption is a key consideration of APISIX. Sensitive information, such as user passwords and private keys, is often present in gateway configurations when implementing authentication or integrating with another system. APISIX can encrypt this information before storage and decrypt it with the configured keyring when the gateway needs to use it.

In APISIX, when the data encryption is enabled, the following data will be encrypted before being saved to etcd:

This guide will help you understand why you should enable data encryption for sensitive data and how you can enable data encryption to harden security.

Enable Data Encryption

By default, APISIX has data encryption enabled with two default keys:

apisix/cli/config.lua
apisix ={
...,
data_encryption = {
enable_encrypt_fields = true,
keyring = { "qeddd145sfvddff3", "edd1c9f0985e76a2" }
},
...
}

To see data encryption in effect, create a consumer JohnDoe:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "JohnDoe"
}'

Configure the key-auth credential for JohnDoe:

curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'

In this configuration, the user key is sensitive information that should not be stored in plaintext.

You should see a response when the credential is created, showing an encrypted key:

{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300662,
"plugins": {
"key-auth": {
"key": "a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}
}

To further verify that the key is encrypted, you can also examine the item saved to etcd:

etcdctl get /apisix/consumers/JohnDoe/credentials/cred-john-key-auth

You should see the key has been encrypted:

{
"update_time":1726300662,
"create_time":1726300662,
"plugins":{
"key-auth":{
"key":"a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}

APISIX also encrypts TLS certificate private keys before saving them to etcd. To verify, follow the steps in Configure HTTPS Between Client and APISIX and observe that server_key is encrypted.

APISIX also encrypts upstream.tls.client_key in Upstreams and inline Stream Route upstreams. The Admin API does not return an inline Stream Route client key in plaintext. See Authenticate to a TLS Upstream with mTLS for the Stream configuration.

Verify Decryption

To verify the consumer key-auth key will be decrypted and used as intended in authentication, create a route with key-auth enabled:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "auth-route",
"uri": "/get",
"plugins": {
"key-auth": {},
},
"upstream" : {
"nodes": {
"httpbin.org":1
}
}
}'

Send a request to the route with the consumer credential:

curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'

You should receive an HTTP/1.1 200 OK response, verifying the key has been successfully decrypted and used in authentication.

Update Keyring

The encrypted data can be reversed to plaintext with the correct keyring. Therefore, it is strongly recommended that you use a customized keyring in production.

Each key must contain exactly 16 or 32 bytes. Use ASCII characters so the character and byte lengths are identical. A 16-byte key selects AES-128-CBC, and a 32-byte key selects AES-256-CBC. APISIX rejects other lengths while validating config.yaml, before startup or reload.

A keyring can contain both lengths during rotation. APISIX encrypts new values with the first key and tries every key when decrypting existing values. To rotate from AES-128 to AES-256, add the new 32-byte key first and retain the old keys afterward:

config.yaml
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- 0123456789abcdef0123456789abcdef
- qeddd145sfvddff3
- edd1c9f0985e76a2

❶ Enable the encryption for sensitive plugin fields.

❷ Add the new 32-byte ASCII key first. Replace this example with a securely generated key.

❸ Add the first and second old keys.

caution

If your APISIX is already running and has data encrypted, do not remove the old keys. Add the new keys at the top of the array, as shown above, so that the encrypted data can be correctly decrypted. Removing the old keys directly can render the encrypted data irreversible.

If no data has been encrypted, you may directly configure the section with your custom keys.

If your APISIX is already running, reload APISIX for configuration changes to take effect.

Troubleshoot Decryption After an Upgrade

An upgrade can add existing plugin fields to encrypt_fields. Configuration written by an earlier version may therefore contain a value that was not encrypted where the current version expects encrypted data. APISIX can also fail to decrypt existing data when the configured keyring no longer contains the key that encrypted it.

If the error log reports a decryption failure:

  1. Preserve the complete old keyring. Do not remove or reorder old keys while investigating.
  2. Add any new key at the beginning of apisix.data_encryption.keyring, keeping every old key afterward.
  3. Reload APISIX and confirm it can read the existing configuration.
  4. Fetch the affected resource with an Admin API GET request. With the old key available, APISIX returns protected fields in plaintext. Re-submit that plaintext in an update request so APISIX encrypts it with the first key in the current keyring. Do not reuse an encrypted value from an earlier write response or directly from etcd.
  5. Verify the resource works before considering any later key retirement.

Do not delete an old key first. Once APISIX can no longer decrypt a value, re-saving the unreadable data cannot recover the original secret.

Disable Data Encryption

To disable data encryption, simply update the enable_encrypt_fields to false:

config.yaml
apisix:
data_encryption:
enable_encrypt_fields: false

If your APISIX is already running, reload APISIX for configuration changes to take effect.

Now if you configure the consumer credential with key-auth again:

curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'

You should see a response where the key is saved in plaintext:

{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300668,
"plugins": {
"key-auth": {
"key": "john-key"
}
},
"id": "cred-john-key-auth"
}
}

Understand Plugin Encrypt Fields

Encrypt plugin fields are defined in the encrypt_fields attribute of each plugin schema. The field path can point to a top-level field or a nested field. For example:

apisix/plugins/basic-auth.lua
local consumer_schema = {
type = "object",
title = "work with consumer object",
properties = {
username = { type = "string" },
password = { type = "string" },
},
# highlight-next-line
encrypt_fields = {"password"},
required = {"username", "password"},
}

Once defined, these fields will be encrypted when data encryption is enabled.

For nested fields, use dot-separated paths such as session.secret, redis.password, or auth_config.private_key. Paths can have any nesting depth. When an intermediate segment is an array, APISIX traverses every object in the array. This supports fields such as instances.auth.aws.secret_access_key in ai-proxy-multi.

At the final path segment, APISIX encrypts these value shapes:

  • a string;
  • an array of strings; or
  • an object whose values are strings.

Non-string items at the final segment are left unchanged. Add only fields that contain secrets and whose runtime code expects the same value shape after decryption.

The table below summarizes encrypt plugin fields of all plugins:

For logger plugins, an encrypted field can be an object whose string values are encrypted, such as headers in elasticsearch-logger and loki-logger. Encryption protects values at rest; authorized Admin API reads can still return decrypted plugin configuration.

PluginField(s)
authz-casdoorclient_secret
authz-keycloakclient_secret
azure-functionsauthorization.apikey, master_apikey
basic-authpassword
cas-authcookie.secret
clickhouse-loggerpassword
dingtalk-authapp_secret, secret, secret_fallbacks
elasticsearch-loggerauth.password, headers
feishu-authapp_secret, secret, secret_fallbacks
rocketmq-loggersecret_key
sls-loggeraccess_key_secret
error-log-loggerclickhouse.password, kafka.brokers.sasl_config.password
google-cloud-loggingauth_config.private_key
csrfkey
hmac-authsecret_key
http-loggerauth_header
jwt-authsecret
kafka-loggerbrokers.sasl_config.password
key-authkey
lagotoken
logglycustomer_token
loki-loggerheaders
openwhiskservice_token
openfunctionauthorization.service_token
tencent-cloud-clssecret_key
openid-connectclient_secret, client_rsa_private_key, dpop.private_key, session.secret, session.redis.password
kafka-proxysasl.password
jwe-decryptkey, secret
ai-cacheredis_password, semantic.embedding.openai.api_key, semantic.embedding.azure_openai.api_key
ai-aliyun-content-moderationaccess_key_secret
ai-aws-content-moderationcomprehend.secret_access_key
ai-lakera-guardapi_key
ai-proxyauth.header, auth.query, auth.gcp.service_account_json, auth.aws.secret_access_key, auth.aws.session_token
ai-proxy-multiinstances.auth.header, instances.auth.query, instances.auth.gcp.service_account_json, instances.auth.aws.secret_access_key, instances.auth.aws.session_token
ai-ragembeddings_provider.azure_openai.api_key, vector_search_provider.azure_ai_search.api_key
ai-rate-limitingredis_password, sentinel_password
aws-lambdaauthorization.apikey, authorization.iam.accesskey, authorization.iam.secretkey
ldap-auth-advancedldap_password
limit-connredis_password
limit-countredis_password, sentinel_password
limit-reqredis_password
saml-authsp_private_key, secret, secret_fallbacks
splunk-hec-loggingendpoint.token