Using encrypted payloads
- This document covers all the logic related to encrypting and decrypting payloads.
- We support encrypted payloads in select APIs.
Getting started
Important
- Encrypted payloads are not enabled by default so please reach out to our team if you want this functionality
- We will be happy to assist wherever possible, please reach out to help@nimbbl.biz
Generating your Encryption/Decryption key
- You will be using the access_secret shared with you on your dashboard for the encryption and decryption process.
- Remove the string part up to and including the text
access_secret
.- For example, if your
access_secret
isaccess_secret_a1x7BxYkRpB4p5H
, you should removeaccess_secret_
to geta1x7BxYkRpB4p5H
.
- For example, if your
- Generate a SHA256 digest of the remaining key obtained from step 2. This digest will be used as the encryption/decryption key.
Steps for encryption
- AES Encrypt the unencrypted payload with AES Mode: AES_GCM.
- Encrypt the data and create a digest.
- Create a bytes text in following format.
- First 16 bytes as nonce.
- After that the encrypted payload.
- Last 16 bytes as authentication tag (This is either automatically generated or has to be manually passed depending on the programming language).
- Convert the bytes text (created in step 3) to hex.
- Send the generated hex in the form of string in the encrypted_payload key.
Sample Code
- Given below is the code snippet for encryption.
- If you need help please reach out to help@nimbbl.biz.
- Java
- Python
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
public class Main {
private static final int GCM_TAG_LENGTH = 16; // in bytes
private static final int GCM_NONCE_LENGTH = 16; // in bytes (explicit padding applied for <=16 bytes)
// Generate a key using SHA-256
public static byte[] generateKey(String secret) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(secret.getBytes(StandardCharsets.UTF_8));
}
// Convert byte array to Hex string
public static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
hexString.append(String.format("%02x", b));
}
return hexString.toString();
}
// PKCS7 Padding for nonce
public static byte[] padNonce(byte[] nonce) {
if (nonce.length >= GCM_NONCE_LENGTH) return nonce;
byte[] paddedNonce = new byte[GCM_NONCE_LENGTH];
System.arraycopy(nonce, 0, paddedNonce, 0, nonce.length);
for (int i = nonce.length; i < GCM_NONCE_LENGTH; i++) {
paddedNonce[i] = (byte) (GCM_NONCE_LENGTH - nonce.length);
}
return paddedNonce;
}
// Encrypt data using AES-GCM
public static String encrypt(String message, String secret) throws Exception {
byte[] key = generateKey(secret);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// Generate random nonce
byte[] nonce = new byte[GCM_NONCE_LENGTH];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(nonce);
nonce = padNonce(nonce);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] encryptedPayload = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
// Extract authentication tag from the encrypted data
int tagStartIndex = encryptedPayload.length - GCM_TAG_LENGTH;
byte[] payloadWithoutTag = new byte[tagStartIndex];
byte[] tag = new byte[GCM_TAG_LENGTH];
System.arraycopy(encryptedPayload, 0, payloadWithoutTag, 0, tagStartIndex);
System.arraycopy(encryptedPayload, tagStartIndex, tag, 0, GCM_TAG_LENGTH);
// Concatenate nonce, payload, and tag
byte[] result = new byte[nonce.length + payloadWithoutTag.length + tag.length];
System.arraycopy(nonce, 0, result, 0, nonce.length);
System.arraycopy(payloadWithoutTag, 0, result, nonce.length, payloadWithoutTag.length);
System.arraycopy(tag, 0, result, nonce.length + payloadWithoutTag.length, tag.length);
// Convert to hex
return bytesToHex(result);
}
}
import hashlib
import orjson
from typing import Union
from Cryptodome.Cipher import AES
from nimbbl.errors import DecryptionErrorException
class NimbblSymetricEncryption:
"""
Nimbbl Symetric Encryption for Payload/Response encryption and decryption.
Implements: AES_GCM Encryption/Decryption
"""
# Internal Private Fields
# __encryption_key
ENCRYPTION_MODE = AES.MODE_GCM
def __init__(self, access_secret: str) -> None:
self.__generate_key(access_secret=access_secret)
def __generate_key(self, access_secret: str, number_of_iternations: int = 1):
"""
Generates Encryption key
Returns: None
Note: Any mismatch in number_of_iterations with the merchant will fail encryption and decryption.
Use this field carefully.
"""
byte_key = access_secret.replace("access_secret_", "").encode("UTF-8")
for _ in range(number_of_iternations):
byte_key = hashlib.sha256(byte_key)
self.__encryption_key = byte_key.digest()
def encrypt(self, data: Union[str, dict, bytes]) -> str:
"""
Returns Ecrypted String:
Encrpyted Payload Format:
- 16 Byte Nonce
- Encrypted Payload
- 16 Bit Verify Tag
"""
if type(data) == dict:
data = orjson.dumps(data)
elif type(data) == str:
data = data.encode("UTF-8")
cipher = AES.new(self.__encryption_key, NimbblSymetricEncryption.ENCRYPTION_MODE)
ciphertext, authTag = cipher.encrypt_and_digest(data)
encrpyted_data = bytearray()
encrpyted_data.extend(cipher.nonce)
encrpyted_data.extend(ciphertext)
encrpyted_data.extend(authTag)
return bytes(encrpyted_data).hex()
Steps for decryption
- Convert the hex string (received in the
encrypted_response
key) back to bytes. - Extract the first 16 bytes as the nonce.
- Extract the last 16 bytes as the authentication tag.
- The remaining bytes in the middle are the encrypted payload.
- Initialize the AES cipher in GCM mode with the extracted nonce and the encryption key.
- Decrypt the encrypted payload and verify it using the authentication tag.
Sample Code
- Given below is the code snippet for decryption.
- If you need help please reach out to help@nimbbl.biz.
- Java
- Python
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Main {
private static final int GCM_TAG_LENGTH = 16; // in bytes
private static final int GCM_NONCE_LENGTH = 16; // in bytes
// Generate a key using SHA-256
public static byte[] generateKey(String secret) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(secret.getBytes(StandardCharsets.UTF_8));
}
// Convert Hex string to byte array
public static byte[] hexStringToByteArray(String hexString) {
int length = hexString.length();
byte[] byteArray = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
int value = Integer.parseInt(hexString.substring(i, i + 2), 16);
byteArray[i / 2] = (byte) value;
}
return byteArray;
}
// Decrypt data using AES-GCM
public static String decrypt(String encryptedMessage, String secret) throws Exception {
byte[] encryptedData = hexStringToByteArray(encryptedMessage);
byte[] key = generateKey(secret);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Extract nonce, payload, and tag
byte[] nonce = new byte[GCM_NONCE_LENGTH];
System.arraycopy(encryptedData, 0, nonce, 0, nonce.length);
byte[] tag = new byte[GCM_TAG_LENGTH];
System.arraycopy(encryptedData, encryptedData.length - GCM_TAG_LENGTH, tag, 0, GCM_TAG_LENGTH);
byte[] payload = new byte[encryptedData.length - GCM_NONCE_LENGTH - GCM_TAG_LENGTH];
System.arraycopy(encryptedData, GCM_NONCE_LENGTH, payload, 0, payload.length);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
// Concatenate payload and tag for decryption
byte[] payloadWithTag = new byte[payload.length + tag.length];
System.arraycopy(payload, 0, payloadWithTag, 0, payload.length);
System.arraycopy(tag, 0, payloadWithTag, payload.length, tag.length);
byte[] plaintext = cipher.doFinal(payloadWithTag);
return new String(plaintext, StandardCharsets.UTF_8);
}
}
import hashlib
import orjson
from typing import Union
from Cryptodome.Cipher import AES
from nimbbl.errors import DecryptionErrorException
class NimbblSymetricEncryption:
"""
Nimbbl Symetric Encryption for Payload/Response encryption and decryption.
Implements: AES_GCM Encryption/Decryption
"""
# Internal Private Fields
# __encryption_key
ENCRYPTION_MODE = AES.MODE_GCM
def __init__(self, access_secret: str) -> None:
self.__generate_key(access_secret=access_secret)
def __generate_key(self, access_secret: str, number_of_iternations: int = 1):
"""
Generates Encryption key
Returns: None
Note: Any mismatch in number_of_iterations with the merchant will fail encryption and decryption.
Use this field carefully.
"""
byte_key = access_secret.replace("access_secret_", "").encode("UTF-8")
for _ in range(number_of_iternations):
byte_key = hashlib.sha256(byte_key)
self.__encryption_key = byte_key.digest()
def decrypt(self, encrypted_data: str, return_as_bytes: bool = False) -> str:
try:
encrpyted_message = bytes.fromhex(encrypted_data)
nonce = encrpyted_message[:16]
tag = encrpyted_message[-16:]
encrpyted_message = encrpyted_message[16:-16]
cipher = AES.new(self.__encryption_key, AES.MODE_GCM, nonce=nonce)
message = cipher.decrypt_and_verify(encrpyted_message, tag)
except Exception:
raise DecryptionErrorException()
if return_as_bytes:
return message
return message.decode()