AES暗号化
文字列の暗号化、画像の暗号化を行うサンプルプログラムを作成してみました。
処理はユーティリティクラスに記載し、テストプログラムで確認を行う構成にしています。
ユーティリティクラス
KeyStoreUtil.java
キーストアに関する処理を記述したクラスです。
package util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.KeyGenerator;
public class KeyStoreUtil {
/** インスタンス */
private static KeyStoreUtil instance;
/**********************************************************
* コンストラクタ
* 概要 :privateとして、外部からインスタンスを生成させない
**********************************************************/
private KeyStoreUtil() {
}
/**********************************************************
* インスタンス取得
* 概要 :未初期化ならnewし、初期化済ならstaticフィールドの値
* マルチスレッドを考慮し、synchronizedとする
**********************************************************/
public synchronized static KeyStoreUtil getInstance() {
if (instance == null) {
instance = new KeyStoreUtil();
}
return instance;
}
/**********************************************************
* キーストアファイル初期化
* 概要 :キーストアファイルの生成を行う
* 引数1:キーストアファイルのファイルパス
* 引数2:キーストアファイルのパスワード
**********************************************************/
public synchronized void initialize(String filePath, char[] ksPass) throws IOException {
FileInputStream inputStream = null;
/** 暗号化キーを保存するキーストアファイル */
File keyFile = new File(filePath);
if (keyFile.exists()) {
inputStream = new FileInputStream(keyFile);
}
KeyStore jceks = null;
// キーストアインスタンス取得
try {
jceks = KeyStore.getInstance("JCEKS");
jceks.load(inputStream, ksPass);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
// keyStoreファイル(出力用)
FileOutputStream outputStream = new FileOutputStream(keyFile);
try {
// keyStoreファイルを保存する
jceks.store(outputStream, ksPass);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
}
}
/**********************************************************
* 鍵の保存
* 概要 :キーストアファイルにエイリアス名に応じた鍵(自動生成)を保存する
* 引数1:キーストアファイルのファイルパス
* 引数2:キーストアファイルのパスワード
* 引数3:エイリアス名
* 引数4:エイリアスのパスワード
**********************************************************/
public synchronized void saveKey(String filePath, char[] ksPass, String aliasName, char[] aliasPass)
throws IOException {
// 入力ストリーム
FileInputStream inputStream = null;
// 暗号化キーを保存するキーストアファイル
File keyFile = new File(filePath);
if (keyFile.exists()) {
inputStream = new FileInputStream(keyFile);
}
// キーストア
KeyStore jceks = null;
try {
jceks = KeyStore.getInstance("JCEKS");
jceks.load(inputStream, ksPass);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
try {
// キーストアに指定されたエイリアス名のエントリがあるか確認する
if (!jceks.isKeyEntry(aliasName)) {
// 鍵ジェネレータによりAESアルゴリズムで鍵を自動生成する
KeyGenerator aes = KeyGenerator.getInstance("AES");
// 生成した鍵をkeyStoreにセットする
jceks.setKeyEntry(aliasName, aes.generateKey(), aliasPass, null);
}
} catch (KeyStoreException e1) {
e1.printStackTrace();
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
// keyStoreファイル(出力用)
FileOutputStream outputStream = new FileOutputStream(keyFile);
try {
// keyStoreファイルを保存する
jceks.store(outputStream, ksPass);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
}
}
/**********************************************************
* 鍵の取得
* 概要 :キーストアファイルに保存された鍵を取得する
* 引数1:キーストアファイルのファイルパス
* 引数2:キーストアファイルのパスワード
* 引数3:エイリアス名
* 引数4:エイリアスのパスワード
**********************************************************/
public synchronized Key loadKey(String filePath, char[] ksPass, String aliasName, char[] aliasPass)
throws IOException {
// 入力ストリーム
FileInputStream inputStream = null;
// 鍵(暗号化キー)が保存されたキーストアファイル
File keyFile = new File(filePath);
if (keyFile.exists()) {
try {
inputStream = new FileInputStream(keyFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
KeyStore jceks = null;
try {
jceks = KeyStore.getInstance("JCEKS");
jceks.load(inputStream, ksPass);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
Key aliasKey = null;
try {
aliasKey = jceks.getKey(aliasName, aliasPass);
} catch (UnrecoverableKeyException e2) {
e2.printStackTrace();
} catch (KeyStoreException e2) {
e2.printStackTrace();
} catch (NoSuchAlgorithmException e2) {
e2.printStackTrace();
}
return aliasKey;
}
}
EncryptUtil.java
暗号化、復号を行うクラスです。
ファイル名、エイリアス名、パスワードなどは分かりやすいように固定値にしています。
package util;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
public class EncryptUtil {
/** キーストアファイル */
private static final String KEYSTORE_FILE_PATH = "mykeystore.jks";
/** キーストアパスワード */
private static final char[] KEYSTORE_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 's' };
/** エイリアス名 */
private static final String ALIAS_NAME = "aesKey";
/** エイリアスパスワード */
private static final char[] ALIAS_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 'a' };
/** インスタンス */
private static EncryptUtil instance;
/**********************************************************
* コンストラクタ
* 概要 :privateとして、外部からインスタンスを生成させない
**********************************************************/
private EncryptUtil() {
}
/**********************************************************
* インスタンス取得
* 概要 :未初期化ならnewし、初期化済ならstaticフィールドの値
* マルチスレッドを考慮し、synchronizedとする
**********************************************************/
public synchronized static EncryptUtil getInstance() {
if (instance == null) {
instance = new EncryptUtil();
}
return instance;
}
/**********************************************************
* 文字列暗号化
* 概要 :指定された文字列を暗号化する
* 引数1:暗号化対象文字列
* 引数2:初期化ベクトル
* 引数3:キャラセット
* 戻り値:暗号化済文字列
**********************************************************/
public synchronized String encrypt(String text, String iVec, String charset) {
try {
Key key = loadKey(ALIAS_NAME);
IvParameterSpec iv = new IvParameterSpec(iVec.getBytes(charset));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encryptBytes = cipher.doFinal(text.getBytes(charset));
return Base64.getEncoder().encodeToString(encryptBytes);
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("failed to encrypt.", e);
}
}
/**********************************************************
* 文字列復号
* 概要 :暗号化された文字列を復号する
* 引数1:暗号化済文字列
* 引数2:初期化ベクトル
* 引数3:キャラセット
* 戻り値:復号済文字列
**********************************************************/
public synchronized String decrypt(String encryptedString, String iVec, String charset) {
try {
Key key = loadKey(ALIAS_NAME);
IvParameterSpec iv = new IvParameterSpec(iVec.getBytes(charset));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] originalBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedString));
return new String(originalBytes);
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("failed to decrypt.", e);
}
}
/**********************************************************
* ファイル暗号化
* 概要 :指定されたファイルを暗号化する
* 初期化ベクトル(IV)は自動生成しファイルの先頭に付ける
* 引数1:暗号化対象ファイルパス(入力元)
* 引数2:暗号化済ファイルパス(出力先)
**********************************************************/
public synchronized void encryptFile(String srcPath, String destPath) throws IOException {
// 1.暗号化するファイルのFileInputStreamを生成
FileInputStream fis = null;
try {
fis = new FileInputStream(srcPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 2.暗号化に用いるKeyを取得
Key key = loadKey(ALIAS_NAME);
Cipher cipher = null;
try {
// 3.Chiperを作成&初期化
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
FileOutputStream fos = null;
try {
// 4.出力先のFileOutputStreamを作成
fos = new FileOutputStream(destPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 5.FileOutputStreamをCipherOutputStreamで包む
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// 6.FileOutputStreamにIVを書き込む(ファイルの先頭にIVをくっ付ける手法)
fos.write(cipher.getIV());
byte[] a = new byte[8];
int i = fis.read(a);
while (i != -1) {
// 7.CipherOutputStreamに暗号化するデータを書き込む
cos.write(a, 0, i);
i = fis.read(a);
}
cos.flush();
cos.close();
fos.close();
fis.close();
}
/**********************************************************
* ファイル復号
* 概要 :暗号化されたファイルを復号する
* 初期化ベクトル(IV)はファイルの先頭(16バイト)に付与されている前提
* 引数1:暗号化済ファイルパス(入力元)
* 引数2:復号済ファイルパス(出力先)
**********************************************************/
public synchronized void decryptFile(String srcPath, String destPath) throws IOException {
// 1.Keyの取得
Key key = loadKey(ALIAS_NAME);
FileInputStream fis = null;
try {
// 2.暗号化したファイルのFileInputStreamの生成
fis = new FileInputStream(srcPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 3.IV用のbyte配列を作成
byte[] iv = new byte[16];
// 4.FileInputStreamからIV部分を3の配列に読み込み
// ファイルの先頭16バイトはIVとして使用している
fis.read(iv);
Cipher cipher = null;
try {
// 5.Chiperを生成
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
// 6.IVからIvParameterSpecを生成
IvParameterSpec ivspec = new IvParameterSpec(iv);
try {
// 7.IvParameterSpecを使ってChiperを初期化
cipher.init(Cipher.DECRYPT_MODE, key, ivspec);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
// 8.CipherInputStreamでFileInputStreamを包む
CipherInputStream cis = new CipherInputStream(fis, cipher);
FileOutputStream fos = null;
try {
// 9.出力先のFileOutputStreamを作成
fos = new FileOutputStream(destPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 10.FileOutputStreamに書き込む
byte buf[] = new byte[256];
int len;
while ((len = cis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
cis.close();
}
/**********************************************************
* 鍵(暗号化キー)取得
* 概要 :鍵(暗号化キー)を取得する
* 引数1:エイリアス名
* 戻り値:鍵(暗号化キー)
**********************************************************/
private Key loadKey(String aliasName) {
Key aliasKey = null;
try {
aliasKey = KeyStoreUtil.getInstance().loadKey(KEYSTORE_FILE_PATH, KEYSTORE_PASSWORD, aliasName,
ALIAS_PASSWORD);
} catch (IOException e) {
e.printStackTrace();
}
return aliasKey;
}
}
処理確認テストクラス
KeyStoreFileCreate.java
キーストアファイルを作成するだけのテストクラスです。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.KeyStoreUtil;
public class KeyStoreFileCreate {
/** キーストアファイルのファイルパス */
private static final String KEYSTORE_FILE_PATH = "mykeystore.jks";
/** キーストアファイルのパスワード */
private static final char[] KEYSTORE_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 's' };
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
KeyStoreUtil.getInstance().initialize(KEYSTORE_FILE_PATH, KEYSTORE_PASSWORD);
}
}
KeyStoreSaveKey.java
キーストアファイルを作成し、自動生成された鍵を キーストアファイル に格納する処理のテストクラスです。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.KeyStoreUtil;
public class KeyStoreSaveKey {
/** キーストアファイルのファイルパス */
private static final String KEYSTORE_FILE_PATH = "mykeystore.jks";
/** キーストアファイルのパスワード */
private static final char[] KEYSTORE_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 's' };
/** エイリアス名 (キー名)*/
private static final String ALIAS_NAME = "aesKey";
/** エイリアスパスワード */
private static final char[] ALIAS_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 'a' };
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
KeyStoreUtil.getInstance().saveKey(KEYSTORE_FILE_PATH, KEYSTORE_PASSWORD, ALIAS_NAME, ALIAS_PASSWORD);
}
}
KeyStoreLoadKey.java
キーストアに格納された鍵を取り出す処理の確認を行うテストクラスです。
package enc;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.KeyStoreUtil;
public class KeyStoreLoadKey {
/** キーストアファイルのファイルパス */
private static final String KEYSTORE_FILE_PATH = "mykeystore.jks";
/** キーストアファイルのパスワード */
private static final char[] KEYSTORE_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 's' };
/** エイリアス名 */
private static final String ALIAS_NAME = "aesKey";
/** エイリアスパスワード */
private static final char[] ALIAS_PASSWORD = { 'p', 'a', 's', 's', 'w', 'o',
'r', 'd', 'a' };
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
Key aliasKey = KeyStoreUtil.getInstance().loadKey(KEYSTORE_FILE_PATH, KEYSTORE_PASSWORD, ALIAS_NAME,
ALIAS_PASSWORD);
System.out.println(aliasKey.getEncoded());
}
}
KeyStoreEncrypt.java
指定された文字列が暗号化された文字列になることを確認するテストクラスです。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.EncryptUtil;
public class KeyStoreEncrypt {
/** 初期化ベクトル(IV) 16byte */
private static final String IV = "1234567812345678";
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
String encTxt = "abcdefg";
System.out.println("Before=" + encTxt);
// 文字コードはDBに合わせる想定
String encrypted = EncryptUtil.getInstance().encrypt(encTxt, IV, "Shift_JIS");
System.out.println("After=" + encrypted);
}
}
KeyStoreDecrypt.java
暗号化された文字列を複合化できるか確認するテストクラスです。
暗号化の鍵は自動生成するため、暗号化済みの文字列は鍵によって変わります。暗号化処理を行った後、暗号化済み文字列を確認し、その文字列を入力値としてテストします。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.EncryptUtil;
public class KeyStoreDecrypt {
/** 初期化ベクトル(IV) 16byte */
private static final String IV = "1234567812345678";
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
// 暗号化済みの文字列
String encryptedBase64 = "hTg9HUCgWnB4wiubi3UV2g==";
System.out.println("Before=" + encryptedBase64);
// 文字コードはDBに合わせる想定
String originalStr = EncryptUtil.getInstance().decrypt(encryptedBase64, IV, "Shift_JIS");
System.out.println("After=" + originalStr);
}
}
KeyStoreEncryptFile.java
画像ファイルが暗号化されるか確認を行うテストクラスです。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.EncryptUtil;
public class KeyStoreEncryptFile {
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
String targetFilePath = "C:\\temp\\test.jpg";
String encryptedFilePath = "C:\\temp\\testEncrypted.jpg";
System.out.println("Before=" + targetFilePath);
EncryptUtil.getInstance().encryptFile(targetFilePath, encryptedFilePath);
System.out.println("After=" + encryptedFilePath);
}
}
KeyStoreDecryptFile.java
画像ファイルを複合化できるか確認を行うテストクラスです。
package enc;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import util.EncryptUtil;
public class KeyStoreDecryptFile {
public static void main(String args[])
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
String encryptedFilePath = "C:\\temp\\testEncrypted.jpg";
String decryptedFilePath = "C:\\temp\\testDecrypted.jpg";
System.out.println("Before=" + encryptedFilePath);
EncryptUtil.getInstance().decryptFile(encryptedFilePath, decryptedFilePath);
System.out.println("After=" + decryptedFilePath);
}
}