Token Generation Using AES-CBC
This document describes how to generate tokens using the AES-CBC method.
- Use the SHA256 value of the "secret key" set by Elice as the key for the AES256 encryption algorithm.
- The IV value for AES256 should be a 128-bit random value. (This ensures that even if the content is the same, different encryption results are obtained each time encryption is performed.)
- Since the input data length for AES256 needs to be a multiple of the IV length, use the PKCS7 padding algorithm to adjust it to a multiple of 128-bits.
- Encrypt the padded data using AES256 with CBC mode, using the key and IV obtained from the previous steps.
- To facilitate decryption, the IV must be transmitted together, so append the IV and encrypted data in that order. (For example, if the IV is 'asdf' and the encrypted data is 'qwer', the resulting value will be 'asdfqwer'.)
- Apply base64 encoding to the completed binary token to convert it into a string that is easier to transmit via URLs.
- Additionally, if necessary, URL-encode the base64 string for transmission.
Code Example
Python
For encryption, the following third-party library is used.
Since it uses the standard AES encryption algorithm and PKCS7 padding, it can be implemented using other libraries that offer the same algorithms.
import base64
import hashlib
import json
import os
import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
# This value is arbitrarily set for the example
CP_SECRET_KEY = '0123456789abcdefg'
def encrypt(content: str, secret_key: str) -> str:
key = hashlib.sha256(secret_key.encode('utf-8')).digest() # Convert CP_SECRET_KEY to a 256-bit Key using SHA256.
iv = os.urandom(16) # Using a 16-byte (128-bit) IV.
padder = padding.PKCS7(128).padder() # Using 128-bit PKCS7 padding.
backend = default_backend() # OpenSSL is generally used as the backend.
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) # Using AES in CBC mode.
encryptor = cipher.encryptor()
content_padded = padder.update(content.encode('utf-8')) + padder.finalize()
content_enc = encryptor.update(content_padded) + encryptor.finalize()
return base64.b64encode(iv + content_enc).decode('utf-8')
token_info = {
'uid': 'test-user-1',
'fullname': 'Kim Tokki',
'email': 'tokki.kim@test.com',
'courseId': 1234,
'ts': int(time.time() * 1000)
}
token = encrypt(json.dumps(token_info), CP_SECRET_KEY)
C#
using System;
using System.Text;
using System.Security.Cryptography;
static String Encrypt(String content, String secretKey)
{
using (SHA256 sha256 = SHA256.Create())
using (Aes aes = Aes.Create())
{
byte[] iv = new byte[16];
rngCsp.GetBytes(iv);
aes.Key = sha256.ComputeHash(Encoding.UTF8.GetBytes(secretKey));
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] contentBytes = Encoding.UTF8.GetBytes(content);
byte[] contentEnc = aes.CreateEncryptor()
.TransformFinalBlock(contentBytes, 0, contentBytes.Length);
byte[] result = new byte[iv.Length + contentEnc.Length];
iv.CopyTo(result, 0);
contentEnc.CopyTo(result, iv.Length);
return Convert.ToBase64String(result);
}
}
Java
import java.lang.System;
import java.security.MessageDigest;
import java.util.Base64; // JDK 8+ only
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter; // JDK 6, JDK 7 only
static String encrypt(String content, String secretKey) throws Exception {
byte[] iv = new byte[16];
new Random().nextBytes(iv);
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(secretKey.getBytes("UTF-8"));
byte[] key = sha256.digest();
// PKCS5Padding in JAVA actually behaves like PKCS7Padding.
Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
aes.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] contentBytes = content.getBytes("UTF-8");
byte[] contentEnc = aes.doFinal(contentBytes, 0, contentBytes.length);
byte[] result = new byte[iv.length + contentEnc.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(contentEnc, 0, result, iv.length, contentEnc.length);
// JDK 8+ only
byte[] resultBase64 = Base64.getEncoder().encode(result);
return new String(resultBase64);
// // JDK 6, JDK 7
// return DatatypeConverter.printBase64Binary(result);
}
Step-by-Step Example of Token Encryption
Below is a step-by-step example of encrypting the token information using the secret key "this_is_secret_key" with AES-CBC encryption.
{
"uid": "test-user-1",
"fullname": "Kim Tokki",
"email": "tokki.kim@test.com",
"ts": 1586961104518
}
-
The token information is serialized into the following JSON string.
{"uid": "test-user-1", "fullname": "Kim Tokki", "email": "tokki.kim@test.com", "ts": 1586961104518}
-
The key value used in AES256 is the SHA256 hash of the UTF-8 encoded string "this_is_secret_key". The hash value is a binary value, so when displayed in HEX, it appears as follows.
7fa96cf6e1987fa29569acc71a2377cff0421aace8f15d8f165e06dc162f13f5
-
During the encryption process, a randomly generated IV is used each time, which is why the same token information will produce different result tokens, helping to maintain security. The randomly generated IV used in this example is also binary and is represented as HEX below.
acc90cc1b46ca2d3c6153e866a1a0b68
-
After encrypting the serialized token information using the above Key and IV values, and appending the IV and encrypted token before base64 encoding, the resulting token is produced as follows.
rMkMwbRsotPGFT6GahoLaLySf3ppn+InnAuntavvhkBYwxo7t6XcvyZ2neAHTk4Va1cJsi/ckzZERar4QSSeTlbBUwpKGF2rQktcxrf+WerjVwlVyfDC6ge+zZZIlS+ZF794zrOX346tMLXxljX7kd2D4XwdifRvLpJaN7/Ij5c=