AES-CBC方式でのトークン生成
この文書では、AES-CBC方式でトークンを生成する方法を説明します。
- エリスで設定した「暗号鍵」のSHA256値をAES256暗号化アルゴリズムのキーとして使用します。
- AES256のIV値は128ビットのランダム値を使用します。 (そのため、同じ内容であっても暗号化するたびに異なる暗号化結果が得られます)
- AES256の入力データの長さはIVの長さの倍数でなければならないため、PKCS7パディングアルゴリズムを使用して128ビットの倍数に調整します。
- AES256+CBCにおいて、上記で取得したキーとIV値を使用してパディングが追加されたデータを暗号化します。
- 復号化のためにIV値を一緒に渡す必要があるため、IV値と暗号化されたデータを順に連結します。 (例えば、IVがasdf、暗号化されたデータがqwerの場合、結果はasdfqwerになります。)
- 完成したバイナリトークンをURLで渡しやすくするために、base64エンコードを適用して文字列に変換します。
- 追加的に必要に応じて、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);
}