2010年2月25日 星期四

Choose WeakHashMap over HashMap?


前陣子上課的時候,老師介紹了在某些場合以WeakHashMap取代HashMap,原因是因為WeakHashMap的key使用了weak references,所以比較容易被Garbage Collection在某些場合解決了memory leak的問題,底下是官方的說明
A hashtable-based Map implementation with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use.
令我好奇想深究的是,上課時的一堆framework, solution都有人聽過,唯獨這WeakHashMap幾乎全場沒人聽過,而講師當場讓大家觀察memory使用率的變化,比起HashMap卻又有一定程度的降低,於是找了很久找到下面這篇文章
http://www.codeinstructions.com/2008/09/weakhashmap-is-not-cache-understanding.html
其中提到了WeakReference and SoftReference此兩類較為weak的object reference的Reference class(相對於一般的regular java references, 俗稱strong references Objects)
以他文中所舉的例子String name = "John Doe";中 "John Doe"這個String object為strong reference
故...
待續...

2010年2月23日 星期二

Directory traversal與 ESAPI提供之RandomAccessReferenceMap


Directory traversal發生於開發者未對request的檔案路徑進行檢查,而產生路徑跳脫,使得使用者跳脫至不應存取之檔案目錄
而預防的方法則應將file路徑進行檢驗將跳脫字元過濾掉
而OWASP所提供的ESAPI中的RandomAccessReferenceMap則提供了一個方向
RandomAccessReferenceMap繼承AccessReferenceMap此interface
此interface將direct的物件reference轉換成indirect的reference,避免資訊的洩漏,如db key或filename等欲保護之敏感資料

而RandomAccessReferenceMap則將direct object referencence隨機產生長度6碼(英數字)的indirect references,某種程度上也能有效的降低CSRF的威脅,底下附上一簡單的範例
ps.如果將對應關係儲存起來應該是就目前熱門提供的短網址服務了


/*
*
*/

package mypackage;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.owasp.esapi.errors.AccessControlException;
import java.io.*;
import java.util.HashMap;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.owasp.esapi.reference.*;;

/**
*
*/

public final class RandomAccessReferenceMapExample extends HttpServlet {

private static final long serialVersionUID = 1L;
private static final int MAX_FILE_SIZE = 1024;
private static String DIR = "/home/student/downloads/";
private static final Logger logger = Logger.getLogger("RandomAccessReferenceMapExample.java");

private static RandomAccessReferenceMap accessMap = new RandomAccessReferenceMap();

public void init() throws ServletException {
// 定義可以被存取的資料

String s = (String)accessMap.addDirectReference(DIR + "檔案名稱");
//將檔案完整路徑加入RandomAccessMap並取得對應的indirect reference
logger.info("indirectRef: " + s);
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {

String fName = request.getParameter("fName");//由request取得的file參數
String indirectFileRef = (String)accessMap.getIndirectReference(DIR + fName);
//將使用者之輸入加上路徑至map取得其indirect reference,若非事先定義者將取得null
logger.info("indirectRef: " + indirectFileRef);

try {
String directRef = (String)accessMap.getDirectReference(indirectFileRef);
//這裡嘗試將indirect reference送進map取得原本file之direct reference
logger.info("direct: " + directRef);
} catch (AccessControlException e) {
//若無產生AccessControlException則可正常取得檔案
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}

byte data[] = new byte[MAX_FILE_SIZE];
int numBytes = 0;
int totalBytesRead = 0;
FileInputStream fis = null;
OutputStream out = null;

try {
fis = new FileInputStream (DIR + request.getParameter("fName"));
while (numBytes != -1 && totalBytesRead < MAX_FILE_SIZE) {
numBytes = fis.read(data, totalBytesRead, MAX_FILE_SIZE - totalBytesRead);
totalBytesRead += numBytes;
}

response.setContentType("text/html");
out = response.getOutputStream();
out.write(data, 0, totalBytesRead);
} catch(IOException e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
fis.close();
out.close();
}
}
}

2010年2月8日 星期一

Salt Please


一般開發人員在對使用者密碼作處理時,通常會使用hash function去對user password做處理產生不可逆之hash值,而惡意人士現在則使用rainbow table這種大量事先產生好之明文<->hash之對應,使得hash變的相對的沒這麼安全了,所以就產生了進階的salt,其原理為我們將明文進行hash之前加入一random的salt值,而再將添加過salt值得明文產生hash值,如此一來就增加密碼被破解的難度,Java的salt範例如下:




import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import org.apache.commons.codec.binary.Base64;



/**

*

* @author andy

*/

public class MessageDigestExample {



public static void main(String args[]) {

MessageDigest md = null;

byte pwd[] = {'p','a','s','s','w','o','r','d'};
byte[] salt = new byte[8];//產生任意長度的salt值
try {

md = MessageDigest.getInstance("SHA-256");

md.update(pwd);

SecureRandom random = null;

random = SecureRandom.getInstance("SHA1PRNG");
random.nextBytes(salt); //產生salt

} catch (NoSuchAlgorithmException e) {

System.err.println(e);

}


md.update(salt); //加入salt

byte[] hash = md.digest();//產生加入salt的hash值

String encodedString = new String( Base64.encodeBase64(hash) );

System.out.println(encodedString);

}

}


而slat的對應表也儲存在文件或資料庫中,所以使用者輸入密碼後,流程將由原先的計算hash改為取得對應salt重新計算新的salt值以做比較

2010年2月7日 星期日

ESAPI CSRFTokenUtil


ESAPI的CSRFTokenUtil
利用java.security.SecureRandom產生secure random token
提供頁面表單所加入的security token以供後端server進行驗證
除可預防傳統的表單重複傳送,也可以預防CSRF(Cross Site Request Forgery)
而工作上所使用的framework sturts也提供了token的功能,但是review原始碼後可以發現struts2所提供的演算法異常的薄弱=_=(真的很弱),所以建議自行改寫
而OWASP的ESAPI提供的CSRFTokenUtil或許可以補強這塊
其中SecureRandom.getInstance("SHA1PRNG")的SHA1PRNG之前都不曾使用過,便好好的研讀並請教熟密碼學的學長,java doc解釋SecureRandom這class如下:
This class provides a cryptographically strong pseudo-random number generator (PRNG)
關於PRNG的解釋引述IV的解釋如下:
"像 random這樣的參數
在一定次數的抽樣下,
是會有重覆,並且可預測的
所以學術界在研究這塊的
就是研究一個 pseudo-random number的產生器
讓它可以儘量randomn一點
通常random number的產生方法
是先產生一串很長的亂數串列
然後還裏面隨機取出一個來
就是亂數
但是人造的串列,即使很長很長
總是會產出一個cycle
所以研究的人就是想辦法把這個cycle弄長
讓它不會被預測中,並且於cycle中提供一個演算法
讓每個數被抽到的機率是一樣的均勻的抽到一個數
於是密碼學上就要來定義,怎麼樣的pseudo-random number產生器是好的產出器
就有了「strong」pseudo-random number generator
有一些條件要滿足,滿足了這些條件,就是叫作strong pseudo-random number generator沒有滿足的,就只能叫作pseudo-random number generator
而SHA1PRNG 是利用sha1演算法為基礎,來產生 pseudo-random number
所以叫作SHA1PNG"
使用方法
前端jsp頁面的表單中加入input type="hidden"

name=<%= CSRFTokenUtil.SESSION_ATTR_KEY %>
value=<%= CSRFTokenUtil.getToken(request.getSession(false)) %>

後端則使用

if (!CSRFTokenUtil.isValid(request.getSession(false), request)){
return mapping.findForward("error");
}

底下是CSRFUtil的source code



package org.owasp.csrf.util;

import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletException;

/**
* Based on OWASP CryptoUtil.java and CSRFGuard.java written by Eric Sheriden for OWASP
*
* This version by Rohit K. Sethi, Security Compass
* Created Aug. 9, 2007
*
* This is a very basic tool to add a unique token to form fields for
* mitigating against Cross Site Request Forgery (CSRF) attacks.
*
*/


public final class CSRFTokenUtil
{
private final static String DEFAULT_PRNG = "SHA1PRNG"; //algorithm to generate key
public final static String SESSION_ATTR_KEY = "CSRF_TOKEN";
private final static String NO_SESSION_ERROR = "No valid session found";
/**
* Generates a random token to use in CSRF with the default
* Crytopgrahically strong Pseudo-Number Random Generator
*
* @return A string with a random token
* @throws NoSuchAlgorithmException if PRNG algorithm is not valid
*/
private static String getToken() throws NoSuchAlgorithmException
{
return getToken(DEFAULT_PRNG);
}

/**
* Generates a random token to use in CSRF with the default
* Crytopgrahically strong Pseudo-Number Random Generator
*
* @param prng Random Number generator to use
* @return A string with a random token
* @throws NoSuchAlgorithmException if PRNG algorithm is not valid
*/
private static String getToken(String prng) throws NoSuchAlgorithmException
{
SecureRandom sr = SecureRandom.getInstance(prng);
return "" + sr.nextLong();
}


/**
* Retrieves the CSRF token from the current session. Creates a token if one
* does not already exist
* @param session HTTP Session for user - must be valid
* @return token for the session, a new one is created if it doesn't already exist
* @throws ServletException if session is null
* @throws NoSuchAlgorithmException if random number generator algorithm doesn't exist
*/
public static String getToken (HttpSession session) throws ServletException, NoSuchAlgorithmException {
//throw exception if session is null
if (session == null) {
throw new ServletException(NO_SESSION_ERROR);
}

//Now attempt to retrieve existing token from session. If it doesn't exist then
//add it
String token_val = (String)session.getAttribute(SESSION_ATTR_KEY);
if (token_val == null){
token_val = getToken();
session.setAttribute(SESSION_ATTR_KEY, token_val);

}
return token_val;

}

/**
* Tests whether or not the value of the CSRF_TOKEN parameter in the request
* is equal to the value of the CSRF_TOKEN attribute in the session
*
* @param session Session with existing token (will be created if it doesn't exist)
* @param request Inbound HttpRequest that you wish to check if it has a valid
* anti-CSRF token
* @return true if the parameter value matches the token in the session, false otherwise
* @throws ServletException If the session is null
* @throws NoSuchAlgorithmException if random number generator algorithm doesn't exist
*/
public static boolean isValid (HttpSession session, HttpServletRequest request)
throws ServletException, NoSuchAlgorithmException {
//throw exception if session is null
if (session == null) {
throw new ServletException(NO_SESSION_ERROR);
}
return getToken(session).equals(
request.getParameter(SESSION_ATTR_KEY));
}

}


這裡可以看到sturts所提供的token產生方式

return new BigInteger(165, RANDOM).toString(36).toUpperCase();




/*
* $Id: TokenHelper.java 781798 2009-06-04 17:08:35Z wesw $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util;

import java.math.BigInteger;
import java.util.Map;
import java.util.Random;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.LocalizedTextUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
* TokenHelper
*
*/
public class TokenHelper {

/**
* The default name to map the token value
*/
public static final String DEFAULT_TOKEN_NAME = "struts.token";

/**
* The name of the field which will hold the token name
*/
public static final String TOKEN_NAME_FIELD = "struts.token.name";
private static final Logger LOG = LoggerFactory.getLogger(TokenHelper.class);
private static final Random RANDOM = new Random();


/**
* Sets a transaction token into the session using the default token name.
*
* @return the token string
*/
public static String setToken() {
return setToken(DEFAULT_TOKEN_NAME);
}

/**
* Sets a transaction token into the session using the provided token name.
*
* @param tokenName the name to store into the session with the token as the value
* @return the token string
*/
public static String setToken(String tokenName) {
Map session = ActionContext.getContext().getSession();
String token = generateGUID();
try {
session.put(tokenName, token);
}
catch(IllegalStateException e) {
// WW-1182 explain to user what the problem is
String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage();
LOG.error(msg, e);
throw new IllegalArgumentException(msg);
}

return token;
}


/**
* Gets a transaction token into the session using the default token name.
*
* @return token
*/
public static String getToken() {
return getToken(DEFAULT_TOKEN_NAME);
}

/**
* Gets the Token value from the params in the ServletActionContext using the given name
*
* @param tokenName the name of the parameter which holds the token value
* @return the token String or null, if the token could not be found
*/
public static String getToken(String tokenName) {
if (tokenName == null ) {
return null;
}
Map params = ActionContext.getContext().getParameters();
String[] tokens = (String[]) params.get(tokenName);
String token;

if ((tokens == null) || (tokens.length < 1)) {
LOG.warn("Could not find token mapped to token name " + tokenName);

return null;
}

token = tokens[0];

return token;
}

/**
* Gets the token name from the Parameters in the ServletActionContext
*
* @return the token name found in the params, or null if it could not be found
*/
public static String getTokenName() {
Map params = ActionContext.getContext().getParameters();

if (!params.containsKey(TOKEN_NAME_FIELD)) {
LOG.warn("Could not find token name in params.");

return null;
}

String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
String tokenName;

if ((tokenNames == null) || (tokenNames.length < 1)) {
LOG.warn("Got a null or empty token name.");

return null;
}

tokenName = tokenNames[0];

return tokenName;
}

/**
* Checks for a valid transaction token in the current request params. If a valid token is found, it is
* removed so the it is not valid again.
*
* @return false if there was no token set into the params (check by looking for {@link #TOKEN_NAME_FIELD}), true if a valid token is found
*/
public static boolean validToken() {
String tokenName = getTokenName();

if (tokenName == null) {
if (LOG.isDebugEnabled())
LOG.debug("no token name found -> Invalid token ");
return false;
}

String token = getToken(tokenName);

if (token == null) {
if (LOG.isDebugEnabled())
LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
return false;
}

Map session = ActionContext.getContext().getSession();
String sessionToken = (String) session.get(tokenName);

if (!token.equals(sessionToken)) {
LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{
token, sessionToken
}));

return false;
}

// remove the token so it won't be used again
session.remove(tokenName);

return true;
}

public static String generateGUID() {
return new BigInteger(165, RANDOM).toString(36).toUpperCase();
}
}

2010年2月2日 星期二

JCA Cipher加解密


JCA加密/解密的介紹:
使用Cipher進行
1.加/解密資料
2.使用Blowfish與OFB mode
3.人工產生IV(Initialization Vector)





import java.security.SecureRandom;

import javax.crypto.KeyGenerator;

import javax.crypto.SecretKey;

import javax.crypto.Cipher;

import java.security.NoSuchAlgorithmException;

import java.security.InvalidKeyException;

import java.security.InvalidAlgorithmParameterException;

import javax.crypto.NoSuchPaddingException;

import javax.crypto.IllegalBlockSizeException;

import javax.crypto.BadPaddingException;

import javax.crypto.spec.IvParameterSpec;

import org.apache.commons.codec.binary.Base64;



/**

*

* @author Frank Kim

*/

public class CipherWithIvExample {



public static void main(String args[]) {

try {

KeyGenerator kg = KeyGenerator.getInstance("Blowfish");

SecretKey sk = kg.generateKey();



byte[] origData = "data".getBytes();



Cipher cipher = Cipher.getInstance("Blowfish/OFB/PKCS5Padding");



byte[] randomBytes = new byte[8];

SecureRandom random = SecureRandom.getInstance("SHA1PRNG");

random.nextBytes(randomBytes);

IvParameterSpec ivSp1 = new IvParameterSpec(randomBytes);



cipher.init(Cipher.ENCRYPT_MODE, sk, ivSp1);

byte[] encryText = cipher.doFinal(origData);



byte[] iv = cipher.getIV();

IvParameterSpec ivSp2 = new IvParameterSpec(iv);



cipher.init(Cipher.DECRYPT_MODE, sk, ivSp2);

byte[] decryText = cipher.doFinal(encryText);



String origString = new String(origData);

String decryString = new String(decryText);

System.out.println("Data matches? " + origString.equals(decryString));



} catch (NoSuchAlgorithmException e1) {

System.err.println(e1);

} catch (InvalidKeyException e2) {

System.err.println(e2);

} catch (NoSuchPaddingException e3) {

System.err.println(e3);

}catch (IllegalBlockSizeException e4) {

System.err.println(e4);

} catch (BadPaddingException e5) {

System.err.println(e5);

} catch (InvalidAlgorithmParameterException e6) {

System.err.println(e6);

}

}



}

JCA-HMAC


JCA的message digest介紹:
1.使用SHA-256產生HMAC




import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.Mac;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import org.apache.commons.codec.binary.Base64;

/**
*
* @author andy
*/
public class HmacExample {

public static void main(String args[]) {
try {
KeyGenerator kg = KeyGenerator.getInstance("HmacSHA256");
SecretKey sk = kg.generateKey();

Mac mac = Mac.getInstance("HmacSHA256");//use HmacSHA256
mac.init(sk);
byte[] hmac = mac.doFinal("data".getBytes());

String encodedString = new String( Base64.encodeBase64(hmac) );
System.out.println("HMAC:"+encodedString);
} catch (NoSuchAlgorithmException e1) {
System.err.println(e1);
} catch (InvalidKeyException e2) {
System.err.println(e2);
}
}

}

JCA-Salt example


JCA的message digest介紹:
1.使用SHA-256
2.加入salt





import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import org.apache.commons.codec.binary.Base64;



/**

*

* @author andy

*/

public class MessageDigestExample {



public static void main(String args[]) {

// put code here to create a MD5 hash with a salt

MessageDigest md=null;

byte password[]={'p','a','s','s','w','o','r','d'};



try {

md= MessageDigest.getInstance("SHA-256");



} catch (NoSuchAlgorithmException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

md.update(password);

byte[] salt=new byte[8];

SecureRandom random=null;

try {

random= SecureRandom.getInstance("SHA1PRNG");

} catch (NoSuchAlgorithmException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

random.nextBytes(salt);

md.update(salt);

byte[] hash=md.digest();

String encodingString=new String(Base64.encodeBase64(hash));

System.out.println("salt please:"+encodingString);

}



}