miyasakura’s diary

日記です。

Spring SecurityでAES暗号化された文字列をRubyで復号してみた

AESで暗号化されたテキストを手元で復号しようと思ったけど意外とAESのことを知らなくて苦労したので簡単にメモ。(勉強を兼ねて無理矢理RubyでやっただけでSpringで普通に復号すれば良いだけなので実用性は無いと思う。)

Spring Securityでの暗号化

こんな感じで暗号化されたもの。

package com.example.security;

import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.stereotype.Component;

@Component
public class Encryption {
    public String encrypt(String password, String plainText) {
        String salt = "0123456789abcdef"
        TextEncryptor encrypt = Encryptors.text(password, salt);
        return encrypt.encrypt(plainText);
    }
}

詳細な暗号化方式はと言うと、下記がドキュメントに書いてあった。

Spring Securityでは、共通鍵暗号化方式を使用した暗号化および復号の機能を提供している。
暗号化アルゴリズムは256-bit AES using PKCS #5’s PBKDF2 (Password-Based Key Derivation Function #2) である。
暗号利用モードはCBC、パディング方式はPKCS5Paddingである。

なるほど、わからん。Wikipediaとかみつつ方式について理解してみる。

  • 256は鍵の長さで、128, 196, 256から選べるうち256bitのものを使っている。鍵の長さが長いほうが(計算量が多くなるので)セキュリティ的に良い
  • 暗号利用モードとはブロック暗号化のメカニズムのことでCBC(Cipher Block Chaining)は前のブロックのXORを取ることでブロック単位での一致を判定することをできなくする
    • CBCの初期ブロックはIV(Initialization Vector:初期化ベクトル)を用いることでメッセージごとのユニーク性を確保する
  • PKCS #5は[暗号化]ブロック暗号とは(AES/DES/Blowfish PKCS5Padding ECB/CBC IV) - [技術資料 + 技術資料] ぺんたん info によると余ったブロックをどう埋めるかという方式
  • PBKDF2というのはパスワードから何かしらの鍵を生成するときに計算量を増やして総当りをしづらくする方式とのこと。パスワードとソルトを使ったハッシュ化を複数回イテレーションすることで実現する。
    • ハッシュ化の繰り返し回数はパラメータぽい

それぞれはわかったので全体の処理の流れを調べると Closure Library の暗号化モジュールの使い方 - WebOS Goodies この図がわかりやすかった。

saltとpasswordからpbkdf2を使ってkeyを生成 → 平文データをIVとkeyを使ってCBC方式によりブロック単位にAES暗号化 という流れ。

復号化にあたっては暗号化の方式がわかった上で、

  • 復号対象のテキスト
  • PBKDF2の
    • 繰り返し回数
    • Salt
    • Password
  • CBCのIV

がわかれば良さそう。上記の内、Spring Securityで外側から指定しないのは繰り返し回数とIVなのでこれが分かれば他の環境でも復号はできるはず。

Spring Securityのパラメータを調べてみる

Ruby で復号する

これらを踏まえてRubyで復号してみる。 class OpenSSL::Cipher (Ruby 2.6.0) を見つつ実装すると下記のようになった。

pack関数でバイト列に変換してあげないとダメだったところでややハマった。

require 'openssl'

salt = ['0123456789abcdef'].pack('H*') # 8バイト
password = 'somepassword'  # 暗号化時に入力したパスワード
cipher = '7bc2a6f8e684a2963285064524a0f85fb3b47bc6300c699699fe99387a9f7b10' # Spring Securityから出力された文字列

# PBKDF2によるkeyの作成。256は鍵の長さ。
key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 1024, 256)

# IVを取り出す(16バイトだがhex文字列になっているので32文字)
iv = [cipher[0, 32]].pack('H*')

# AESによる暗号文本体
encrypted = [cipher[32, cipher.length]].pack('H*')

dec = OpenSSL::Cipher.new("AES-256-CBC")
dec.decrypt
dec.key = key
dec.iv = iv

decrypted = dec.update(encrypted) + dec.final

puts(decrypted)

ちなみにSpring Securityと同等の暗号化をRubyでしたい場合はこんな感じ。

require 'openssl'
require 'securerandom'

salt = ['0123456789abcdef'].pack('H*')
password = 'somepassword'
text = 'hirabun'

key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, 1024, 256)
iv = SecureRandom.random_bytes(16)

enc = OpenSSL::Cipher.new("AES-256-CBC")
enc.encrypt
enc.key = key
enc.iv = iv

encrypted = enc.update(text) + enc.final
cipher = (iv + encrypted).unpack('H*').first

puts(cipher)

感想

AES本体のアルゴリズムには踏み込んでいないものの、それでも結構知らない部分が多かったので勉強になった。ちなみにSpring SecurityのAES暗号時にはstrongという指定できるようでそちらだと AES with GCMとなって今回のCBC方式とは違うらしい。

何となくわかったつもりでブログにまとめようとしたら意外と理解していない部分が多かったので調べ直した部分も多く、やはりこういうアウトプットは大事だなぁと改めて思った次第です。