Skip to content

Encrypting & Decrypting Data

Encrypting and Decrpyting data involves using a Key from a Keyring.

The Grizzly system is built around the idea of KeyRings. KeyRings manage Encryption Keys. These Encryption Keys are encrypted with the KeyRing's Public Key. This Public Key can be provided by you, or a Private and Public key pair can be managed by Grizzly. If you provide the Key Pair, it is up to you to securely store this Private Key, and ensure you have access to it when encrypting/decrypting your data with an Encryption Key. This guide separates the two flows: Grizzly managed Key Pair, and customer managed Key Pair.

For this example, we'll be using the REST API, and will need to have the following setup:

  • An Account
  • A KeyRing
  • An API Key with access to the KeyRing
REST Authentication For each every call made to the REST endpoints, you will need to send your API Key as a Bearer token:

Headers
{
   "Content-Type": "application/json",
   "Authentication": "Bearer <API Key>"
}
Create an Account An Account identifies the user, service, system, or whatever entity is peforming actions taken within the system. Accounts can have API Keys created on their behalf.

POST auth/accounts

Body
{
    "uid": "Account-Name",
    "notes": "Test Account"
}

Response

You should get a response that looks like the following:
{
    "id": "account-fb7ea9f2-e000-495d-8d41-15819aaa3dc7",
    "uid": "Account-Name",
    "notes": "Test Account",
    "props": {},
    "createdAt": "2025-06-04T21:37:29.635Z",
    "updatedAt": "2025-06-04T21:37:29.635Z"
}
Create a KeyRing This will create a KeyRing where the RSA Key Pair is managed by Grizzly. Having Grizzly manage this Key Pair makes it easier for you to work with the platform. If you have a requirement to provide your own Key Pair, see the Customer Provided Keys solution.

POST asym/keyrings

Body
{
    "name": "TestKeyRing"
}

Response
{
    "ring": {
        "id": "ring-123456",
        "name": "testkeyring7",
        "displayName": "TestKeyRing7",
        "activeKey": "key-123456-00000000",
        "activeAlgorithm": "aes256",
        "publicKey": "LS0tLS1CRUdJTiBS...",
        "createdAt": "2025-06-05T19:01:12.777Z",
        "updatedAt": "2025-06-05T19:01:12.777Z"
    },
    "key": {
        "type": "aes256",
        "id": "key-123456-00000000",
        "props": {
            "aks": {
                "pairId": "keypair-6de34a7b-dab7-4c3e-819f-62ca3b092023"
            }
        },
        "keyData": {
            "key": "708SvJzb..."
        }
    },
    "privateKey": "LS0tLS1CRUdJTiBQUklWQV..."
}
Create a KeyRing: Customer Managed Key Pair When creating this KeyRing, you'll be managing the the RSA Key Pair. Every Key generated on this KeyRing will be encrypted with the Public Key. When you receive a Key for encrypting, you'll decrypt that Key with the Private Key of the RSA Key Pair.

Public Key: RSA 4096-bit PEM encoded

POST ks/keyrings

Body
{
   "name": "TestKeyRing",
   "publicKey": "LS0tLS1CRUdJTiBQVUJ..."
}

Response
{
   "ring": {
      "id": "ring-123456",
      "name": "testkeyring4",
      "displayName": "TestKeyRing4",
      "activeKey": "key-123456-00000000",
      "activeAlgorithm": "aes256",
      "createdAt": "2025-06-05T18:55:52.208Z",
      "updatedAt": "2025-06-05T18:55:52.208Z",
      "props": {},
      "publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZL..."
   },
   "key": {
      "type": "aes256",
      "id": "key-123456-00000000",
      "props": {
         "aks": {
            "pairId": "default"
         }
      },
      "keyData": {
         "encrypted": {
            "key": {
               "encoding": "byte",
               "value": "GKZCiwllB7cME3R..."
            }
         }
      }
   }
}
Create an API Key An API Key will grant access to a KeyRing. We're going to create an API Key with both encrypt and decrypt privileges, and the ability to view the KeyRing.

POST /auth/apikey?uid=true

Body
{
    "accountId": "Account-Name",
    "flags": {
        "keyring.TestKeyRing.read": true,
        "keyring.TestKeyRing.encrypt": true,
        "keyring.TestKeyRing.decrypt": true
    }
}

Response

You should get a Response like:

{
    "key": "apikey-deba8256-d3d1-450f-bef1-3a4e79d44853",
    "keyid": "apikeyid-8dc18c83-5e54-4069-81c2-b8eb2c54bca4",
    "active": true,
    "references": {
        "accountId": "account-fb7ea9f2-e000-495d-8d41-15819aaa3dc7"
    },
    "flags": {
        "keyring.testkeyring.read": true,
        "keyring.testkeyring.decrypt": true,
        "keyring.testkeyring.encrypt": true
    },
    "props": {
        "accountId": "Account-Name"
    },
    "createdAt": "2025-06-04T22:08:15.641Z",
    "updatedAt": "2025-06-04T22:08:15.669Z"
}
The key property is your API Key you'll use in the following sections. This is the only time an API Key is ever provided.

Encrypting Data

To encrypt data, you'll need your API Key from the previous section. There are a couple ways to encrypt the data:

  • Use our SDKs - this is the easiest way
  • Using the REST APIs
Typescript SDK
import { KeyClient } from '@grizzlycbg/nodejs'

let client = await KeyClient.create({
   host: `https://<grizzly-endpoint>`,
   asymHost: `https://<grizzly-endpoint>/asym`,
   apikey: "<API-Key>"
})

// Call to encrypt with the data to encrypt, and the KeyRing name
const encrypted = await client.encrypt("Secret Message", "Finance")

console.log(`Encrypted: ${encrypted})
REST APIs To encrypt data:

- Call the `ks/encrypt` endpoint to receieve an encrypted Key

- Decrypt the encrypted Key

- Use the decrypted Key to encrypt your data

- Prepend the `header` to the encrypted data. This is necessary to match the encrpytion Key that was used to encrypt this data.

Calling the `ks/encrypt` endpoint

POST ks/encrypt

Headers
{
   "Content-Type": "application/json",
   "Authentication": "Bearer <API Key>"
}
Body
{
   "ringname": "TestKeyRing"
}
Response
{
   "key": {
      "type": "aes256",
      "id": "key-000000-00000000",
      "props": {
         "aks": {
            "pairId": "default"
         }
      },
      "keyData": {
         "encrypted": {
            "key": {
               "value": "WA+Tc6MxvZ8H5pva6...", // <-- Base64 encoded Encryption Key 
               "encoding": "byte"
            }
         },
         "iv": "Q7N8C4hhQiVqAAAD"   // <-- base64 encoded IV
      }
   },
   "ringId": "ring-000000",
   "hash": "AAAAAAAAAAAAAAAAAAA=", // <-- Unique hash representing the KeyRing and Key
   "header": "AQAgAAAAAAAAAAAAAAAAAAABAAxDs3wLiGFCJWoAAAM="
}
There are several important pieces of information here:

* key: Contains the Key information.

* ringId: The Keyring ID

* hash: The hash identifies the Key in a KeyRing. This is used later to lookup the Key that was used to encrypt data.

* header: The header is a parsable byte chunk that should be prepended to your encrypted data. This will get used when decrypting to identify the Key that was used, and any other cryptographic material that was used to encrypt the data. The header contains the hash, so there's no need to store the hash if the header is prepended to the encrypted data. The `ringId` and the `key.id` will be used in the next part.

Decrypt the encrypted Key Pass in the KeyRing ID and Key ID in the following URL. It will return the decrypted version of the Key you will use for encrypting.

GET asym/keyrings/[keyring-id]/keys/[key-id]?id=true

Response
{
   "type": "aes256",
   "id": "key-123456-00000000",
   "props": {
      "aks": {
         "pairId": "default"
      }
   },
   "keyData": {
      "key": "sGBt3shukd1OEK/QBWXaD8E5TdN+AiWXS4c9qWV/53E="
   }
}
keyData.key contains the decrypted Key, base64 encoded. Convert it to a Byte Array. The previous REST call also provided a unique IV. Use both the encryption Key and the IV to encrypt your data.
Typescript const encryptionKey = Buffer.from(key.keyData, "base64")
Python import base64

key_data = base64.b64decode(encoded_key_data)
Use the decrypted Key to encrypt your data Use both the Encryption key and IV for performing the AES256-GCM encryption.
Typescript
import crypto from "node:crypto"

const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);

let encrypted = cipher.update(
   "Some text that needs to be encrypted",
   "utf8",
   "hex"
);

encrypted += cipher.final();
encrypted += cipher.getAuthTag();
encrypted = Buffer.concat([header, encrypted])

console.log(`Encrypted: ${encrypted})
Python Using the `pycryptodome` library
from Crypto.Cipher import AES

cipher = AES.new(key, AES.MODE_GCM, iv)

encode_me = "I should be encoded!"
ciphertext, auth_tag = cipher.encrypt_and_digest(encode_me.encode())

# Prepend the encrypted data with the Header (base64 decoded header)
# And append the auth_tag for validation
ciphertext = header + ciphertext + auth_tag

Decrypting Data

To decrypt data, you'll also need the API Key from the previous section. Decrypting data involves sending Grizzly either the hash or the header that was receieved when encrypting the data.

We'll walk through two ways to decrypt:

  • Use our SDKs - the easiest way
  • Using the REST APIs
Typescript SDK
import { KeyClient } from '@grizzlycbg/nodejs'

let client = await KeyClient.create({
   rest: {
      keystore: {
         host: `https://<grizzly-endpoint>`,
         port: "443",
         auth: {
            apikey: "<API-Key>"
         },
         pathPrefix: "ks"
      },
      // Only required if Grizzly is managing the KeyPairs for you
      asym: {
         host: `https://<grizzly-endpoint>/asym`,
         port: "443",
         auth: {
            apikey: "<API-Key>"
         }
      }
   }
})

const ring = await client.ring.get("TestKeyRing")
const decrypted = await ring.decrypt(encrypted)

console.log(`Decrypted: ${decrypted})
REST APIs To Decrypt data:

- Call the `ks/decrypt` endpoint and pass in the header

- Decrypt the encrypted Key

- Use the decrypted Key to decrypt your data

Calling the `ks/decrypt` endpoint In the previous section, where we encrypted the data, we prepended the header to it, as well as appended the `authTag` to the encrypted data. Parse those off of the encrypted data.
Typescript
// This assumes the encrypted data is still a Buffer. If it has been converted
// to text, convert it to a Buffer first.

// The header length is 2 bytes long starting at index 1
const headerLength = encrypted.readUInt16BE(1)
const header = encrypted.slice(0, headerLength)

// Slice off the auth tag - the last 16 bytes
const authTag = encrypted.slice(encrypted.length - 16)

// Extract the encrypted data
const payload = encrypted.slice(headerLength, encrypted.length - 16)
Python
# This assumes the encrypted data is still a byte array. If it has been converted
# to text, convert it to a byte array.

# The header length is 2 bytes long starting at index 1
# Extract version and header length
version, header_length = struct.unpack(">BH", data[:3])

# Extract the header
header = data[:header_length]

# Extract payload (everything between header and last 16 bytes)
payload = data[header_length:-16]

# Extract the last 16 bytes as the auth tag
auth_tag = data[-16:]

The `header` is sent as a base64 encoded string.

POST ks/decrypt

Headers
{
   "Content-Type": "application/json",
   "Authentication": "Bearer <API Key>"
}
Body
{
   "header": "AQAgAAAA..."
}
Response
{
   "ringId": "ring-123456",
   "ringname": "testkeyring",
   "key": {
      "id": "key-123456-00000000",
      "type": "aes256",
      "props": {
         "aks": {
            "pairId": "default"
         }
      },
      "keyData": {
         "encrypted": {
            "key": {
               "value": "bITQanNayAxLb4...",
               "encoding": "byte"
            }
         },
         "iv": "M9sw3WsZ9xpSAAAB"
      }
   }
}

key contains the Key information. Save it for the next section where we decrypt it. You will also need the `ringId` and the `key.id`.

Decrypt the encrypted Key Pass in the KeyRing ID and Key ID in the following URL. It will return the decrypted version of the Key you will use for decryption.

GET asym/keyrings/[keyring-id]/keys/[key-id]?id=true

Response
{
   "type": "aes256",
   "id": "key-123456-00000000",
   "props": {
      "aks": {
         "pairId": "default"
      }
   },
   "keyData": {
      "key": "sGBt3shukd1OEK/..."
   }
}
keyData.key contains the decrypted Key, base64 encoded. Convert it to a Byte Array.
Typescript const encryptionKey = Buffer.from(key.keyData, "base64")
Python import base64

key_data = base64.b64decode(encoded_key_data)
Use the decrypted Key to decrypt your data Use both the Encryption key and IV for performing the AES256-GCM decryption.
Typescript
import crypto from "node:crypto"

const cipher = crypto.createDecipheriv("aes-256-gcm", key, iv);

let decrypted = cipher.update(encrypted);

// Call setAuthTag() before calling final()
cipher.setAuthTag(authTag);

decrypted += cipher.final();

console.log(`Decrypted: ${decrypted})
Python Using the `pycryptodome` library
from Crypto.Cipher import AES

cipher = AES.new(key, AES.MODE_GCM, iv)

# Validate authentication tag and decrypt
try:
   decrypted = cipher.decrypt_and_verify(ciphertext, auth_tag)
   return decrypted.decode()
except ValueError:
   return "Decryption failed: Authentication tag mismatch"