AES-CBC การสร้างโทเค็นด้วยวิธีการ

เอกสารนี้อธิบายวิธีการสร้างโทเค็นโดยใช้วิธี AES-CBC

  1. ใช้ค่า SHA256 ของ "คีย์ลับ" ที่ตั้งค่าใน Elice เป็นคีย์สำหรับขั้นตอนการเข้ารหัส AES256

  2. ใช้ค่า IV ขนาด 128-bit ที่เป็นค่าสุ่ม (ดังนั้นแม้ว่าข้อมูลที่เหมือนกันจะถูกเข้ารหัสได้ผลลัพธ์ที่แตกต่างกันทุกครั้ง)

  3. ขนาดข้อมูลเข้าของ AES256 ต้องเป็นตัวคูณของขนาดของ IV ดังนั้นใช้อัลกอริทึม PKCS7 padding เพื่อปรับขนาดให้เป็นตัวคูณของ 128-bit

  4. เข้ารหัสข้อมูลที่ได้ผ่านขั้นตอนที่ 3 ด้วย key และ IV ที่ได้จากขั้นตอนที่ 1 และ 2 โดยใช้ AES256+CBC

  5. เนื่องจากต้องมี IV เพื่อถอดรหัสข้อมูล จึงต้องเชื่อมต่อ IV และข้อมูลที่ถูกเข้ารหัสเข้าด้วยกัน (เช่น ถ้า IV เป็น asdf และข้อมูลที่เข้ารหัสคือ qwer ผลลัพธ์ที่ได้คือ asdfqwer)

  6. แปลงข้อมูลที่เต็มรูปแบบเป็นตัวอักษรภายในโทเค็นที่เป็นไบนารีให้เป็นภาษาใกล้เคียง URL โดยใช้การเข้ารหัส base64

  7. ตามความจำเป็นอาจจะต้องเข้ารหัส base64 ที่ถูกเข้ารหัสเป็น URL ก่อนการส่ง

ตัวอย่างโค้ด

Python

ใช้ไลบรารีบุคคลภายนอกต่อไปนี้สำหรับเข้ารหัส

เนื่องจากใช้วิธีเข้ารหัส AES แบบมาตรฐานและ padding PKCS7 ดังนั้นคุณสามารถใช้ไลบรารีอื่นที่ให้บริการวิธีเดียวกันได้

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

# ค่านี้ถูกตั้งค่าเพื่อตัวอย่างเท่านั้น
CP_SECRET_KEY = '0123456789abcdefg'

def encrypt(content: str, secret_key: str) -> str:
    key = hashlib.sha256(secret_key.encode('utf-8')).digest()  # แปลง CP_SECRET_KEY เป็น Key ขนาด 256-bit ด้วย SHA256
    iv = os.urandom(16)  # ใช้ IV ขนาด 16-byte (128-bit)
    padder = padding.PKCS7(128).padder()  # ใช้ padding PKCS7 ขนาด 128-bit

    backend = default_backend()  # โดยทั่วไปใช้ OpenSSL เป็น backend
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)  # ใช้ AES โหมด CBC
    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 ขึ้นไป
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

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 ใน JAVA จริงตรงกับ 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 ขึ้นไปเท่านั้น
    byte[] resultBase64 = Base64.getEncoder().encode(result);
    return new String(resultBase64);

		// // เฉพาะ JDK 6, JDK 7
    // return DatatypeConverter.printBase64Binary(result);
}

ตัวอย่างขั้นตอนการเข้ารหัสโทเค็น

นี่คือตัวอย่างขั้นตอนการเข้ารหัสโทเค็นด้วยคีย์ "this_is_secret_key" ในรูปแบบ AES-CBC

{
    "uid": "test-user-1",
    "fullname": "김토끼",
    "email": "tokki.kim@test.com",
    "ts": 1586961104518
}
  1. ข้อมูลโทเค็นถูกใส่ลงในสตริง JSON ต่อไปนี้

    {"uid": "test-user-1", "fullname": "김토끼", "email": "tokki.kim@test.com", "ts": 1586961104518}
  2. Key ที่ใช้ใน AES256 ถูกสร้างโดยการแปลงสตริง "this_is_secret_key" (ในรูปแบบ UTF-8) เป็น SHA256 hash value และแสดงผลลัพธ์เป็นตัวเลข HEX ต่อไปนี้

    7fa96cf6e1987fa29569acc71a2377cff0421aace8f15d8f165e06dc162f13f5
  3. IV ของการเข้ารหัสที่เปลี่ยนไปทุกครั้งถูกสร้างโดยการสร้างค่าสุ่ม ในตัวอย่างนี้เป็นตัวเลข HEX ต่อไปนี้

    acc90cc1b46ca2d3c6153e866a1a0b68
  4. ข้อมูลโทเค็นที่ถูกเข้ารหัสด้วย Key และ IV ที่ได้จากขั้นตอนที่ 1 และ 2 ก่อนที่จะต่อกันด้วย Base64 จะได้โทเค็นต่อไปนี้

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

Last updated