前陣子上課的時候,老師介紹了在某些場合以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月25日 星期四
Choose WeakHashMap over HashMap?
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);
}
}
訂閱:
文章 (Atom)
