AES-CBC Generate Tokens using a Tokenization Method

This document describes how to generate tokens using the AES-CBC method.

  1. The SHA256 value of the "Encryption Key" set in Elice is used as the key for the AES256 encryption algorithm.

  2. The IV value for AES256 is a 128-bit random value. (Therefore, even if the same content is encrypted, different encryption results are obtained each time encryption is performed.)

  3. The input data length for AES256 must be a multiple of the IV length, so use the PKCS7 padding algorithm to make it a multiple of 128 bits.

  4. Encrypt the padded data using AES256 + CBC with the key and IV values obtained above.

  5. To decrypt the data, it is necessary to pass the IV value along with the encrypted data. Therefore, concatenate the IV value and the encrypted data in order. (For example, if the IV is "asdf" and the encrypted data is "qwer", the result will be "asdfqwer".)

  6. Convert the completed binary token to a string by applying base64 encoding to facilitate transmission via URL.

  7. Additionally, if necessary, URL encode the base64 string before transmission.

Code Examples

Python

Use the following third-party library for encryption:

Since standard AES encryption algorithm and PKCS7 padding are used, you can implement it using other libraries that provide the same algorithm.

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 an arbitrarily set value for demonstration purposes
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)  # Use a 16-byte (128-bit) IV.
    padder = padding.PKCS7(128).padder()  # Use 128-bit PKCS7 padding.

    backend = default_backend()  # Usually OpenSSL is used as the backend.
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)  # Use 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': '김토끼',
    '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();

    // In JAVA, PKCS5Padding actually operates as 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 Encrypting a Token

The following is a step-by-step example of encrypting the token information using the AES-CBC encryption method with the encryption key "this_is_secret_key".

{
    "uid": "test-user-1",
    "fullname": "김토끼",
    "email": "tokki.kim@test.com",
    "ts": 1586961104518
}
  1. The token information is serialized into the following JSON string:

    {"uid": "test-user-1", "fullname": "김토끼", "email": "tokki.kim@test.com", "ts": 1586961104518}
  2. The Key value used for AES256 is the SHA256 hash value of the UTF-8 encoded string "this_is_secret_key". The hash value is a binary value, so when represented in HEX, it is as follows:

    7fa96cf6e1987fa29569acc71a2377cff0421aace8f15d8f165e06dc162f13f5
  3. During the encryption process, the IV value is a randomly changing value used each time, which ensures that even the same token information always results in a different encrypted token. The randomly generated IV value used in this example is as follows. The IV value is also a binary value, so it is represented in HEX:

    acc90cc1b46ca2d3c6153e866a1a0b68
  4. Using the Key and IV values above, encrypt the serialized token information, then concatenate the IV and the encrypted token and encode them in Base64. The resulting token is as follows:

    rMkMwbRsotPGFT6GahoLaLySf3ppn+InnAuntavvhkBYwxo7t6XcvyZ2neAHTk4Va1cJsi/ckzZERar4QSSeTlbBUwpKGF2rQktcxrf+WerjVwlVyfDC6ge+zZZIlS+ZF794zrOX346tMLXxljX7kd2D4XwdifRvLpJaN7/Ij5c=

Last updated