/*
* Copyright 2004-2012 the Seasar Foundation and the Others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.seasar.util.crypto;
import java.io.UnsupportedEncodingException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import javax.crypto.Cipher;
import org.seasar.util.crypto.impl.CipherContextImpl;
import org.seasar.util.exception.SIllegalStateException;
import org.seasar.util.misc.Base64Util;
import static org.seasar.util.collection.CollectionsUtil.*;
import static org.seasar.util.misc.AssertionUtil.*;
/**
* {@link Cipher}を扱うユーティリティです。
* <p>
* Cipher のインスタンスをプールします。基本的な使い方は次のようになります。
* </p>
* <p>
* 暗号化や複合化の情報を保持する{@link CipherContext}のインスタンスを定数として定義します。 {@link CipherContext}
* の標準的な実装クラスとして {@link CipherContextImpl}が用意されています (利用可能なアルゴリズムは{@link Cipher}
* のJavadoc等を参照してください)。
* </p>
*
* <pre>
* public static final CipherContext BLOWFISH_CONTEXT =
* new CipherContextImpl("Blowfish", "hogefuga");
* </pre>
* <p>
* {@link Cipher}は初期化にコストがかかるので、{@link CipherPoolUtil}は{@link CipherContext}
* のインスタンスをプールします。 あらかじめインスタンスをプールしておくには{@link #create(CipherContext, int)}
* メソッドを使います。
* </p>
*
* <pre>
* CipherPoolUtil.create(BLOWFISH_CONTEXT, 5);
* </pre>
* <p>
* 文字列を暗号化するには{@link #encryptoText(CipherContext, String, String)}を使います。
* </p>
*
* <pre>
* String encryptoText =
* CipherPoolUtil.encryptoText(BLOWFISH_CONTEXT, "パスワード", "UTF-8");
* </pre>
* <p>
* 文字列を復号化するには{@link #decryptoText(CipherContext, String, String)}を使います。
* </p>
*
* <pre>
* String decryptoText1 =
* CipherPoolUtil
* .decryptoText(BLOWFISH_CONTEXT, encryptoText1, "UTF-8");
* </pre>
*
* @author shinsuke
*/
public abstract class CipherPoolUtil {
/**
* encrypto用のCipherプール
*/
protected static final ConcurrentMap<String, Queue<Cipher>> encryptoQueueMap =
newConcurrentHashMap();
/**
* decrypto用のCipherプール
*/
protected static final ConcurrentMap<String, Queue<Cipher>> decryptoQueueMap =
newConcurrentHashMap();
/**
* {@link CipherContext}の{@link Cipher}インスタンスを事前にプールします。
*
* @param context
* 生成するCipherContext
* @param size
* 生成するCipherインスタンス数
*/
public static void create(final CipherContext context, final int size) {
assertArgumentNotNull("context", context);
final Queue<Cipher> encryptoCipherQueue =
getEncryptoCipherQueue(context);
final Queue<Cipher> decryptoCipherQueue =
getDecryptoCipherQueue(context);
for (int i = 0; i < size; i++) {
encryptoCipherQueue.add(context.getCipher(Cipher.ENCRYPT_MODE));
decryptoCipherQueue.add(context.getCipher(Cipher.DECRYPT_MODE));
}
}
/**
* {@link CipherContext}の{@link Cipher}でバイト配列を暗号化します。
*
* @param context
* 利用するCipherContext
* @param data
* 暗号化するバイト配列
* @return 暗号化されたバイト配列
*/
public static byte[] encrypto(final CipherContext context, final byte[] data) {
assertArgumentNotNull("context", context);
assertArgumentNotNull("data", data);
final Cipher cipher = getEncryptoCipher(context);
try {
final byte[] encrypted = cipher.doFinal(data);
putEncryptoCipher(context, cipher);
return encrypted;
} catch (final Exception e) {
throw new SIllegalStateException(e);
}
}
/**
* {@link CipherContext}の{@link Cipher}で文字列を暗号化します。
*
* @param context
* 利用するCipherContext
* @param text
* 暗号化する文字列
* @param charsetName
* エンコーディング名
* @return 暗号化された文字列
*/
public static String encryptoText(final CipherContext context,
final String text, final String charsetName) {
assertArgumentNotNull("context", context);
assertArgumentNotNull("text", text);
assertArgumentNotEmpty("charsetName", charsetName);
try {
return Base64Util.encode(encrypto(
context,
text.getBytes(charsetName)));
} catch (final Exception e) {
throw new SIllegalStateException(e);
}
}
/**
* {@link CipherContext}の{@link Cipher}でバイト配列を復号化します。
*
* @param context
* 利用するCipherContext
* @param data
* 復号化するバイト配列
* @return 復号化されたバイト配列
*/
public static byte[] decrypto(final CipherContext context, final byte[] data) {
assertArgumentNotNull("context", context);
assertArgumentNotNull("data", data);
final Cipher cipher = getDecryptoCipher(context);
try {
final byte[] decrypted = cipher.doFinal(data);
putDecryptoCipher(context, cipher);
return decrypted;
} catch (final Exception e) {
throw new SIllegalStateException(e);
}
}
/**
* {@link CipherContext}の{@link Cipher}で文字列を暗号化します。
*
* @param context
* 利用するCipherContext
* @param text
* 復号化する文字列
* @param charsetName
* エンコーディング名
* @return 復号化された文字列
*/
public static String decryptoText(final CipherContext context,
final String text, final String charsetName) {
assertArgumentNotNull("context", context);
assertArgumentNotNull("text", text);
assertArgumentNotEmpty("charsetName", charsetName);
try {
return new String(
decrypto(context, Base64Util.decode(text)),
charsetName);
} catch (final UnsupportedEncodingException e) {
throw new SIllegalStateException(e);
}
}
private static Cipher getEncryptoCipher(final CipherContext context) {
final Cipher cipher = getEncryptoCipherQueue(context).poll();
if (cipher == null) {
return context.getCipher(Cipher.ENCRYPT_MODE);
}
return cipher;
}
private static void putEncryptoCipher(final CipherContext context,
final Cipher cipher) {
getEncryptoCipherQueue(context).offer(cipher);
}
private static Queue<Cipher> getEncryptoCipherQueue(
final CipherContext context) {
final Queue<Cipher> queue = encryptoQueueMap.get(context.getId());
if (queue != null) {
return queue;
}
return putIfAbsent(
encryptoQueueMap,
context.getId(),
new ConcurrentLinkedQueue<Cipher>());
}
private static Cipher getDecryptoCipher(final CipherContext context) {
final Cipher cipher = getDecryptoCipherQueue(context).poll();
if (cipher == null) {
return context.getCipher(Cipher.DECRYPT_MODE);
}
return cipher;
}
private static void putDecryptoCipher(final CipherContext context,
final Cipher cipher) {
getDecryptoCipherQueue(context).offer(cipher);
}
private static Queue<Cipher> getDecryptoCipherQueue(
final CipherContext context) {
final Queue<Cipher> queue = decryptoQueueMap.get(context.getId());
if (queue != null) {
return queue;
}
return putIfAbsent(
decryptoQueueMap,
context.getId(),
new ConcurrentLinkedQueue<Cipher>());
}
}