Javaアプリで設定情報を保存する方法 (ファイル暗号化編)

Javaアプリで設定情報をXML形式で保存する、という記事を書きました。

参考: Javaアプリで設定情報を保存する方法 (XML編)

ファイルがXML形式で保存され、見やすいのはいいのですが、例として挙げたブログ情報の中にはユーザ名やパスワードが含まれており、それがプレーンテキストで保存されている状態はセキュリティ的にどうかと思います。

<object class="jp.beourselves.open.sample.beanxml.BlogInfo" width="300" height="150">
 <void property="blogId">
  <int>1</int>
 </void>
 <void property="endPointUrl">
  <string>http://www.hogehoge.com/wordpress</string>
 </void>
 <void property="password">
  <string>hogeraccho</string>
 </void>
 <void property="username">
  <string>nishimura</string>
 </void>
</object>

この手の設定情報を保存するなら、暗号化して保存した方がいいですね。

ブログ情報を暗号化して保存し、復号して読み込む

今回も、前回同様にブログ情報を保存するクラスを使いましょう。

BlogInfoクラス

package jp.beourselves.open.sample.beanio;

public class BlogInfo {
	/**
	 * エンドポイントURL
	 */
	private String endPointUrl;

	/**
	 * ユーザ名
	 */
	private String username;

	/**
	 * パスワード
	 */
	private String password;

	/**
	 * ブログID
	 */
	private int blogId;

	/**
	 * デフォルトコンストラクタ
	 */
	public BlogInfo(){}
	
	@Override
	public String toString() {
		return endPointUrl + "," + username + "," + password + "," + String.valueOf(blogId);
	}

	public String getEndPointUrl() {
		return endPointUrl;
	}

	public void setEndPointUrl(String endPointUrl) {
		this.endPointUrl = endPointUrl;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getBlogId() {
		return blogId;
	}

	public void setBlogId(int blogId) {
		this.blogId = blogId;
	}
}

このBlogInfoクラスを暗号化して保存し、暗号化して保存されたファイルを複合して読み込むクラスは次のようになります。

BeanFileCryptoIOUtilクラス

package jp.beourselves.open.sample.beanio;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class BeanFileCryptoIOUtil {
	private final static String CRYPTO_ALGORYTHM = "Blowfish";
	private final static String KEY = "nurukunai";

	/**
	 * 暗号化されたBeanファイルを復号して読み込む ファイルが存在しない場合は null が戻る
	 * @param fileName Beanファイル
	 * @return Bean (ファイルが存在しない場合 null)
	 */
	public static Object readCryptoBean(String fileName) {
		Object bean = null;
		try {
			// 暗号鍵を生成
			SecretKeySpec sksSpec = new SecretKeySpec(KEY.getBytes(), CRYPTO_ALGORYTHM);

			// 暗号アルゴリズムを設定
			Cipher cipher = Cipher.getInstance(CRYPTO_ALGORYTHM);
			
			// 復号モードに初期化
			cipher.init(Cipher.DECRYPT_MODE, sksSpec);

			XMLDecoder decoder = new XMLDecoder(
					new CipherInputStream(
							new FileInputStream(fileName), cipher));
			bean = decoder.readObject();
			decoder.close();
		} catch (FileNotFoundException | NoSuchAlgorithmException
				| NoSuchPaddingException | InvalidKeyException e) {
			e.printStackTrace();
		}

		return bean;
	}

	/**
	 * Beanファイルを暗号化して書き込む
	 * @param fileName Beanファイル
	 * @param bean 書き込むBean
	 */
	public static void writeCryptoBean(String fileName, Object bean) {
		try {
			File file = new File(fileName);

			// 指定のフォルダがない場合は作る (親フォルダがルートの場合は無視する)
			File parentDir = file.getParentFile();
			if (parentDir != null && !parentDir.exists())
				parentDir.mkdirs();

			// 暗号鍵を作成
			SecretKeySpec sksSpec = new SecretKeySpec(KEY.getBytes(), CRYPTO_ALGORYTHM);

			// 暗号アルゴリズムを設定
			Cipher cipher = Cipher.getInstance(CRYPTO_ALGORYTHM);

			// 暗号化モードに初期化
			cipher.init(Cipher.ENCRYPT_MODE, sksSpec);

			XMLEncoder encoder = new XMLEncoder(
					new CipherOutputStream(
							new FileOutputStream(fileName), cipher));
			encoder.writeObject(bean);
			encoder.close();
		} catch (FileNotFoundException | NoSuchAlgorithmException
				| NoSuchPaddingException | InvalidKeyException e) {
			e.printStackTrace();
		}
	}
}

前回のコードと見比べてみるとわかりますが、暗号化するための仕掛けを施しているところが違います。
といっても、実質保存で4行、読み込みで4行違うだけですね。

暗号化して保存するためのCipherクラス、CipherOutputStreamクラス

66〜73行目は、暗号化するためにCipherクラスを用意しています。

			// 暗号鍵を作成
			SecretKeySpec sksSpec = new SecretKeySpec(KEY.getBytes(), CRYPTO_ALGORYTHM);

			// 暗号アルゴリズムを設定
			Cipher cipher = Cipher.getInstance(CRYPTO_ALGORYTHM);

			// 暗号化モードに初期化
			cipher.init(Cipher.ENCRYPT_MODE, sksSpec);

書き込む際にはCipherOutputStreamクラスを使って書き込んでいます (75〜77行目)。

			XMLEncoder encoder = new XMLEncoder(
					new CipherOutputStream(
							new FileOutputStream(fileName), cipher));
			encoder.writeObject(bean);
			encoder.close();

FileOutputStreamをCiperOutputStreamでくくるだけ、という感じですね。
これだけで暗号化したファイルが作れちゃいます。

ちなみに、「Blowfish」という暗号化アルゴリズムで暗号化しています。
このアルゴリズムはSSHやファイル暗号化ソフトウェアなどに広く利用されているとのこと (Wikipediaより)。

暗号化する際に必要となるキーは”nurukunai”としています。

暗号化されたファイルを読み込み復号するためのCipherクラス、CipherInputStreamクラス

30〜37行目では復号するためにCipherクラスを用意しています。
特に37行目のDECRYPT_MODEに注意してください。
(保存するときはENCRYPT_MODEでしたね)

			// 暗号鍵を生成
			SecretKeySpec sksSpec = new SecretKeySpec(KEY.getBytes(), CRYPTO_ALGORYTHM);

			// 暗号アルゴリズムを設定
			Cipher cipher = Cipher.getInstance(CRYPTO_ALGORYTHM);
			
			// 復号モードに初期化
			cipher.init(Cipher.DECRYPT_MODE, sksSpec);

読み込む際にはCipherInputStreamクラスを使っています (39〜41行目)。

			XMLDecoder decoder = new XMLDecoder(
					new CipherInputStream(
							new FileInputStream(fileName), cipher));
			bean = decoder.readObject();
			decoder.close();

こちらはFileInputStreamをCipherInputStreamでくくるだけ、といった感じですね。

実際にBeanFileCryptoIOUtilクラスを使ってみよう

それでは、実際に先のBeanFileIOUtilを使ってみましょう。

package jp.beourselves.open.sample.beanio;

public class BeanFileCryptoIOUtilSample {
	public static void main(String[] args) {
		BeanFileCryptoIOUtilSample app = new BeanFileCryptoIOUtilSample();

		// ファイルに保存する
		app.write();
		
		// ファイルから読み込む
		app.read();
	}
	
	/**
	 * ファイルに保存する
	 */
	public void write(){
		// 保存するデータを生成する
		BlogInfo blogInfo = new BlogInfo();
		blogInfo.setEndPointUrl("http://www.hogehoge.com/wordpress");
		blogInfo.setUsername("nishimura");
		blogInfo.setPassword("hogeraccho");
		blogInfo.setBlogId(1);
				
		// ファイルにデータを保存する
		BeanFileCryptoIOUtil.writeCryptoBean("data/bloginfo.dat", blogInfo);
	}
	
	/**
	 * ファイルから読み込む
	 */
	public void read(){
		// ファイルからデータを読み込む
		BlogInfo blogInfo = (BlogInfo) BeanFileCryptoIOUtil.readCryptoBean("data/bloginfo.dat");
		System.out.println(blogInfo.toString());
	}
}

このプログラムで保存したファイルはこんな感じになります。

ss_201312040201.png

この記事の冒頭でお見せしたのと同じ内容のはずですが、普通の人間が見ても何のことやらサッパリわからない感じですね。
ちゃんと暗号化できています。

これが読み込みできていますので、復号もできています。

ということで、手軽に情報保存したいんだけど暗号化しておきたい、という場合は、今回紹介したようにCipherクラスとCipherOutputStreamクラス、CipherInputStreamクラスを使ってファイルの読み書きをしましょう。

なお、Cipherを準備するあたりで「Blowfish」以外の暗号化アルゴリズムを指定することもできます。
必要に応じてアレンジしてみてください。
(個人的に利用する分には、「Blowfish」という暗号化アルゴリズムで全く問題ないと思います)

無料メールセミナー