読者です 読者をやめる 読者になる 読者になる

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