メインコンテンツまでスキップ

AES-CBC方式でのトークン生成

この文書では、AES-CBC方式でトークンを生成する方法を説明します。

  1. エリスで設定した「暗号鍵」の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() # SH256を通じてCP_SECRET_KEYを256-bit Keyに変換します。
iv = os.urandom(16) # 16-byte (128-bit) IVを使用します。
padder = padding.PKCS7(128).padder() # 128-bit PKCS7 paddingを使用します。

backend = default_backend() # 通常OpenSSLがbackendとして使用されます。
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+ 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();

// 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+ only
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に使用されるキー値はUTF-8エンコーディングされた文字列"this_is_secret_key"のSHA256ハッシュ値を使用します。ハッシュ値はバイナリ値であるため、HEXで表示すると次のようになります。

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

    acc90cc1b46ca2d3c6153e866a1a0b68
  4. 上記のキー値とIV値を使用してシリアライズされたトークン情報を暗号化した後、IVと暗号化されたトークンを連結してBase64エンコードすると、最終的に以下のトークンを得ることができます。

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