Javaの標準AES暗号化アルゴリズム互換のRuby(Python)実装
随分前に悩んだことでまた悩んでしまったのでメモ。
Java/Python/Rubyのそれぞれの実装をまとめました。
Javaで"AES"と指定して暗号化/復号したデータをRubyで復元する際、 AESの知識が乏しいせいでRubyでどうやって復元すれば良いのかわからず困ってしまったため、 次回から悩まないためにJavaの標準AESアルゴリズムと互換性のあるRubyの実装書いてみました。
ついでに最近少し触っているPythonでも書いてみました。
AESはブロック暗号です。 ブロック暗号についてはコチラが詳しいです。
Javaのjavax.crypto.Cipherクラスで単純に"AES"として暗号化すると"AES/ECB/PKCS5Padding"という形式になります。
鍵長: 128bit
暗号モード: ECB
パディング方式: PKCS5Padding
暗号モードのECBはある文字列を暗号化すると必ず同じ文字列となるのでCBCやOFBの方が良さそうですが、 もう既にECBで作成してしまったコードがあるので今回は"AES/ECB/PKCS5Padding"での互換実装とします。
PythonのCrypto.Cipher.AESは自動でパディングしてくれないので、 独自にパディング処理を組み込む必要があります。
Java:
import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class AESCipher { private SecretKeySpec key; public AESCipher(String key) { this.key = new SecretKeySpec(key.getBytes(), "AES"); } public String encrypt(String str) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, this.key); return Base64.encodeBase64String(cipher.doFinal(str.getBytes())); } public String decrypt(String str) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, this.key); return new String(cipher.doFinal(Base64.decodeBase64(str))); } public static void main(String[] args) throws GeneralSecurityException { String key = "1234567890123456"; String text = "nori3tsu"; AESCipher cipher = new AESCipher(key); String enctext = cipher.encrypt(text); System.out.println(enctext); String dectext = cipher.decrypt(enctext); System.out.println(dectext); } }
Ruby:
require 'openssl' require 'base64' class AESCipher def initialize(key) @key = key end def encrypt(text) cipher = OpenSSL::Cipher.new("AES-128-ECB") cipher.encrypt() cipher.key = @key enc = cipher.update(text) enc << cipher.final() Base64.encode64(enc) end def decrypt(text) enc = Base64.decode64(text) cipher = OpenSSL::Cipher.new("AES-128-ECB") cipher.decrypt() cipher.key = @key cipher.update(enc) + cipher.final end end if __FILE__ == $0 key="1234567890123456" text = "nori3tsu" chipher = AESCipher.new(key) enctext = chipher.encrypt(text) puts enctext dectext = chipher.decrypt(enctext) puts dectext end
Python:
from Crypto.Cipher import AES import base64 BS = 16 pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) unpad = lambda s : s[0:-ord(s[-1])] class AESCipher: def __init__(self, key): self.key = key def encrypt(self, text): cipher = AES.new(self.key, AES.MODE_ECB) raw = pad(text) enc = cipher.encrypt(raw) return base64.b64encode(enc) def decrypt(self, text): cipher = AES.new(self.key, AES.MODE_ECB) enc = base64.b64decode(text) return unpad(cipher.decrypt(enc)) if __name__== "__main__": key="1234567890123456" text = "nori3tsu" cipher = AESCipher(key) enctext = cipher.encrypt(text) print "%s" % enctext dectext = cipher.decrypt(enctext) print "%s" % dectext
Pythoの参考コード。CBC+ランダムIV版。 https://gist.github.com/crmccreary/5610068