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 οnclick="generateSeed()">newSEED</button><br />
<textarea id="code"></textarea><br />
<button οnclick="$('code').value = encrypt($('code').value)">encode</button>
<button οnclick="$('code').value = decrypt($('code').value)">decode</button>

<script type="text/javascript">
generateSeed();
</script>


///
[align=center][color=darkred][b]在浏览器中加密Cookie[/b][/color][/align]
在网络应用中,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 + "毫秒");
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于确保 cookie安全性,可以采取以下几种解决办法: 1. 使用 HTTPS 协议:通过使用 HTTPS 加密协议来传输请求和响应,可以防止中间人窃听和篡改的风险,确保数据的安全性。 2. 设置 Secure 标记:在设置 cookie 时,使用 Secure 标记来指示浏览器仅在通过 HTTPS 连接发送请求时才发送该 cookie。这可以防止通过非加密连接发送 cookie,提高其安全性。 3. 设置 HttpOnly 标记:在设置 cookie 时,使用 HttpOnly 标记来禁止 JavaScript 访问该 cookie。这可以防止恶意脚本通过 XSS(跨站脚本攻击)获取用户的 cookie,提高其安全性。 4. 设置 SameSite 标记:在设置 cookie 时,使用 SameSite 标记来限制 cookie 的发送范围。可以设置为 "Strict"、"Lax" 或 "None"。Strict 模式下,cookie 仅在与请求网站具有一致性的情况下发送;Lax 模式下,在某些情况下,例如从外部站点链接访问时,也可以发送;None 模式下,始终发送 cookie。这可以防止 CSRF(跨站请求伪造)攻击。 5. 对敏感信息进行加密处理:如果需要在 cookie 中存储敏感信息,应该先对其进行加密处理,确保即使 cookie 被窃取,也无法直接获取敏感信息。 6. 实施访问控制和身份验证:在服务器端实施访问控制和身份验证机制,确保只有经过验证的用户才能访问敏感功能或数据,从而降低 cookie 被利用的风险。 通过采取这些安全措施,可以增加 cookie安全性,减少潜在的攻击风险。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值