工作記錄:DiscuzX中uc_authcode函數js版本,配合java雙向加密解密

上週準備寫一個js版的uc_authcode函數,根據網上整理的資料:http://bbs.csdn.net/topics/390310377?page=1中Frogant的源碼(純JS版)進行相關測試,同時引入了md5.js和base64.js庫。測試結果的準確率不到一半,於是根據DX中的uc_authcode源碼進行跟蹤調試,發現問題出在base64的編碼上,可能是中外對字符串的編碼不一致導致的,後來在網上搜到其他版本的base64.js庫測試可用:http://download.csdn.net/detail/duyipeng/2034266上傳的資源可以測試通過。

注意:無論是js、java還是php的腳本文件的編碼必須爲utf-8編碼,否則可能會出現錯誤。原版的代碼在IE下兼容有一些問題,經過修改之後目前可兼容IE6,7,8,opera,firefox,google等瀏覽器。源代碼如下:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title></title>
        <style type="text/css">
        </style>
    </head>
    <body>
		<?php
		if(isset($_GET['jsCode']))
		{
			//傳入的字符串
			$jsCode = base64_decode($_GET['jsCode']);
			p('jsCode:'.$jsCode);
			//解碼後的字符串
			$decode = uc_authcode($jsCode, 'DECODE', 'key');
			p('解碼後:'.$decode);
			//urldecode之後的字符串
			p('urldecode後:'.urldecode($decode));
		}else{
			$en = '~!@#$%^&*()_+=-0987654321\][.,/';
			$en_code = uc_authcode($en, 'ENCODE', 'key');
			p(uc_authcode($en_code, 'DECODE', 'key'));

			$cn = '屌絲(聯通)';
			$cn_code = uc_authcode($cn, 'ENCODE', 'key');
			p(uc_authcode($cn_code, 'DECODE', 'key'));

			$str = urlencode('來自php加密');
			$encode = uc_authcode($str, 'ENCODE', 'key');
		}


		function p($var)
		{
			echo "<pre>";
			if($var === false)
			{
				echo 'false';
			}else if($var === ''){
				print_r("''");
			}else{
				print_r($var);
			}
			echo "</pre>";
		}

		/**
		* @param    string      $string 加密內容
		* @param    string      $operation 加密動作
		* @param    string      $key 私鑰
		* @param    int         $expiry 有效時間秒
		* @return   string      加密串
		*/
		function uc_authcode($string, $operation = 'DECODE', $key = '', $expiry = 0)
		{
			$ckey_length = 4;
			$key = md5($key);
			$keya = md5(substr($key, 0, 16));
			$keyb = md5(substr($key, 16, 16));
			$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
			$cryptkey = $keya.md5($keya.$keyc);
			$key_length = strlen($cryptkey);
			$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
			$string_length = strlen($string);
			$result = '';
			$box = range(0, 255);
			$rndkey = array();
			for($i = 0; $i <= 255; $i++)
			{
				$rndkey[$i] = ord($cryptkey[$i % $key_length]);
			}
			for($j = $i = 0; $i < 256; $i++)
			{
				$j = ($j + $box[$i] + $rndkey[$i]) % 256;
				$tmp = $box[$i];
				$box[$i] = $box[$j];
				$box[$j] = $tmp;
			}
			for($a = $j = $i = 0; $i < $string_length; $i++)
			{
				$a = ($a + 1) % 256;
				$j = ($j + $box[$a]) % 256;
				$tmp = $box[$a];
				$box[$a] = $box[$j];
				$box[$j] = $tmp;
				$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
			}
			if($operation == 'DECODE')
			{
				if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16))
				{
					return substr($result, 26);
				}else{
					return '';
				}
			}else{
				return $keyc.str_replace('=', '', base64_encode($result));
			}
		}

		?>
        <div id="resultDiv"><?php echo $encode;?></div>
        <script type="text/javascript" src="md5.min.js"></script>
        <script type="text/javascript" src="base64.min.js"></script>
        <script type="text/javascript">
            /**  
            * @param    string      str 加密內容  
            * @param    string      operation 加密動作  
            * @param    string      key 密鑰  
            * @param    int         expiry 有效時間秒  
            * @return   string      加密串  
            */  
            function uc_authcode(str, operation, key, expiry) {
                var operation = operation ? operation : 'DECODE';
                var key = key ? key : '';
                var expiry = expiry ? expiry : 0;

                var ckey_length = 4;
                key = md5(key);

                // 密匙a會參與加解密
                var keya = md5(key.substr(0, 16));
                // 密匙b會用來做數據完整性驗證
                var keyb = md5(key.substr(16, 16));
                // 密匙c用於變化生成的密文
                // IE下不支持substr第一個參數爲負數的情況
                if(ckey_length){
                    if(operation == 'DECODE'){
                        var keyc = str.substr(0, ckey_length);
                    }else{
                        var md5_time = md5(microtime());
                        var start = md5_time.length - ckey_length;
                        var keyc = md5_time.substr(start, ckey_length)
                    }
                }else{
                    var keyc = '';
                }
                // 參與運算的密匙
                var cryptkey = keya + md5(keya + keyc);

                var strbuf;

                if (operation == 'DECODE') {
                    str = str.substr(ckey_length);
                    strbuf = base64_decode(str);
                    //string = b.toString();
                } else {
                    expiry = expiry ? expiry + time() : 0;
                    tmpstr = expiry.toString();
                    if (tmpstr.length >= 10)
                        str = tmpstr.substr(0, 10) + md5(str + keyb).substr(0, 16) + str;
                    else {
                        var count = 10 - tmpstr.length;
                        for (var i = 0; i < count; i++) {
                            tmpstr = '0' + tmpstr;
                        }
                        str = tmpstr + md5(str + keyb).substr(0, 16) + str;
                    }
                    strbuf = str;
                }
                

                var box = new Array(256);
                for (var i = 0; i < 256; i++) {
                    box[i] = i;
                }
                var rndkey = new Array();
                // 產生密匙簿
                for (var i = 0; i < 256; i++) {
                    rndkey[i] = cryptkey.charCodeAt(i % cryptkey.length);
                }
                // 用固定的算法,打亂密匙簿,增加隨機性,好像很複雜,實際上對並不會增加密文的強度
                for (var j = i = 0; i < 256; i++) {
                    j = (j + box[i] + rndkey[i]) % 256;
                    tmp = box[i];
                    box[i] = box[j];
                    box[j] = tmp;
                }

                // 核心加解密部分
                var s = '';
                //IE下不支持直接通過下標訪問字符串的字符,需要先轉換爲數組
                strbuf = strbuf.split('');
                for (var a = j = i = 0; i < strbuf.length; i++) {
                    a = (a + 1) % 256;
                    j = (j + box[a]) % 256;
                    tmp = box[a];
                    box[a] = box[j];
                    box[j] = tmp;
                    // 從密匙簿得出密匙進行異或,再轉成字符
                    s += chr(ord(strbuf[i])^(box[(box[a] + box[j]) % 256]));
                }
                
                if (operation == 'DECODE') {
                    if ((s.substr(0, 10) == 0 || s.substr(0, 10) - time() > 0) && s.substr(10, 16) == md5(s.substr(26) + keyb).substr(0, 16)) {
                        s = s.substr(26);
                    } else {
                        s = '';
                    }
                } else {
                    s = base64_encode(s);
                    var regex = new RegExp('=', "g");
                    s = s.replace(regex, '');
                    s = keyc + s;
                }
                
                return s;
            }
            
            function time() {
                var unixtime_ms = new Date().getTime();
                return parseInt(unixtime_ms / 1000);
            }

            function microtime(get_as_float) {
                var unixtime_ms = new Date().getTime();
                var sec = parseInt(unixtime_ms / 1000);
                return get_as_float ? (unixtime_ms / 1000) : (unixtime_ms - (sec * 1000)) / 1000 + ' ' + sec;
            }
            function chr(s) {
                return String.fromCharCode(s);
            }
            function ord(s) {
                return s.charCodeAt();
            }

            function md5(str) {
                return hex_md5(str);
            }
            
            var resultDiv = document.getElementById('resultDiv');
            var str = resultDiv.innerHTML;
            //php加密,js解密
            document.write(uc_authcode(str, 'DECODE', 'key')+'<br/>');
            //使用錯誤密鑰將會得到空字符串
            document.write(uc_authcode(str, 'DECODE', 'errorkey')+'<br/>');
            //英文測試,js加密解密
            var en = '~!@#$%^&*()_+=-0987654321\][.,"\'></abcdefghijklmnopqrstuvwxyz';
            var en_code = uc_authcode(en, 'ENCODE', 'key');
            document.write(uc_authcode(en_code, 'DECODE', 'key')+'<br/>');
            //中文測試,js加密解密,特殊編碼字符串需要通過手動進行encodeURI編碼解碼,暫未發現異常
            var cn = encodeURI('屌絲聯通營業(),。?“:》《%……');
            var cn_code = uc_authcode(cn, 'ENCODE', 'key');
            document.write(cn_code+'<br/>');
            resultDiv.innerHTML = '<a href="?jsCode='+base64_encode(cn_code)+'">點我使用php解碼</a>';
        </script>
    </body>
</html>

經過反覆嘗試,中文字符未採用duyipeng上傳的base64.js中的utf16to8編碼方式,而是採用js原生的encodeURI進行編碼,在php端同樣可以使用urlencode進行編碼。最後進行雙向測試,目前進行測試的字符全部加密解密成功,由此可以實現js和php進行無縫對接。
注意:在使用GET方式傳參過程中,經過uc_authcode之後的加密串可能會帶有空格等特殊字符,需要使用js版base64_encode進行編碼後傳輸,在php端使用php版base64_decode進行解碼,然後使用uc_authcode進行解密。

源碼下載地址:http://download.csdn.net/detail/zhengshuiguang/8289509

同樣,java在編碼中文字符的時候建議使用URLEncoder和URLDecoder進行編碼和解碼再加密。不過,本類中的encode和decode只是js的移植版,不保證絕對兼容,之所以這麼做是因爲直接使用org.apache.commons.codec.binary.Base64等編碼和解碼的時候遇到了其他語言加密java可以解密但是java加密後js或php都無法解密。

此java類在小數據量情況下基本上不會造成性能問題,大量數據通信暫未測試。(小範圍內測通過)

代碼如下:

package com.github.shuiguang.utils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Date;

/**
 * uc_authcode工具類
 * @author shuiguang
 *
 */
public class UCUtil {

	public static final String base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	
	public static void main(String[] args) throws InterruptedException, IOException {
		String operation = "DECODE";
		
		String key = "key";
		
		int expiry = 1;
		
		String ucAuthcode = UCUtil.ucAuthcode(URLEncoder.encode("我的世界", "UTF-8"), "ENCODE", key, expiry);
		
		// 打印出密文
		System.out.println(ucAuthcode);
		
		String result2 = UCUtil.ucAuthcode(ucAuthcode, operation, key, expiry);

		// 密文未過期可以打印出明文
		System.out.println("result2="+URLDecoder.decode(result2, "UTF-8"));

		// 休眠1s後密文過期
		Thread.sleep(1000);

		String result3 = UCUtil.ucAuthcode(ucAuthcode, operation, key, expiry);

		// 密文過期後打印出空字符串
		System.out.println("result3="+URLDecoder.decode(result3, "UTF-8"));
		
	}
	
	/**
	 * 加密算法
	 * @param str 待加密內容
	 * @param operation 加密動作
	 * @param key 密鑰
	 * @param expiry 有效時間,單位s
	 * @return 加密後的字符串
	 * @throws UnsupportedEncodingException 
	 */
	public static String ucAuthcode(String str, String operation, String key, int expiry) throws UnsupportedEncodingException {
		int ckey_length = 4;
		String defaultCharset = "UTF-8";
		
		key = md5(key, defaultCharset);
		
		// 密匙a會參與加解密
		String keya = md5(key.substring(0, 16), defaultCharset);
		// 密匙b會用來做數據完整性驗證
		String keyb = md5(key.substring(16, 32), defaultCharset);
		
		String keyc;
		
		// 密匙c用於變化生成密文
		keyc = "";
		if(ckey_length > 0) {
			if("DECODE".equals(operation)) {
				keyc = str.substring(0, ckey_length);
			}else{
				String md5_time = md5(microtime(), defaultCharset);
				int start = md5_time.length() - ckey_length;
				keyc = md5_time.substring(start, start+ckey_length);
			}
		}
		// 參與運算的密匙
		String cryptkey = keya + md5(keya + keyc, defaultCharset);
		
		String strbuf;
		
		if("DECODE".equals(operation)) {
			str = str.substring(ckey_length);
			strbuf = decode(str);
		}else{
			expiry = expiry > 0 ? expiry + time() : 0;
			String tmpstr = expiry + "";
			if(tmpstr.length() >= 10) {
				str = tmpstr.substring(0, 10) + md5(str + keyb, defaultCharset).substring(0, 16) + str;
			}else{
				int count = 10 - tmpstr.length();
				for (int i = 0; i < count; i++) {
					tmpstr = "0"+tmpstr;
				}
				str = tmpstr + md5(str + keyb, defaultCharset).substring(0, 16) + str;
			}
			strbuf = str;
		}
		int[] box = new int[256];
		for (int i = 0; i < box.length; i++) {
			box[i] = i;
		}
		
		char[] rndkey = new char[256];
		for (int i = 0; i < rndkey.length; i++) {
			rndkey[i] = cryptkey.charAt(i % cryptkey.length());
		}
		
		// 用固定算法打亂密匙薄,增加隨機性,好像很複雜,實際上並不會增加密文的強度
		for (int i = 0, j = 0; i < 256; i++) {
			j = (j + box[i] + rndkey[i]) % 256;
			int tmp = box[i];
			box[i] = box[j];
            box[j] = tmp;
		}
		
		// 核心加密解密部分
		String s = "";
		char[] charArray = strbuf.toCharArray();
		for (int a = 0, i = 0, j = 0; i < charArray.length; i++) {
			a = (a + 1) % 256;
			j = (j + box[a]) % 256;
			int tmp = box[a];
			box[a] = box[j];
			box[j] = tmp;
			// 從密匙薄得出密匙進行異或,再轉成字符
			char c = (char) (charArray[i]^box[(box[a] + box[j]) % 256]);
			s += c + "";
		}
		if("DECODE".equals(operation)) {
			int prefix;
			// 與js差異之一
			try{
				prefix = Integer.parseInt(s.substring(0, 10));
			}catch(Exception e) {
				prefix = 0;
			}
			if(("0000000000".equals(s.substring(0, 10)) || prefix - time() > 0) && md5(s.substring(26) + keyb, defaultCharset).substring(0, 16).equals(s.substring(10, 26))) {
				s = s.substring(26);
			}else{
				s = "";
			}
		}else{
			s = encode(s);
			s = s.replaceAll("=", "");
			s = keyc + s;
		}
		return s;
	}
	
	/**
	 * 32位 md5 加密 
	 * @author Z
	 * @param s
	 * @param charset
	 * @return String 加密後的32位長度的字符串
	 */
	public final static String md5(String s, String charset) {
        try {
            byte[] btInput = s.getBytes(charset);
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < md.length; i++) {
                int val = ((int) md[i]) & 0xff;
                if (val < 16){
                    sb.append("0");
                }
                sb.append(Integer.toHexString(val));
            }
            return sb.toString();
        } catch (Exception e) {
            return null;
        }
    }
	
	/**
	 * 返回當前微秒數
	 * @return
	 */
	public static String microtime() {
		long unixtime_ms = new Date().getTime();
		int sec = (int) unixtime_ms / 1000;
		return (unixtime_ms - (sec * 1000)) / 1000 + " " + sec;
	}
	
	/**
	 * 獲取當前Unix時間秒數
	 * @return
	 */
	public static int time() {
		long time = new Date().getTime()/1000;
		return (int) time;
	}
	
	/**
	 * base64編碼(純英文)
	 * @param str
	 * @return
	 */
	private static String encode(String str) {
		String out = "";
		int i = 0;
		int len = 0;
		int c1, c2, c3;
		len = str.length(); 
		while(i < len) {
			c1 = (int) str.charAt(i++) & 0xff; 
		    if(i == len) 
		    { 
		        out += base64EncodeChars.charAt(c1 >> 2); 
		        out += base64EncodeChars.charAt((c1 & 0x3) << 4); 
		        out += "=="; 
		        break; 
		    } 
		    c2 = (int) str.charAt(i++); 
		    if(i == len) 
		    {
		        out += base64EncodeChars.charAt(c1 >> 2); 
		        out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 
		        out += base64EncodeChars.charAt((c2 & 0xF) << 2); 
		        out += "="; 
		        break; 
		    } 
		    c3 = (int) str.charAt(i++); 
		    out += base64EncodeChars.charAt(c1 >> 2); 
		    out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 
		    out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); 
		    out += base64EncodeChars.charAt(c3 & 0x3F); 
		}
		
		return out;
	}

	/**
	 * base64解碼(純英文)
	 * @param str
	 * @return
	 */
	private static String decode(String str) {
		int[] base64DecodeChars = new int[]{ 
			    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
			    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
			    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 
			    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 
			    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 
			    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 
			    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 
			    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1}; 
		int c1, c2, c3, c4;
		int i = 0;
		int len = 0;
		String out = "";
		len = str.length();
		while(i < len) { 
	    /* c1 */ 
	    do { 
	        c1 = base64DecodeChars[(int) str.charAt(i++) & 0xff]; 
	    } while(i < len && c1 == -1); 
	    if(c1 == -1) 
	        break; 

	    /* c2 */ 
	    do { 
	        c2 = base64DecodeChars[(int) str.charAt(i++) & 0xff]; 
	    } while(i < len && c2 == -1); 
	    if(c2 == -1) 
	        break; 
	    out += (char) ((c1 << 2) | ((c2 & 0x30) >> 4)) + ""; 

	    /* c3 */ 
	    do { 
	        c3 = ((int) str.charAt(i++) & 0xff); 
	        if(c3 == 61) 
	        return out; 
	        c3 = base64DecodeChars[c3]; 
	    } while(i < len && c3 == -1); 
	    if(c3 == -1) 
	        break; 
	    out += (char) (((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)) + ""; 

	    /* c4 */ 
	    do {
	    	// 與js差異之二
	    	try{
	    		c4 = (int) str.charAt(i++) & 0xff;
		        if(c4 == 61) 
		        return out; 
		        c4 = base64DecodeChars[c4]; 
	    	} catch (Exception e) {
	    		c4 = -1;
	    	}
	    } while(i < len && c4 == -1); 
	    if(c4 == -1) 
	        break; 
	    out += (char) (((c3 & 0x03) << 6) | c4) + ""; 
	    } 
	    return out;
	}
	
	public static String ucAuthcode(String str, String operation, String key) throws UnsupportedEncodingException {
		return ucAuthcode(str, operation, key, 0);
	}

	public static String ucAuthcode(String str, String operation) throws UnsupportedEncodingException {
		return ucAuthcode(str, operation, "", 0);
	}
}




java版(小範圍內測通過)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章