AES-CBC 方法によるトークン生成

このドキュメントでは、AES-CBC方式を使用してトークンを生成する方法について説明します。

  1. Eliceで設定した「暗号鍵」のSHA256値をAES256暗号化アルゴリズムの鍵として使用します。

  2. AES256のIV値には128ビットのランダムな値を使用します。(したがって、同じ内容でも暗号化ごとに異なる暗号化結果が得られます)

  3. AES256の入力データの長さはIVの長さの倍数である必要があります。そのため、PKCS7パディングアルゴリズムを使用して128ビットの倍数に合わせます。

  4. AES256+CBCに上記で取得した鍵とIV値を使用してパディングが追加されたデータを暗号化します。

  5. 復号化のためにIV値を一緒に渡す必要があるため、IV値と暗号化されたデータを順番に連結します。(例えば、IVが asdf、暗号化されたデータが qwer の場合、結果値は asdfqwer になります。)

  6. 完成したバイナリトークンをURLで簡単に送信するために、base64エンコーディングを適用して文字列に変換します。

  7. 必要に応じて、base64文字列をURLエンコードして送信します。

コード例

Python

暗号化には以下のサードパーティライブラリを使用します。

標準のAES暗号化アルゴリズムと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を256ビットのキーに変換します。
    iv = os.urandom(16)  # 16バイト(128ビット)のIVを使用します。
    padder = padding.PKCS7(128).padder()  # 128ビットのPKCS7パディングを使用します。

    backend = default_backend()  # 通常、OpenSSLがバックエンドとして使用されます。
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)  # CBCモードのAESを使用します。
    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();

    // JavaでのPKCS5Paddingは実際には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. AES256に使用されるKeyはUTF-8でエンコードされた文字列"this_is_secret_key"のSHA256ハッシュ値を使用します。ハッシュ値はバイナリ値なので、HEXで表示すると次のようになります。

    7fa96cf6e1987fa29569acc71a2377cff0421aace8f15d8f165e06dc162f13f5
  3. 暗号化プロセスで使用されるIV値は毎回変わるランダムな値が使用されるため、同じトークン情報を暗号化しても常に異なる結果トークンが得られ、これはセキュリティを維持するのに役立ちます。この例で使用されるランダムに生成されたIV値は次のようになります。IV値もバイナリ値なので、HEXで表示しています。

    acc90cc1b46ca2d3c6153e866a1a0b68
  4. 上記のKey値とIV値を使用して直列化されたトークン情報を暗号化し、IVと暗号化されたトークンを連結してBase64エンコーディングすれば、最終的に次のトークンが得られます。

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

Last updated