2013-11-06

2013-11-06 22:27

Cookieとは?

Cookieはクライアントのブラウザのメモリ(ブラウザを終了時にはHDDへ)にサーバとの通信における持続的な情報を保持しています。
主にショッピングサイトにおけるカートやログイン状態、告配信業者などがクライアントの詳細なアクセス履歴を取得する用途で使われます。

Cookie情報を暗号化して、セキュリティ向上へ…

Cookieは様々な分野で便利に使える反面、簡単に設定した値の確認・改ざんが出来るため、何らかのセキュリティの対応をせず、値をそのままCookieに設定すると権限のないページへの接続不正ログインxsssql injectionなどの攻撃の対象になります。

今回は、Cookieを簡単に改ざん出来ないように暗号化するソースを整理してみました。

・Cookieに値を設定する流れ
  1. CookieDataクラスに設定したい値をセットする(MapもしくはBean)
    Class:CookieData.java
  2. CookieDataクラスをシリアライズ(serialize) ⇒ 暗号化 ⇒ HEX に変換する
    Class:CookieSecurity.java
  3. HEXデータをミックスし、Cookieにセットする
    Class:CookieUtil.java
・Cookieから値を取得する流れ
  1. Cookieから値を取得する
  2. 取得した値をアンミックス(ミックスされてない状態に)する
  3. HEX ⇒ 復号化 ⇒ デシリアライズ(deserialize)しObjectに変換する


・[CookieSecurity.java]:クラスをシリアライズ、デシリアライズ、暗号化、復号化するクラスです。
>>CookieSecurity.javaダウンロード
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 * 

 * Filename : CookieSecurity.java
 * Class    : xxx.xxx.CookieSecurity
 * Function : 
 * Comment  : Cookieを暗号化、復号化などに使われる
 * History  : 2013/11/01, su min park, develop
 *       
 * 
* @version 1.0 * @author su min park * @since JDK 1.7 */ public class CookieSecurity { private static CookieSecurity instance = new CookieSecurity(); private final String CIPHER_ALGORITHM = "AES"; private final String CIPHER_TRANSFORMATION = CIPHER_ALGORITHM + "/ECB/PKCS5Padding"; private Cipher _encrypt; private Cipher _decrypter; private CookieSecurity() { String password = "12345678901$3456"; // パスワードは16文字で //パスワード生成 Key key = createKey(password); try { //暗号化モジュール初期化 initializeCipher(key); } catch (InvalidKeyException e) { System.out.println("xxx.xxx.CookieSecurity_constructor : InvalidKeyException " + e.getMessage()); } catch (NoSuchAlgorithmException e) { System.out.println("xxx.xxx.CookieSecurity_constructor : NoSuchAlgorithmException " + e.getMessage()); } catch (NoSuchPaddingException e) { System.out.println("xxx.xxx.CookieSecurity_constructor : NoSuchPaddingException " + e.getMessage()); } finally { password = null; } } /** * シングルトン・パターン(Singleton Pattern) * * @return CookieSecurity instance */ public static CookieSecurity getInstance() { return instance; } /** * HEXデータを復号化し、そのデータを元にオブジェクトを生成する
* Cookieに設定されていた値をこのメソッドを利用してClassに変換する。 * @param hexData String 復号化するHexデータ * @return Object 成功した場合はObject、失敗した場合はnull */ public Object getHexToObj(String hexData) { if (hexData == null) { return null; } try { //復号化 byte[] decrypted = decrypt(hexToByteArray(hexData)); //シリアライズ return objFromString(new String(decrypted)); } catch (ClassNotFoundException e) { System.out.println("xxx.xxx.CookieSecurity_getCookieData : ClassNotFoundException " + e.getMessage()); } catch (IOException e) { System.out.println("xxx.xxx.CookieSecurity_getCookieData : IOException " + e.getMessage()); } catch (Exception e) { System.out.println("xxx.xxx.CookieSecurity_getCookieData : Exception " + e.getMessage()); } return null; } /** * 暗号化するためのメッソド
* CookieDataクラスをシリアライズし、暗号化する * @param cookieData CookieData データがセットされたcookieDataクラス * @return String 成功した場合はString、失敗した場合はnull */ public String getCookieDataToHex(CookieData cookieData) { if (cookieData == null) { return null; } String hexData = null; try { //シリアライズ String seriStr = objToString(cookieData); //暗号化 byte[] encrypted = encrypt( seriStr.getBytes() ); hexData = byteArrayToHex(encrypted); } catch (IOException e) { System.out.println("xxx.xxx.CookieSecurity_setCookieData : IOException " + e.getMessage()); } return hexData; } /** * byte[] to hex : unsigned byte(バイト) 配列を16HEXの文字列に変更 * * @param ba byte[] * @return */ public String byteArrayToHex(byte[] ba) { if (ba == null || ba.length == 0) { return null; } StringBuffer sb = new StringBuffer(ba.length * 2); String hexNumber; for (int x = 0; x < ba.length; x++) { hexNumber = "0" + Integer.toHexString(0xff & ba[x]); sb.append(hexNumber.substring(hexNumber.length() - 2)); } return sb.toString(); } /** * hex to byte[] : 16HEX 文字列をバイトに変換する * * @param hex hex string * @return */ public byte[] hexToByteArray(String hex) throws Exception { if (hex == null || hex.length() == 0) { return null; } byte[] ba = new byte[hex.length() / 2]; for (int i = 0; i < ba.length; i++) { ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); } return ba; } /** * bit数のパスワードを生成する。 * @param password String パスワード * @param bitNum int Bit数 * @return Key 生成されたkey */ public Key createKeyRandom(String password, int bitNum) { SecureRandom random = new SecureRandom(password.getBytes() ); byte buff[] = new byte[bitNum >> 3]; random.nextBytes(buff); return new SecretKeySpec(buff, CIPHER_ALGORITHM); } /** * SecretKeySpecを利用してkeyを生成する
* パスワードは16文字 * @param password String パスワード * @return Key 生成されたkey */ public Key createKey(String password) { return new SecretKeySpec(password.getBytes(), CIPHER_ALGORITHM); } /** * 暗号化・復号化に必要なクラスを初期化する * パスワードは16文字 * @param key Key パスワード */ public void initializeCipher(Key key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { _encrypt = Cipher.getInstance(CIPHER_TRANSFORMATION); _encrypt.init(Cipher.ENCRYPT_MODE, key); _decrypter = Cipher.getInstance(CIPHER_TRANSFORMATION); _decrypter.init(Cipher.DECRYPT_MODE, key); } /** * 暗号化する。 * @param encrypt_byte byte[] バイトデータ * @return byte[] 暗号化された結果 */ public byte[] encrypt(byte[] encrypt_byte) { try { return _encrypt.doFinal(encrypt_byte); } catch (Exception exc) { exc.printStackTrace(); return null; } } /** * 復号化する。 * @param decrypt_byte byte[] バイトデータ * @return byte[] 復号化された結果 */ public byte[] decrypt(byte[] decrypt_byte) { try { return _decrypter.doFinal(decrypt_byte); } catch (Exception exc) { exc.printStackTrace(); return null; } } /** * Base64文字列からオブジェクト(class)を読み込む * @param base64 String オブジェクトに変換する文字列(Base64文字列) * @return Object 変換されたオブジェクト * @throws IOException シリアライズ途中エラーが発生した場合 * @throws ClassNotFoundException readObject途中エラーが発生した場合 */ private Object objFromString( String base64 ) throws IOException , ClassNotFoundException { /** commons-codecライブラリーを使わない場合 */ //byte [] data = Base64Coder.decode( s ); byte [] data = Base64.decodeBase64(base64); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); Object o = ois.readObject(); ois.close(); return o; } /** * オブジェクト(class)を文字列に変換する。
* 文字列に変換されるclassは Serializableを継承している必要がある。 (implements Serializable) * @param obj Serializable オブジェクトシリアライズ(Object Serialization)するオブジェクト * @return String シリアライズされた文字列 * @throws IOException シリアライズ途中エラーが発生した場合 */ private String objToString( Serializable obj ) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream( baos ); oos.writeObject(obj); } finally { if (oos != null ) oos.close(); } return new String( Base64.encodeBase64(baos.toByteArray(), false) ); /* commons-codecライブラリーを使わない場合 */ //return new String( Base64Coder.encode( baos.toByteArray() ) ); } }

TOPへ

・[CookieUtil.java]:実際のCookieに値をSetしたりGetしたりするためのクラスです。
>>CookieUtil.javaダウンロード
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 

 * Filename : CookieUtil.java
 * Class    : xxx.xxx.CookieUtil
 * Function : 
 * Comment  : CookieにSet,Getするためのクラス
 * History  : 2013/11/01, su min park, develop
 *       
 * 
* @version 1.0 * @author su min park * @since JDK 1.7 */ public class CookieUtil { private CookieSecurity cookieSecurity = CookieSecurity.getInstance(); private static CookieUtil instance = new CookieUtil();; private final String SEC_COOKIE_NM1 = "SEC_CN1"; private final String SEC_COOKIE_NM2 = "SEC_CN2"; private final String CK_CHECK = "CK_CHECK"; private final int MIX_SIZE1 = 15; /** * シングルトン・パターン(Singleton Pattern) * * @return CookieUtil instance */ public static CookieUtil getInstance() { return instance; } /** * cookieにセットする(オーバーロード) * @param response HttpServletResponse レスポンス * @param ckNm String Cookie名称 * @param ckVal String Cookie名称の値 * @throws Exception */ public void setCookie(HttpServletResponse response, String ckNm, String ckVal) throws Exception { setCookie(response, ckNm, ckVal, null, -1, false); } public void setCookie(HttpServletResponse response, String ckNm, String ckVal, int maxAge) throws Exception { setCookie(response, ckNm, ckVal, null, maxAge, false); } public void setCookie(HttpServletResponse response, String ckNm, String ckVal, int maxAge, boolean sec) throws Exception { setCookie(response, ckNm, ckVal, null, maxAge, sec); } public void setCookie(HttpServletResponse response, String ckNm, String ckVal, String domain) throws Exception { setCookie(response, ckNm, ckVal, domain, -1, false); } /** * cookieにセットする * @param response HttpServletResponse レスポンス * @param ckNm String Cookie名称 * @param ckVal String Cookie名称の値 * @param domain String Cookieを設定するドメイン * @param maxAge int Cookieの維持時間 Default = -1(ブラウザを閉じたとき) * @param sec boolean セキュア(SSL)仕様有無 Default = false * @throws Exception */ public void setCookie(HttpServletResponse response, String ckNm, String ckVal, String domain, int maxAge, boolean sec) throws Exception { Cookie cookie = new Cookie(ckNm, ckVal); if (domain != null && domain.length() > 0) {cookie.setDomain(domain);} cookie.setPath("/"); cookie.setSecure(sec); cookie.setMaxAge(maxAge); response.addCookie(cookie); } /** * CookieDataタイプの情報を暗号化し、Cookieにセットする。 * @param response HttpServletResponse レスポンス * @param cookieData CookieData Cookieに設定する値 * @throws Exception setSecCookie hexData is null */ public void setSecCookie(HttpServletResponse response, CookieData cookieData) throws Exception { if (response == null || cookieData == null) { return; } //現在の日時(セキュリティチェックのため) java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:SS:ss"); String secUpdDt = format.format(new java.util.Date()); //cookieDataに更新時間を更新 cookieData.setSecUpdDt(secUpdDt); //cookieDataをHexに変換し、ミックスする String hexCookieData = cookieSecurity.getCookieDataToHex(cookieData); hexCookieData = getMixCookieValue(hexCookieData, false); if (hexCookieData == null) { throw new Exception("setSecCookie hexData is null "); } //成功した場合、Cookieの修正日も一緒にCookieにセットする byte[] encrypted = cookieSecurity.encrypt( secUpdDt.getBytes() ); String hexSecUpdDt = getMixCookieValue(cookieSecurity.byteArrayToHex(encrypted), false); setCookie(response, CK_CHECK, hexSecUpdDt); //Cookieにセットする int dataSize = hexCookieData.length(); setCookie(response, SEC_COOKIE_NM1, hexCookieData.substring(0, dataSize)); setCookie(response, SEC_COOKIE_NM2, hexCookieData.substring(dataSize)); } /** * setSecCookieを利用して設定した情報を復号化し、CookieData形式で返す * @param request HttpServletRequest リクエスト * @return CookieData 結果データ * @throws Exception getSecCookie SecUpdDt Check fail */ public CookieData getSecCookie(HttpServletRequest request) throws Exception { /** get cookies **/ Cookie[] headerCookies = request.getCookies(); int headerCookisSize = 0; if (headerCookies != null) { headerCookisSize = headerCookies.length; } CookieData cookieData = null; String secCookieValue1 = ""; String secCookieValue2 = ""; String cookieCheck = ""; for (int hcsInx = 0; hcsInx < headerCookisSize; hcsInx++) { Cookie headerCookie = headerCookies[hcsInx]; if (headerCookie.getName().equals(SEC_COOKIE_NM1)) {secCookieValue1 = headerCookie.getValue();} else if (headerCookie.getName().equals(SEC_COOKIE_NM2)) {secCookieValue2 = headerCookie.getValue();} else if (headerCookie.getName().equals(CK_CHECK)) {cookieCheck = headerCookie.getValue();} } String secCookieValue = secCookieValue1 + secCookieValue2; if (secCookieValue.length() > 0) { secCookieValue = getMixCookieValue(secCookieValue, true); cookieData = (CookieData)cookieSecurity.getHexToObj(secCookieValue); //修正日のデータを取得する byte[] decrypted = cookieSecurity.decrypt(cookieSecurity.hexToByteArray(getMixCookieValue(cookieCheck, true))); String cookieCheckRst = (decrypted == null ? null : new String(decrypted)); if (cookieData != null) { if (cookieCheckRst == null) {throw new Exception("getSecCookie cookieCheckRst is null ");} if (!cookieCheckRst.equals(cookieData.getSecUpdDt())) { throw new Exception("getSecCookie SecUpdDt Check fail "); } } } return cookieData; } /** * Cookieにset,getするデータを混ぜる
* 混ぜる方式は決める必要がある * @param cookieValue String ミックスする文字列 * @param isUnMixed boolean Mixする(false)かMixされたデータを元に戻す(true)かの判定 * @return 結果データ */ private String getMixCookieValue(String cookieValue, boolean isUnMixed) { if (cookieValue == null || cookieValue.length() < (MIX_SIZE1+1)) { return cookieValue; } if (isUnMixed) { cookieValue = cookieValue.substring(MIX_SIZE1) + cookieValue.substring(0, MIX_SIZE1); } else { int cutInt = cookieValue.length() - MIX_SIZE1; cookieValue = cookieValue.substring(cutInt) + cookieValue.substring(0, cutInt); } return cookieValue; } /** * 全てのCookieを削除する * @param request HttpServletRequest リクエスト * @param response HttpServletResponse レスポンス * @return 処理結果 */ public static boolean removeAllCookies(HttpServletRequest request, HttpServletResponse response){ if (request == null) { return false; } String url = request.getRequestURL().toString().replace("http://", "").replace("https://", ""); int cutInx = url.indexOf("/"); url = url.substring(0, (cutInx == -1 ? url.length() : cutInx)); Cookie[] initHeaderCookies = request.getCookies(); if (initHeaderCookies != null) { for (int inx = 0; inx < initHeaderCookies.length; inx++) { String cookieNm = initHeaderCookies[inx].getName(); Cookie cookie = new Cookie(cookieNm, ""); cookie.setMaxAge(0); cookie.setDomain(url); response.addCookie(cookie); System.out.println(request.getRemoteAddr() + " : cookieUtil delete : " + cookieNm); cookie.setDomain("." + url); response.addCookie(cookie); } } return true; } }

TOPへ

・[CookieData.java]:値を保持するクラスです。(値はCookieに保存するのではなく、Class(Bean)に設定してシリアライズ⇒暗号化するため)
>>CookieData.javaダウンロード
import java.io.Serializable;


/**
 * 

 * Filename : CookieData.java
 * Class    : xxx.xxx.CookieData
 * Function : get, set
 * Comment  : Cookieを保存しておくクラス
 *      CookieデータはMAPもしくはBeanで処理する。
 *      Beanの場合、変数を追加したりすると追加する前のCookie情報と衝突するので、
 *      管理が必要。
 * History  : 2013/11/01, su min park, develop
 *       
 * 
* @version 1.0 * @author su min park * @since JDK 1.7 */ class CookieData implements Serializable { /** * Cookieを暗号化する際にユーザのIPも一緒に指定することで、セキュリティの向上が出来る。 * ※但し、(固定IPではないので)IPが変更された場合もう一度ログインして貰ったりする(アプリの)機能が必要である */ private String secUserIp = ""; //Cookieを暗号化する際にその都度、値が変わるためupdDt追加 /** Cookieを更新する際にsecUpdDtも更新する。Cookieを取得した時、そのクッキーの改ざん有無などのチェックに使われる */ private String secUpdDt = ""; //Cookieに設定される変数(Mapタイプも構わない) private String svcNm = ""; private String svcKey = ""; private String svcMemNm = ""; private String svcMemKey = ""; private String svcMemKey2 = ""; //get, set public String getSecUserIp() { return secUserIp; } public void setSecUserIp(String secUserIp) { this.secUserIp = secUserIp; } public String getSecUpdDt() { return secUpdDt; } public void setSecUpdDt(String secUpdDt) { this.secUpdDt = secUpdDt; } public String getSvcNm() { return svcNm; } public void setSvcNm(String svcNm) { this.svcNm = svcNm; } public String getSvcKey() { return svcKey; } public void setSvcKey(String svcKey) { this.svcKey = svcKey; } public String getSvcMemNm() { return svcMemNm; } public void setSvcMemNm(String svcMemNm) { this.svcMemNm = svcMemNm; } public String getSvcMemKey() { return svcMemKey; } public void setSvcMemKey(String svcMemKey) { this.svcMemKey = svcMemKey; } /** public String toString(){ return "SomeClass instance says: Don't worry, " + "I'm healty. Look, my data is svcNm= " +svcNm + ", svcKey = " + svcKey + ", svcMemNm = " + svcMemNm; } */ }

TOPへ


テストのために…

以下のロジックを利用するとテストが出来ます。
CookieData cookieData= null; 
CookieUtil cookieUtil = CookieUtil.getInstance();
try {
 cookieData = cookieUtil.getSecCookie(request);

 /* データをGET */

} catch (Exception e) {
 //エラー処理
 e.printStackTrace();
}

if (cookieData == null) {
 System.out.println("cookieData Create");
 cookieData = new CookieData();
}

//Cookie保存処理
cookieData.setSvcKey("--2---");
try {
 cookieUtil.setSecCookie(response, cookieData);
} catch (Exception e) {
 //エラー処理
 e.printStackTrace();
}

TOPへ





※【java.security.InvalidKeyException: Illegal key size or default parameters】エラーが発生する場合は、 local_policy.jar, US_export_policy.jarライブラリーを 更新してください。

・ライブラリー位置:/Java設置ディレクトリ/jre/lib/security
 例)/usr/local/java/jre/lib/security
・ダウンロードする方法:設置されているJavaのバージョンを確認し、SunサイトからJCEをダウンロード

過去のバージョンをダウンロードする方法は以下のようになります。(ダウンロードする方法は変わる可能性があります。)
http://www.oracle.com/technetwork/java/javase/downloads/index.html
  ↓
Previous Releases ボタン
  ↓
Java Platform Technologies ボタン
  ↓
設置されているJavaバージョンのJCEをダウンロード
  ↓
Zip中身のlocal_policy.jar, US_export_policy.jarファイルを/Java設置ディレクトリ/jre/lib/securityに上書き

0 コメント:

コメントを投稿