---
title: Encryption key management
description: Most regulatory requirements mandate that the keys used to decrypt sensitive data be rotated out and replaced with new keys on a regular basis. The main purpose of rotating encryption keys is to reduce the amount of data encrypted with that key, so that the potential impact of a security breach with a specific key is reduced. You can update encryption keys in several ways, including the following:
component: pingidm
version: 8.1
page_id: pingidm:security-guide:key-mgmt
canonical_url: https://docs.pingidentity.com/pingidm/8.1/security-guide/key-mgmt.html
keywords: ["Security", "Encryption Keys", "Secrets", "JSON", "Managed Object", "Key Rotation"]
section_ids:
  rotating-encryption-keys: Manual key rotation
  scheduled-key-rotation: Scheduled key rotation
  changing-active-alias: Change the active alias for managed object encryption
---

# Encryption key management

Most regulatory requirements mandate that the keys used to decrypt sensitive data be rotated out and replaced with new keys on a regular basis. The main purpose of rotating encryption keys is to reduce the amount of data encrypted with that key, so that the potential impact of a security breach with a specific key is reduced. You can update encryption keys in several ways, including the following:

## Manual key rotation

IDM evaluates keys in `secrets.json` sequentially. For example, assume that you have [added a new key](ca-signed-certs.html#import-signed-cert) named `my-new-key` to the keystore. To use this new key to encrypt passwords, you would include `my-new-key` as the *first alias* in the `idm.password.encryption` secret:

```json
{
    "secretId" : "idm.password.encryption",
    "types": [ "ENCRYPT", "DECRYPT" ],
    "aliases": [ "my-new-key", "&{openidm.config.crypto.alias|openidm-sym-default}" ]
}
```

The properties that use this key (in this case, passwords) are re-encrypted with the new key the next time the managed object is *updated*. You do not need to restart the server.

|   |                                                                                                                                                                                                                                                                                                                                            |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | If you rotate an encryption key, the *active* encryption key might not be the correct key to use for decryption of properties that have already been encrypted with a previous key.You must therefore keep all applicable keys in `secrets.json` until every object that is encrypted with old keys have been updated with the latest key. |

You can force key rotation on all managed objects by running the `triggerSyncCheck` action on the entire managed object data set. The `triggerSyncCheck` action examines the crypto blob of each object and updates the encrypted property with the correct key.

For example, the following command forces all managed user objects to use the new key:

```
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
--header "Content-Type: application/json" \
--request POST \
"https://localhost:8443/openidm/managed/user/?_action=triggerSyncCheck"
{
    "status": "OK",
    "countTriggered": 10
}
```

In a large managed object set, the `triggerSyncCheck` action can take a long time to run on only a single node. You should therefore avoid using this action if your data set is large. An alternative to running `triggerSyncCheck` over the entire data set is to iterate over the managed data set and call `triggerSyncCheck` on each individual managed object. You can call this action manually or by using a script.

The following example shows the manual commands that must be run to launch the `triggerSyncCheck` action on all managed users. The first command uses a query filter to return all managed user IDs. The second command iterates over the returned IDs calling `triggerSyncCheck` on each ID:

```
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
"https://localhost:8443/openidm/managed/user?_queryFilter=true&_fields=_id"
{
  "result": [
    {
      "_id": "9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb",
      "_rev": "000000004988917b"
    },
    {
      "_id": "55ef0a75-f261-47e9-a72b-f5c61c32d339",
      "_rev": "00000000dd89d671"
    },
    {
      "_id": "998a6181-d694-466a-a373-759a05840555",
      "_rev": "000000006fea54ad"
    },
    ...
  ]
}
```

```
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
--header "Content-Type: application/json" \
--request POST \
"https://localhost:8443/openidm/managed/user/9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb?_action=triggerSyncCheck"
```

In large data sets, the most efficient way to achieve key rotation is to use the scheduler service to launch these commands. The following section shows how to use the scheduler service for this purpose.

### Scheduled key rotation

This example uses a script to generate multiple scheduled tasks. Each scheduled task iterates over a subset of the managed object set (defined by the `pageSize`). The generated scheduled task then calls another script that launches the `triggerSyncCheck` action on each managed object in that subset.

To schedule key rotation, set up a similar schedule:

1. Create a schedule configuration named `schedule-triggerSyncCheck.json` in your project's `conf` directory. That schedule should look as follows:

   ```json
   {
       "enabled" : true,
       "persisted" : true,
       "type" : "cron",
       "schedule" : "0 * * * * ? *",
       "concurrentExecution" : false,
       "invokeService" : "script",
       "invokeContext" : {
           "waitForCompletion" : false,
           "script": {
               "type": "text/javascript",
               "name": "sync/scheduleTriggerSyncCheck.js"
           },
           "input": {
               "pageSize": 2,
               "managedObjectPath" : "managed/user",
               "quartzSchedule" : "0 * * * * ? *"
           }
       }
   }
   ```

   You can change the following parameters of this schedule configuration to suit your deployment:

   * `pageSize`

     The number of objects that each generated schedule will handle. This value should be high enough not to create too many schedules. The number of schedules that is generated is equal to the number of objects in the managed object store, divided by the page size.

     For example, if there are 500 managed users and a page size of 100, five schedules will be generated (500/100).

   * `managedObjectPath`

     The managed object set over which the scheduler iterates. For example, `managed/user` if you want to iterate over the managed user object set.

   * `quartzSchedule`

     The schedule at which these tasks should run. For example, to run the task every minute, this value would be `0 * * * * ? *`.

2. The schedule calls a `scheduleTriggerSyncCheck.js` script, located in a directory named `project-dir/script/sync`. Create the `sync` directory, and add the script:

   ```javascript
   var managedObjectPath = object.managedObjectPath;
   var pageSize = object.pageSize;
   var quartzSchedule = object.quartzSchedule;

   var managedObjects = openidm.query(managedObjectPath, {
       "_queryFilter": "true",
       "_fields": "_id"
   });

   var numberOfManagedObjects = managedObjects.result.length;

   for (var i = 0; i < numberOfManagedObjects; i += pageSize) {
       var scheduleId = java.util.UUID.randomUUID().toString();
       var ids = managedObjects.result.slice(i, i + pageSize).map(function(obj) {
           return obj._id
       });
       var schedule = newSchedule(scheduleId, ids);
       openidm.create("/scheduler", scheduleId, schedule);
   }

   function newSchedule(scheduleId, ids) {
       var schedule = {
           "enabled": true,
           "persisted": true,
           "type": "cron",
           "schedule": quartzSchedule,
           "concurrentExecution": false,
           "invokeService": "script",
           "invokeContext": {
               "waitForCompletion": true,
               "script": {
                   "type": "text/javascript",
                   "name": "sync/triggerSyncCheck.js"
               },
               "input": {
                   "ids": ids,
                   "managedObjectPath": managedObjectPath,
                   "scheduleId": scheduleId
               }
           }
       };
       return schedule;
   }
   ```

3. Each generated scheduled task calls a script named `triggerSyncCheck.js`. Create the script in your project's `script/sync` directory:

   ```javascript
   var ids = object.ids;
   var scheduleId = object.scheduleId;
   var managedObjectPath = object.managedObjectPath;

   for (var i = 0; i & lt; ids.length; i++) {
       openidm.action(managedObjectPath + "/" + ids[i], "triggerSyncCheck", {}, {});
   }

   openidm.delete("scheduler/" + scheduleId, null);
   ```

4. Test the key rotation:

   1. Edit your project's `conf/managed.json` file to return user passwords by default by setting `"scope" : "public"`.

      ```json
      "password" : {
          ...
          "encryption" : {
              "purpose" : "idm.password.encryption"
          },
          "scope" : "public",
          ...
      }
      ```

      Because passwords are not returned by default, you will not be able to refer to the new encryption on the password unless you change the property's `scope`.

   2. Perform a GET request to return any managed user entry in your data set:

      ```
      curl \
      --header "X-OpenIDM-Username: openidm-admin" \
      --header "X-OpenIDM-Password: openidm-admin" \
      --header "Accept-API-Version: resource=1.0" \
      --cacert ca-cert.pem \
      --request GET \
      "https://localhost:8443/openidm/managed/user/ccd92204-aee6-4159-879a-46eeb4362807"
      {
        "_id" : "ccd92204-aee6-4159-879a-46eeb4362807",
        "_rev" : "0000000009441230",
        "preferences" : {
          "updates" : false,
          "marketing" : false
        },
        "mail" : "bjensen@example.com",
        "sn" : "Jensen",
        "givenName" : "Babs",
        "userName" : "bjensen",
        "password" : {
          "$crypto" : {
            "type" : "x-simple-encryption",
            "value" : {
              "cipher" : "AES/CBC/PKCS5Padding",
              "stableId" : "openidm-sym-default",
              "salt" : "CVrKDuzfzunXfTDbCwU1Rw==",
              "data" : "1I5tWT5aRH/12hf5DgofXA==",
              "keySize" : 16,
              "purpose" : "idm.password.encryption",
              "iv" : "LGE+jnC3ZtyvrE5pfuSvtA==",
              "mac" : "BEXQ1mftxA63dXhJO6dDZQ=="
            }
          }
        },
        "accountStatus" : "active",
        "effectiveRoles" : [ ],
        "effectiveAssignments" : [ ]
      }
      ```

      Notice that the user's password is encrypted with the default encryption key (`openidm-sym-default`).

   3. Create a new encryption key in the IDM keystore:

      ```
      keytool \
      -genseckey \
      -alias my-new-key \
      -keyalg AES \
      -keysize 128 \
      -keystore /path/to/openidm/security/keystore.jceks \
      -storetype JCEKS
      ```

   4. Shut down the server for keystore to be reloaded.

   5. Change your project's `conf/managed.json` file to change the encryption purpose for managed user passwords:

      ```json
      "password" : {
          ...
          "encryption" : {
              "purpose" : "idm.password.encryption2"
          },
          "scope" : "public",
          ...
      }
      ```

   6. Add the corresponding `purpose` to the `secrets.json` file in the `mainKeyStore` code block:

      ```json
      "idm.password.encryption2": {
        "types": [ "ENCRYPT", "DECRYPT" ],
        "aliases": [
          {
            "alias": "my-new-key"
          }
        ]
      }
      ```

   7. Restart the server and wait one minute for the scheduled task to start.

   8. Perform a GET request again to return the entry of the managed user that you returned previously:

      ```
      curl \
      --header "X-OpenIDM-Username: openidm-admin" \
      --header "X-OpenIDM-Password: openidm-admin" \
      --header "Accept-API-Version: resource=1.0" \
      --cacert ca-cert.pem \
      --request GET \
      "https://localhost:8443/openidm/managed/user/ccd92204-aee6-4159-879a-46eeb4362807"
      {
        "_id" : "ccd92204-aee6-4159-879a-46eeb4362807",
        "_rev" : "0000000009441230",
        "preferences" : {
          "updates" : false,
          "marketing" : false
        },
        "mail" : "bjensen@example.com",
        "sn" : "Jensen",
        "givenName" : "Babs",
        "userName" : "bjensen",
        "password" : {
          "$crypto" : {
            "type" : "x-simple-encryption",
            "value" : {
              "cipher" : "AES/CBC/PKCS5Padding",
              "stableId" : "my-new-key",
              "salt" : "CVrKDuzfzunXfTDbCwU1Rw==",
              "data" : "1I5tWT5aRH/12hf5DgofXA==",
              "keySize" : 16,
              "purpose" : "idm.password.encryption2",
              "iv" : "LGE+jnC3ZtyvrE5pfuSvtA==",
              "mac" : "BEXQ1mftxA63dXhJO6dDZQ=="
            }
          }
        },
        "accountStatus" : "active",
        "effectiveRoles" : [ ],
        "effectiveAssignments" : [ ]
      }
      ```

      The user password is now encrypted with `my-new-key`.

## Change the active alias for managed object encryption

This example describes how to configure and then change the managed object encryption key with a scheduled task. You'll create a new key, set up a managed user, add the key to `secrets.json`, restart IDM, run a `triggerSyncCheck`, and review the result.

1. Create a new key for the IDM keystore in the `security/keystore.jceks` file:

   ```
   keytool \
   -genseckey \
   -alias my-new-key \
   -keyalg AES \
   -keysize 128 \
   -keystore /path/to/openidm/security/keystore.jceks \
   -storetype JCEKS
   ```

2. For the purpose of this example, in `managed.json`, set `"scope" : "public"` to expose the applied password encryption key.

3. Create a managed user:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --cacert ca-cert.pem \
   --header "Content-Type: application/json" \
   --request PUT \
   --data '{
     "userName": "rsutter",
     "sn": "Sutter",
     "givenName": "Rick",
     "mail": "rick@example.com",
     "telephoneNumber": "6669876987",
     "description": "Another user",
     "country": "USA",
     "password": "Passw0rd"
   }' \
   "https://localhost:8443/openidm/managed/user/ricksutter"
   ```

4. Add the newly created `my-new-key` alias to your `conf/secrets.json` file, in the `idm.password.encryption` code block:

   ```json
   "idm.password.encryption": {
     "types": [ "ENCRYPT", "DECRYPT" ],
     "aliases": [ "my-new-key", "&{openidm.config.crypto.alias|openidm-sym-default}" ]
   }
   ```

5. To apply the new key to your configuration, shut down and restart IDM.

6. Force IDM to update the key for your users with the `triggerSyncCheck` action:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --cacert ca-cert.pem \
   --header "Content-Type: application/json" \
   --request POST \
   "https://localhost:8443/openidm/managed/user/?_action=triggerSyncCheck"
   ```

7. Review the result for the newly created user, `ricksutter`:

   ```
   curl \
   --header "X-OpenIDM-Username: openidm-admin" \
   --header "X-OpenIDM-Password: openidm-admin" \
   --header "Accept-API-Version: resource=1.0" \
   --cacert ca-cert.pem \
   --request GET  \
   "https://localhost:8443/openidm/managed/user/ricksutter"
   ```

8. In the output, you should see the new `my-new-key` encryption key applied to that user's password:

   ```json
   ...
    "password": {
      "$crypto": {
        "type": "x-simple-encryption",
        "value": {
          "cipher": "AES/CBC/PKCS5Padding",
          "stableId": "my-new-key",
          "salt": "bGyKG3PKmwHONOfxerr1Qg==",
          "data": "6vXZiJ3ZNN/UUnsrT7dTQw==",
          "keySize": 16,
          "purpose": "idm.password.encryption",
          "iv": "doAdtxfWfFbrPIIfubGi5g==",
          "mac": "OML6xd9qvDtD5AvMc1Tc3A=="
        }
      }
    },
   ...
   ```
