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を取ることでブロック単位での一致を判定することをできなくする
- 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のパラメータを調べてみる
- 繰り返し回数
- IV
- 16バイトのRandomGenerateされたバイト列を使って、生成された暗号文の最初にくっつけている spring-security/AesBytesEncryptor.java at master · spring-projects/spring-security · GitHub
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方式とは違うらしい。
何となくわかったつもりでブログにまとめようとしたら意外と理解していない部分が多かったので調べ直した部分も多く、やはりこういうアウトプットは大事だなぁと改めて思った次第です。