---
title: Rotating PingGateway encryption and signing keys
description: Rotate PingGateway encryption and signing keys, with examples for stateless access token signing and shared JWT session key rotation
component: pinggateway
version: 2026
page_id: pinggateway:maintenance-guide:rotate-keys
canonical_url: https://docs.pingidentity.com/pinggateway/2026/maintenance-guide/rotate-keys.html
revdate: 2025-04-01T17:53:34Z
keywords: ["Maintenance", "Configuration", "Key Rotation", "Keystores", "Sessions"]
section_ids:
  rotating-keys-about: About key rotation
  why_and_when_to_rotate_keys: Why and when to rotate keys
  steps_for_rotating_symmetric_keys: Steps for rotating symmetric keys
  steps_for_rotating_asymmetric_keys: Steps for rotating asymmetric keys
  key_rotation_for_keys_in_a_jwk_set: Key rotation for keys in a JWK set
  rotating-keys-signed-sat: Rotate keys for stateless access tokens signed with a KeyStoreSecretStore
  rotating-keys-jwtsession: Rotate keys in a shared JWT session
---

# Rotating PingGateway encryption and signing keys

The following sections give an overview of how to manage rotation of encryption keys and signing keys, and include examples for key rotation based on use cases from the [Gateway guide](../gateway-guide/preface.html#preface).

## About key rotation

Key rotation is the process of generating a new version of a key, assigning that version as the *active* key to encrypt or sign new messages, or as a *valid* key to decrypt or validate messages, and then deprovisioning the old key.

### Why and when to rotate keys

Regular key rotation is a security consideration that is sometimes required for internal business compliance. Regularly rotate keys to:

* Limit the amount of data protected by a single key.

* Reduce dependence on specific keys, making it easier to migrate to stronger algorithms.

* Prepare for when a key is compromised. The first time you try key rotation shouldn't be during a real-time recovery.

Key revocation is a type of key rotation, done exceptionally if you suspect that a key has been compromised. To decide when to revoke a key, consider the following points:

* If limited use of the old keys can be tolerated, provision the new keys and then deprovision the old keys. Messages produced before the new keys are provisioned are impacted.

* If use of the old keys cannot be tolerated, deprovision the old keys before you provision the new keys. The system is unusable until new keys are provisioned.

### Steps for rotating symmetric keys

The following steps outline key rotation and revocation for symmetric keys managed by a KeyStoreSecretStore. For an example, refer to [Rotate keys in a shared JWT session](#rotating-keys-jwtsession).

1. Using OpenSSL, Keytool, or another key creation mechanism, create the new symmetric key. The keystore should contain the old key and the new key.

2. Provision the new key.

   1. In the `mappings` property of KeyStoreSecretStore, add the alias for the new key after the alias for the old key. The new key is now valid. Because the old key is the first key in the list, it is the active key.

   2. Move the new key to be the first key in the list. The new key is now the active key.

3. Deprovision the old key.

   To ensure that no messages or users are impacted, wait until messages encrypted or signed with the old key are out of the system before you deprovision the old key.

   1. In the `mappings` property of KeyStoreSecretStore, delete the alias for the old key. The old key can no longer be used.

   2. Using OpenSSL, Keytool, or another key creation mechanism, delete the old symmetric key.

### Steps for rotating asymmetric keys

The following steps outline the process for key rotation and revocation for asymmetric keys managed by a KeyStoreSecretStore or HsmSecretStore. For an example, refer to [Rotate keys for stateless access tokens signed with a KeyStoreSecretStore](#rotating-keys-signed-sat).

1. Create new asymmetric keys for signing and encryption, using OpenSSL, Keytool, or another key creation mechanism.

2. Provision the message consumer with the private portion of the new encryption key, and the public portion of the new signing key.

   The message consumer can now decrypt and verify messages with the old key and the new key.

3. Provision the message producer, with the public portion of the new encryption key, and the private portion of the signing key. The message producer starts encrypting and signing messages with the new key, and stops using the old key.

4. Deprovision the message consumer with the private portion of the old encryption key, and the public portion of the old signing key. The message consumer can no longer decrypt and verify messages with the old key.

   To ensure that no messages or users are impacted, wait until messages encrypted or signed with the corresponding old key are out of the system before you deprovision the old key.

5. Deprovision the message producer, with the public portion of the old encryption key, and the private portion of old signing key.

### Key rotation for keys in a JWK set

When keys are provided by a JWK Set from AM, the key rotation is transparent to PingGateway. AM generates a key ID (`kid`) for each key it exposes at the `jwk_uri`. For more information, refer to [Mapping and rotating secrets](https://docs.pingidentity.com/pingam/8.1/security/secret-mapping.html) in AM's *Security guide*.

When PingGateway processes a request with a JWT containing a `kid`, PingGateway uses the `kid` to identify the key in the JWK Set. If the `kid` is available at the `jwk_uri` on AM, PingGateway processes the request. Otherwise, PingGateway tries all compatible secrets from the JWK Set. If none of the secrets work, the JWT is rejected.

## Rotate keys for stateless access tokens signed with a KeyStoreSecretStore

This example extends the example in [Signed PingAM tokens with KeyStoreSecretStore](../gateway-guide/oauth2-rs-stateless-signed-ksss.html) to rotate the keys that sign an access token and verify the signature.

Rotate Keys For Stateless Access Tokens Signed With a KeyStoreSecretStore

Before you start, set up and test the example in [Signed PingAM tokens with KeyStoreSecretStore](../gateway-guide/oauth2-rs-stateless-signed-ksss.html).

1. Set up the new keys:

   1. Generate a new private key called `signature-key-new`, and a corresponding public certificate called `x509certificate-new.pem`:

      ```console
      $ openssl req -x509 \
      -newkey rsa:2048 \
      -nodes \
      -subj "/CN=ig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout keystore_directory/signature-key-new.key \
      -out keystore_directory/x509certificate-new.pem \
      -days 365
      ```

      Output

      ```
      ... writing new private key to 'keystore_directory/signature-key-new.key'
      ```

   2. Convert the private key and certificate files into a new PKCS#12 keystore file:

      ```console
      $ openssl pkcs12 \
      -export \
      -in keystore_directory/x509certificate-new.pem \
      -inkey keystore_directory/signature-key-new.key \
      -out keystore_directory/keystore-new.p12 \
      -passout pass:password \
      -name signature-key-new
      ```

   3. List the keys in the new keystore:

      ```console
      $ keytool -list \
      -keystore "keystore_directory/keystore-new.p12" \
      -storepass "password" \
      -storetype PKCS12
      ```

      Output

      ```
      ...
      Your keystore contains 1 entry
      Alias name: signature-key-new
      ```

   4. Import the new keystore into `keystore.p12`, so that `keystore.p12` contains both keys:

      ```console
      $ keytool -importkeystore
      -srckeystore keystore_directory/keystore-new.p12
      -srcstoretype pkcs12
      -srcstorepass password
      -destkeystore keystore_directory/keystore.p12
      -deststoretype pkcs12
      -deststorepass password
      ```

      Output

      ```
      Entry for alias signature-key-new successfully imported ...
      ```

   5. List the keys in `keystore.p12`, to make sure it contains the new and old keys:

      ```console
      $ keytool -list \
      -keystore "keystore_directory/keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      ```

      Output

      ```
      ...
      Your keystore contains 2 entries
      Alias name: signature-key
      Alias name: signature-key-new
      ```

2. Set up AM:

   1. Copy the updated keystore to AM:

      1. Copy `keystore.p12` to AM:

         ```console
         $ cp keystore_directory/keystore.p12 am_keystore_directory/AM_keystore.p12
         ```

      2. List the keys in the updated AM keystore:

         ```console
         $ keytool -list \
         -keystore "am_keystore_directory/AM_keystore.p12" \
         -storepass "password" \
         -storetype PKCS12
         ```

         Output

         ```
         ...
         Your keystore contains 2 entries
         Alias name: signature-key
         Alias name: signature-key-new
         ```

      3. Restart AM to update the keystore cache.

   2. Update the KeyStoreSecretStore on AM:

      1. In AM, select [icon: eye-slash, set=fa]Secret Stores > keystoresecretstore.

      2. Select the Mappings tab, and in `am.services.oauth2.stateless .signing.RSA` add the alias `signature-key-new`.

         The mapping now contains two aliases, but the alias `signature-key` is still the active alias. AM still uses `signature-key` to sign tokens.

      3. Drag `signature-key-new` above `signature-key`.

         AM now uses `signature-key-new` to sign tokens.

3. Set up PingGateway:

   1. Set up PingGateway for HTTPS, as described in [Configure PingGateway for TLS (server-side)](../installation-guide/securing-connections.html#server-side-tls).

   2. Import the public certificate to the PingGateway keystore, with the alias `verification-key-new`:

      ```console
      $ keytool -import \
      -trustcacerts \
      -rfc \
      -alias verification-key-new \
      -file "keystore_directory/x509certificate-new.pem" \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storetype PKCS12 \
      -storepass "password"
      ```

      Output

      ```
      ...
      Trust this certificate? [no]:  yes
      Certificate was added to keystore
      ```

   3. List the keys in the PingGateway keystore:

      ```console
      $ keytool -list \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      ```

      Output

      ```
      ...
      Your keystore contains 2 entries
      Alias name: verification-key
      Alias name: verification-key-new
      ```

   4. In `rs-stateless-signed-ksss.json`, edit the KeyStoreSecretStore mapping with the new verification key:

      ```json
      "mappings": [
        {
          "secretId": "stateless.access.token.verification.key",
          "aliases": [ "verification-key", "verification-key-new" ]
        }
      ]
      ```

      If the Router `scanInterval` is disabled, restart PingGateway to reload the route.

      PingGateway can now check the authenticity of access tokens signed with `verification-key`, the old key, and `verification-key-new`, the new key. However, AM signs with the old key.

4. Test the setup:

   1. Get an access token for the demo user, using the scope `myscope`:

      ```console
      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=myscope" \
      http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
      ```

   2. Display the token:

      ```console
      $ echo ${mytoken}
      ```

   3. Access the route by providing the token returned in the previous step:

      ```console
      $ curl -v \
      --cacert /path/to/secrets/ig.example.com-certificate.pem \
      --header "Authorization: Bearer ${mytoken}" \
      https://ig.example.com:8443/rs-stateless-signed-ksss
      ```

      Output

      ```
      ...
      Decoded access_token: {
      sub=demo,
      cts=OAUTH2_STATELESS_GRANT,
      ...
      ```

Deprovision Old Keys

1. Remove `signature-key` from the AM keystore:

   1. Delete the key from the keystore:

      ```console
      $ keytool -delete \
      -keystore "am_keystore_directory/AM_keystore.p12" \
      -storepass "password" \
      -alias signature-key
      ```

   2. List the keys in the AM keystore to make sure `signature-key` is removed:

      ```console
      $ keytool -list \
      -keystore "am_keystore_directory/AM_keystore-new.p12" \
      -storepass "password" \
      -storetype PKCS12
      ```

   3. Restart AM.

2. Remove `verification-key` from the PingGateway keystore:

   1. Delete the key from the keystore:

      ```console
      $ keytool -delete \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -alias verification-key
      ```

   2. List the keys in the PingGateway keystore to make sure that `verification-key` is removed:

      ```console
      $ keytool -list \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      ```

3. In AM, delete the mapping for `signature-key` from `keystoresecretstore`.

4. In PingGateway, delete the mapping for `verification-key` from the route `rs-stateless-signed-ksss.json`. If the Router `scanInterval` is disabled, restart PingGateway to reload the route.

## Rotate keys in a shared JWT session

This section builds on the example in [Share JWT session between multiple instances of PingGateway](../installation-guide/jwtsession-using.html#jwtsession-sharesecrets) to rotate a key used in a shared JWT session.

When a JWT session is shared between multiple instances of PingGateway, the instances are able to share the session information for load balancing and failover.

Before you start, set up the example in [Set up shared secrets for multiple instances of PingGateway](../installation-guide/jwtsession-using.html#proc-jwtsession-sharesecrets), where three instances of PingGateway share JWT-based sessions and use the same authenticated encryption key. Instance 1 acts as a load balancer, and generates a session. Instances 2 and 3 access the session information.

1. Test the setup with the existing key, `symmetric-key`:

   1. Access instance 1 to generate a session:

      ```console
      $ curl -v http://ig.example.com:8001/log-in-and-generate-session
      ```

      Output

      ```
      GET /log-in-and-generate-session HTTP/1.1
      ...

      HTTP/1.1 200 OK
      Content-Length: 84
      Set-Cookie: IG=eyJ...HyI; Path=/; Domain=.example.com; HttpOnly
      ...
      Sam Carter logged IN. (JWT session generated)
      ```

   2. Using the JWT cookie returned in the previous step, access instance 2:

      ```console
      $ curl -v http://ig.example.com:8001/webapp/browsing?one --header "cookie:IG=<JWT cookie>"
      ```

      Output

      ```
      GET /webapp/browsing?one HTTP/1.1
      ...
      cookie: IG=eyJ...QHyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance2)
      ```

      Note that instance 2 can access the session info.

   3. Using the JWT cookie again, access instance 3:

      ```console
      $ curl -v http://ig.example.com:8001/webapp/browsing?two --header "Cookie:IG=<JWT cookie>"
      ```

      Output

      ```
      GET /webapp/browsing?two HTTP/1.1
      ...
      cookie: IG=eyJ...QHyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance3)
      ```

      Note that instance 3 can access the session info.

2. Commission a new key:

   1. Generate a new encryption key, called `symmetric-key-new`, in the existing keystore:

      ```console
      $ keytool \
      -genseckey \
      -alias symmetric-key-new
      -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
      -storepass password \
      -storetype PKCS12 \
      -keyalg HmacSHA512 \
      -keysize 512
      ```

   2. Make sure the keystore contains the old key and the new key:

      ```console
      $ keytool \
      -list \
      -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
      -storepass password \
      -storetype PKCS12
      ```

      Output

      ```
      ...
      Your keystore contains 2 entries
      symmetric-key, ...
      symmetric-key-new ...
      ```

   3. Add the key alias to `instance1-loadbalancer.json`, `instance2-retrieve-session-username.json`, and `instance3-retrieve-session-username.json`, for each PingGateway instance, as follows:

      ```json
      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key", "symmetric-key-new"]
      }]
      ```

      If the Router `scanInterval` is disabled, restart PingGateway to reload the route.

      The active key is `symmetric-key`, and the valid key is `symmetric-key-new`.

   4. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.

3. Make the new key the active key for generating sessions:

   1. In `instance1-loadbalancer.json`, change the order of the keys to make `symmetric-key-new` the active key, and `symmetric-key` the valid key:

      ```json
      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key-new", "symmetric-key"]
      }]
      ```

      Don't change `instance2-retrieve-session-username.json` or `instance3-retrieve-session-username.json`.

   2. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.

      Instance 1 creates the session using the new active key, `symmetric-key-new`.

      Because `symmetric-key-new` is declared as a valid key in instances 2 and 3, the instances can still access the session. It isn't necessary to make `symmetric-key-new` the active key.

4. Decommission the old key:

   1. Remove the old key from all of the routes, as follows:

      ```json
      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key-new"]
      }]
      ```

      Key `symmetric-key-new` is the only key in the routes.

   2. Remove the old key, `symmetric-key`, from the keystore:

      1. Delete `symmetric-key`:

         ```console
         $ keytool \
         -delete \
         -alias symmetric-key \
         -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
         -storepass password \
         -storetype PKCS12 \
         -keypass password
         ```

      2. Make sure the keystore contains only `symmetric-key-new`:

         ```console
         $ keytool \
         -list \
         -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
         -storepass password \
         -storetype PKCS12
         ```

         Output

         ```
         ...
         Your keystore contains 1 entry
         symmetric-key-new ...
         ```

   3. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.
