cookie的安全性问题
cookie存密码实际是一个很有历史渊源的问题了,只要使用自动登录技术就得用到cookie。于是cookie里就有了密码,从最初的txt跨站到flash,到后来的木马直接窃取cookie,都是cookie明文存储密码的弊端。
后来有人提出了对cookie进行加密,一般做法就是把字符对应的Unicode编码改成数字,每个字符间加上"a"作为解决方案,这算是第一种办法。当然也有人直接把md5散列后的密码存储到cookie里,虽然密码不能被反编译,但是很容易进行伪造,这是第二种办法。后来像discuz这样那个的论坛选择将md5配以配置文件中存储的一个固定秘钥进行加密,把128bit的MD5散列合密钥混合最终输出64进制(Base64)密码,这应算是第三类。
按照密码学的说法,第一类是古典密码(替换字),第二类根本算不上什么密码(口令),第三类可以算是现代密码(密钥)。
第三类密码实际上原理主要就在于异或的使用,这也是RAID模式之一,因为两组数据(a ^ b)进行异或,其得到的结果(c)与其中任意一组(c ^ a)进行异或就可以得到另一个数据(b)。由于密码并非时常更换的东西,而一个系统密钥也不会经常更改,所以他们进行混合后的结果等于一个定值,并没有很好的加密作用,虽然不能进行解码得到原始密码,但是直接进行伪造不失为一个好的办法。
所以在需要更加安全的密码环境里使用一次性密码,也就是那个系统密钥在一次操作后就会过期,我用一下伪代码来描述这个过程:
加密{
创建一个新钥匙
密码和钥匙混合
写入混合结果到cookie
}
解密{
读取cookie
密码和密钥分离
清空cookie
}
解密验证后,再次执行加密过程,这样即使密码没变,但是cookie中的加密信息却在做着毫无规律的变换。当然钥匙要存储在数据库里。
一个Javascript范例(可以处理ASCII 0-127号字符,做为密码加密足够了。。。如果要用来支持中文的话,只要稍加修改,不过那样的话用分割符会比较合算):
<script type="text/javascript">
var seed = 0;
function generateSeed()
{
seed = Math.floor(Math.random() * 0x7f) + 1;
$("key").value = seed;
}
function encrypt(s)
{
var fnl = "", code = 0;
for(var i = 0; i < s.length; i++){
code = s.charCodeAt(i) & 0x7f ^ (seed << 7 - i % 8 | seed >> i % 8 | 0x80) & 0xff;
fnl += code.toString(16);
}
return fnl;
}
function decrypt(s)
{
var fnl = "", code = 0;
for(var i = 0; i < s.length >> 1; i++){
code = new Number("0x" + s.substr(i * 2, 2));
fnl += String.fromCharCode((code ^ (seed << 7 - i % 8 | seed >> i % 8 | 0x80)) & 0x7f);
}
return fnl;
}
function $(id){
return document.getElementById(id);
}
</script>
SEED: <input type="text" id="key" value="" readonly="readonly" /><button onclick="generateSeed()">newSEED</button><br />
<textarea id="code"></textarea><br />
<button onclick="$('code').value = encrypt($('code').value)">encode</button>
<button onclick="$('code').value = decrypt($('code').value)">decode</button>
<script type="text/javascript">
generateSeed();
</script>
///////////////////////////////////////////////////////////////////////
在浏览器中加密Cookie
在网络应用中,cookie是一种非常方便的存储数据的方法。正因如此,你在开发WEB应用的时候更需要注意cookie的安全性。有很多办法可以做到保证cookie的安全,这里我们再讨论一种浏览器端的cookie加密。
对cookie的攻击
cookie是存储在客户端的,通常是一段文本。如果电脑是多人使用的,那么其他人就能够看到你的cookie信息,并且保存下来那个长期会话的ID,就可以伪造你的身份了。
Cookie经常是服务器端通过HTTP头部的’Set-Cookie’来设置,然后发送到客户端。这样就有可能被嗅探到。你可以使用SSL/TLS来加密网络包来防止嗅探,但是很多网站,包括Facebook也只是在登录界面使用HTTPS链接,然后就会切换到HTTP链接。像FireSheep这样的工具用来嗅探和劫持cookie是轻而易举的。
还有另外一种常见的攻击,跨站脚本攻击(XSS漏洞)当一些程序(一般是Javascript)被植入进入网页程序,然后在用户不知道的情况下执行。当Javascript在这种环境中执行的时候,就可以读取到用户的cookie信息。这种情况是很难防御的,当用户访问他们的网站的时候,用户完全在这网站的控制之下。你只能期望Y网站又足够的安全防御来防止XSS攻击。能完全杜绝这种攻击的办法只能是你关闭Javascript脚本执行。
浏览器端Cookie加密
CompletelyPrivateFiles.com提供了网络加密的解决方案。作为他们基础设施的一部分,他们建立了一个Javascript的API,可以对cookie进行256位的AES加密。这个API是免费的,可以在这里下载。
这个API通过分配一个随机动态的seed-key给用户或程序来产生一个强大的256位的密钥。然后在客户端通过这个密钥来加解密cookie,这是一个设计非常小巧的API,可以很容易整合进入已有的程序中。
你需要一个API账户,以便在程序中从服务器取得seed-key。在注册之后会得到一个sub-token,然后添加对应的js库就可以开始了。
然后当你需要设置安全cookie的时候,只要使用
setSecureCookie(secret, cookieName, cookieVal);
其中secret是你需要设置的密钥,可以是用户名、时间戳等等。
读取cookie的时候:
var cookieVal = getSecureCookie(secret, cookieName);
这里的secret必须和之前设置时候的secret值一样。
Cookie的安全
考虑到本文开头所说的,在每种情况下,攻击者只是为了能取到cookie的值,我们将这个值加密之后,他取到的值也就没有什么意义了。
万一有些人有访问本地资源的权限,可以扫描cookie内容,那么他看到的也只是乱码,没有什么意义。
或者说你将加密后的cookie通过表单再发送到服务器,然后服务端通过’Set-Cookie’的请求来发挥客户端设置cookie,那么在传输过程中也是加密后的内容,计算通过HTTP传输也没有安全问题。
XSS的攻击很难防御,因为攻击者对网页有完全的控制权,如果攻击不是很具体,那么可能只是读取一些内存中的变量,或者根据XSS漏洞读取你的cookie,但是想要获得有价值的cookie信息,必须先过解密者一关。
需要注意的是,加密并不能组织恶意用户损坏你的cookie值,是程序无法解密。但这并不会损坏cookie的价值。
用户隐私
除了提高安全性,你还可以通过加密的cookie来保护用户的隐私。随着越来越多的用户数据保存在云端,以及有关隐私的问题越来越突出,你或许希望保证用户的信息只能被他们自己看到。
这个问题的解决办法再用户登录之后,通过加密的cookie来存储一些敏感的信息。cookie有个过期的问题。可以通过把cookie加密后存储在服务器端,然后使用‘set-cookie’头,这样就可以了,这样甚至可以做到无限期的保存。
一个例子就是在线支付,交易是存储在网络的,但是实际的银行账户信息是通过加密的cookie来访问的。将cookie存储在服务端,然后在本地解密,银行信息就可以在程序的上下文中进行,但是也保证了用户的隐私。
总结
客户端的cookie加密对于提高网络应用的安全性是显而易见的。对于你的程序的安全性设计提供了另一个思路。他不仅仅是一个安全工具,也是一个隐私保护工具,这在现在的网络编程中是非常重要的。
//////////////////////////////////////////////////////////////////////////////
简单加密算法(转载)
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class CryptUtil {
private static final CryptUtil instance = new CryptUtil();
private CryptUtil() {
}
public static CryptUtil getInstance() {
return instance;
}
private Key initKeyForAES(String key) throws NoSuchAlgorithmException {
if (null == key || key.length() == 0) {
throw new NullPointerException("key not is null");
}
SecretKeySpec key2 = null;
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(key.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
key2 = new SecretKeySpec(enCodeFormat, "AES");
} catch (NoSuchAlgorithmException ex) {
throw new NoSuchAlgorithmException();
}
return key2;
}
/**
* AES加密算法,不受密钥长度限制
* @param content
* @param key
* @return
*/
public String encryptAES(String content, String key){
try{
SecretKeySpec secretKey = (SecretKeySpec) initKeyForAES(key);
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);// 初始化
byte[] result = cipher.doFinal(byteContent);
return asHex(result); // 加密
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* aes解密算法,不受密钥长度限制
* @param content
* @param key
* @return
*/
public String decryptAES(String content, String key){
try{
SecretKeySpec secretKey = (SecretKeySpec) initKeyForAES(key);
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, secretKey);// 初始化
byte[] result = cipher.doFinal(asBytes(content));
return new String(result); // 加密
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
/**
* 将2进制数值转换为16进制字符串
* @param buf
* @return
*/
public String asHex(byte buf[]){
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++){
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
/**
* 将16进制转换
* @param hexStr
* @return
*/
public byte[] asBytes(String hexStr) {
if (hexStr.length() < 1)
return null;
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++){
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2),16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
public static void main(String[] args) {
CryptUtil crypt = CryptUtil.getInstance();
String content = "asdfsdfdsfds|33333443";
System.out.println(crypt.encryptAES(content, "aaa22"));
String dcontent = crypt.encryptAES(content, "aaa22");
System.out.println(crypt.decryptAES(dcontent, "aaa22"));
}
}
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
AES加解密算法,使用Base64做转码以及辅助加密:
package com.wintv.common;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
/*******************************************************************************
* AES加解密算法
*
* @author arix04
*
*/
public class AES {
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes());//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(sSrc.getBytes());
return new BASE64Encoder().encode(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("ASCII");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("0102030405060708"
.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
public static void main(String[] args) throws Exception {
/*
* 加密用的Key 可以用26个字母和数字组成,最好不要用保留字符,虽然不会错,至于怎么裁决,个人看情况而定
* 此处使用AES-128-CBC加密模式,key需要为16位。
*/
String cKey = "1234567890123456";
// 需要加密的字串
String cSrc = "Email : arix04@xxx.com";
System.out.println(cSrc);
// 加密
long lStart = System.currentTimeMillis();
String enString = AES.Encrypt(cSrc, cKey);
System.out.println("加密后的字串是:" + enString);
long lUseTime = System.currentTimeMillis() - lStart;
System.out.println("加密耗时:" + lUseTime + "毫秒");
// 解密
lStart = System.currentTimeMillis();
String DeString = AES.Decrypt(enString, cKey);
System.out.println("解密后的字串是:" + DeString);
lUseTime = System.currentTimeMillis() - lStart;
System.out.println("解密耗时:" + lUseTime + "毫秒");
}
}
AES加解密算法,使用byte2hex做转码:
package com.wintv.common;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
/*******************************************************************************
* AES加解密算法
*
* @author arix04
*
*/
public class AES {
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("0102030405060708".getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(sSrc.getBytes());
return byte2hex(encrypted).toLowerCase();
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("ASCII");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec("0102030405060708"
.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = hex2byte(sSrc);
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
public static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2),
16);
}
return b;
}
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
public static void main(String[] args) throws Exception {
/*
* 加密用的Key 可以用26个字母和数字组成,最好不要用保留字符,虽然不会错,至于怎么裁决,个人看情况而定
*/
String cKey = "1234567890123456";
// 需要加密的字串
String cSrc = "Email : arix04@xxx.com";
System.out.println(cSrc);
// 加密
long lStart = System.currentTimeMillis();
String enString = AES.Encrypt(cSrc, cKey);
System.out.println("加密后的字串是:" + enString);
long lUseTime = System.currentTimeMillis() - lStart;
System.out.println("加密耗时:" + lUseTime + "毫秒");
// 解密
lStart = System.currentTimeMillis();
String DeString = AES.Decrypt(enString, cKey);
System.out.println("解密后的字串是:" + DeString);
lUseTime = System.currentTimeMillis() - lStart;
System.out.println("解密耗时:" + lUseTime + "毫秒");
}
}