首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > Web前端 >

cookie的安全性有关问题

2012-09-10 
cookie的安全性问题cookie存密码实际是一个很有历史渊源的问题了,只要使用自动登录技术就得用到cookie。于

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 + "毫秒");
    }
}

热点排行