本記事よりも、2019-3-2 に書き改めた方を参照すべし。。
oboe2uran.hatenablog.com
Java と Pyrhon 間の AES 256 暗合複合ができるようにするものです。
他言語と相互間の暗合複合を考慮の AES 暗合複合 Python のコード - Oboe吹きプログラマの黙示録
の続きです。
Java 側のコード
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * AES 256 暗合複合. mode : ECB or CBC padding : PKCS5Padding */ public final class AESCipher{ private SecretKeySpec key; private IvParameterSpec iv; private byte[] ivary; private String mode; private AESCipher(String password, byte[] ivector){ try{ byte[] keydata = password.getBytes(); MessageDigest sha = MessageDigest.getInstance("SHA-256"); keydata = sha.digest(keydata); keydata = Arrays.copyOf(keydata, 32); key = new SecretKeySpec(keydata, "AES"); mode = "AES/CBC/PKCS5Padding"; ivary = Arrays.copyOf(ivector, 16); iv = new IvParameterSpec(ivary); }catch(NoSuchAlgorithmException e){ throw new RuntimeException(e.getMessage()); } } private AESCipher(String password){ try{ byte[] keydata = password.getBytes(); MessageDigest sha = MessageDigest.getInstance("SHA-256"); keydata = sha.digest(keydata); keydata = Arrays.copyOf(keydata, 32); key = new SecretKeySpec(keydata, "AES"); mode = "AES/ECB/PKCS5Padding"; }catch(NoSuchAlgorithmException e){ throw new RuntimeException(e.getMessage()); } } /** * AES CBC モードインスタンス生成 * @param keyword 共通鍵 * @param ivector 初期ベクトル * @return AESCipher */ public static AESCipher of(String keyword, byte[] ivector){ return new AESCipher(keyword, ivector); } /** * AES ECB モードインスタンス生成 * @param keyword 共通鍵 * @param ivector 初期ベクトル * @return AESCipher */ public static AESCipher of(String keyword){ return new AESCipher(keyword); } /** * CBC モードで使用する Initilize Vector * @return 16byte byte[] */ public byte[] getIV(){ return ivary; } /** * 暗合化. * @param message 平文 * @return 暗合文byte[] */ public byte[] encrypt(String message){ try{ Cipher cipher = Cipher.getInstance(mode); cipher.init(Cipher.ENCRYPT_MODE, key, iv); return cipher.doFinal(message.getBytes()); }catch(Exception e){ throw new RuntimeException(e); } } /** * 複合 * @param encbytes 暗合文byte[] * @return 平文 */ public byte[] decrypt(byte[] encbytes){ try{ Cipher cipher = Cipher.getInstance(mode); cipher.init(Cipher.DECRYPT_MODE, key, iv); return cipher.doFinal(encbytes); }catch(Exception e){ throw new RuntimeException(e); } } /** * ランダムキー生成(長さ=16 を指定して IV 作成する時に利用する) * @param len 長さ * @return a-zA-Z0-9 の文字列 */ public static String randomKey(int len){ SecureRandom secureRandom = new SecureRandom(); String CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; StringBuilder sb = new StringBuilder(); for(int i=0; i < len; i++){ sb.append(CHARACTERS.charAt(secureRandom.nextInt(CHARACTERS.length()))); } return sb.toString(); } }
これと、先日の Base64 かどうかをチェックするクラス
Base64 かどうか判定する - Oboe吹きプログラマの黙示録
→ B64Util クラス
テスト用にファイルを読み書きするツール、・・・今回サンプル用のクラス
サンプルは、暗号文、キーをファイルに書いて受け渡す為。
ファイルの読み書き→FileTool
public final class FileTool{ private FileTool(){} public static byte[] readBinary(String path) throws IOException{ try(InputStream in = new FileInputStream(path)){ byte[] data = new byte[in.available()]; in.read(data); in.close(); return data; } } public static String readText(String path) throws IOException{ try(InputStream in = new FileInputStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream()){ in.transferTo(out); return out.toString(); } } public static void write(String text, String path) throws IOException{ try(OutputStream out = new FileOutputStream(path)){ out.write(text.getBytes()); out.flush(); } } public static void write(byte[] data, String path) throws IOException{ try(OutputStream out = new FileOutputStream(path)){ out.write(data, 0, data.length); out.flush(); } } }
Java → Python
Java → Python の為の暗合作成
String planetext = FileTool.readText("origin/plane.txt"); String keyword = AESCipher.randomKey(7); byte[] iv = AESCipher.randomKey(16).getBytes(); String ivhex = IntStream.range(0, iv.length) .mapToObj(i->String.format("%02x", iv[i])) .collect(Collectors.joining("")); // 鍵とIVの保存 FileTool.write(keyword, "out/aeskey.txt"); FileTool.write(ivhex, "out/ivhex.txt"); // 暗合化 AESCipher cipher = AESCipher.of(keyword, iv); byte[] encbytes = cipher.encrypt(planetext); /* Python に渡す為に、16進数 HEX表現文字列にしてファイル書き出す */ String hexstr = IntStream.range(0, encbytes.length) .mapToObj(i->String.format("%02x", encbytes[i])).collect(Collectors.joining("")); FileTool.write(hexstr, "out/aes_encted.hex");
Python での複合
import codecs from cryptoaes import AESCipher with codecs.open('../../../../out/aeskey.txt', "r", 'utf-8') as f: key = f.read() with codecs.open('../../../../out/ivhex.txt', 'r', 'utf-8') as f: ivhex = f.read() iv = bytes.fromhex(ivhex) with codecs.open('../../../../out/aes_encted.hex', 'r', 'utf-8') as f: enctext = f.read() print(enctext) aes = AESCipher(key, bytes.fromhex(ivhex)) dectxt = aes.decryptCBC(enctext, type='hex') print(dectxt)
Python → Java
Python → Java の為の暗合作成
# -*- coding: UTF-8 -*- import codecs from cryptoaes import AESCipher from base64 import b64encode with codecs.open('../../../../out/aeskey.txt', "r", 'utf-8') as f: key = f.read() with codecs.open('../../../../out/ivhex.txt', 'r', 'utf-8') as f: ivhex = f.read() iv = bytes.fromhex(ivhex) with codecs.open('../../../../origin/plane.txt', 'r', 'utf-8') as f: planetxt = f.read() aes = AESCipher(key, bytes.fromhex(ivhex)) enctxt = b64encode(aes.encryptCBC(planetxt)).decode('utf-8') with codecs.open('../../../../out/aes_encted.txt', 'w', 'utf-8') as f: f.write(enctxt)
Java 複合
String keyword = FileTool.readText("out/aeskey.txt"); String ivhex = FileTool.readText("out/ivhex.txt"); String enctxt = FileTool.readText("out/aes_encted.txt"); byte[] encbytes = Base64.getDecoder().decode(enctxt); // ベクトル16進文字列 → byte[] List<Byte> ivlist = Pattern.compile("[\\w]{1,2}").matcher(ivhex).results(). map(r->(byte)Integer.parseInt(r.group(), 16)).collect(Collectors.toList()); byte[] iv = Bytes.toArray(ivlist); // 複合 AESCipher cipher = AESCipher.of(keyword, iv); String dectxt = new String(cipher.decrypt(encbytes), StandardCharsets.UTF_8); // Base64 チェック→ デコード繰り返す。 while(B64Util.isBase64(dectxt)){ dectxt = new String(Base64.getDecoder().decode(dectxt), StandardCharsets.UTF_8); } System.out.println(dectxt);
decrypt 実行後、B64Util.isBase64() でBase64エンコードされたものかどうかをチェックして
Base64デコードを繰り返すようにすることで、
Javaで暗合化したものも、Python で暗合化したものも、両方このまま
複合する処理として走らせることができます。