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:
- Sensitive plugin fields
- TLS private keys, including upstream client private keys
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 ={
...,
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:
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.
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:
- Preserve the complete old keyring. Do not remove or reorder old keys while investigating.
- Add any new key at the beginning of
apisix.data_encryption.keyring, keeping every old key afterward. - Reload APISIX and confirm it can read the existing configuration.
- Fetch the affected resource with an Admin API
GETrequest. 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. - 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:
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:
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.
| Plugin | Field(s) |
|---|---|
authz-casdoor | client_secret |
authz-keycloak | client_secret |
azure-functions | authorization.apikey, master_apikey |
basic-auth | password |
cas-auth | cookie.secret |
clickhouse-logger | password |
dingtalk-auth | app_secret, secret, secret_fallbacks |
elasticsearch-logger | auth.password, headers |
feishu-auth | app_secret, secret, secret_fallbacks |
rocketmq-logger | secret_key |
sls-logger | access_key_secret |
error-log-logger | clickhouse.password, kafka.brokers.sasl_config.password |
google-cloud-logging | auth_config.private_key |
csrf | key |
hmac-auth | secret_key |
http-logger | auth_header |
jwt-auth | secret |
kafka-logger | brokers.sasl_config.password |
key-auth | key |
lago | token |
loggly | customer_token |
loki-logger | headers |
openwhisk | service_token |
openfunction | authorization.service_token |
tencent-cloud-cls | secret_key |
openid-connect | client_secret, client_rsa_private_key, dpop.private_key, session.secret, session.redis.password |
kafka-proxy | sasl.password |
jwe-decrypt | key, secret |
ai-cache | redis_password, semantic.embedding.openai.api_key, semantic.embedding.azure_openai.api_key |
ai-aliyun-content-moderation | access_key_secret |
ai-aws-content-moderation | comprehend.secret_access_key |
ai-lakera-guard | api_key |
ai-proxy | auth.header, auth.query, auth.gcp.service_account_json, auth.aws.secret_access_key, auth.aws.session_token |
ai-proxy-multi | instances.auth.header, instances.auth.query, instances.auth.gcp.service_account_json, instances.auth.aws.secret_access_key, instances.auth.aws.session_token |
ai-rag | embeddings_provider.azure_openai.api_key, vector_search_provider.azure_ai_search.api_key |
ai-rate-limiting | redis_password, sentinel_password |
aws-lambda | authorization.apikey, authorization.iam.accesskey, authorization.iam.secretkey |
ldap-auth-advanced | ldap_password |
limit-conn | redis_password |
limit-count | redis_password, sentinel_password |
limit-req | redis_password |
saml-auth | sp_private_key, secret, secret_fallbacks |
splunk-hec-logging | endpoint.token |