7.4 加密和解密
加密和解密是字符串的高级操作,字符串加密解密算法千奇百怪,本节将结合常规字符串加密和解密方法进行讲解,帮助用户初步认识加密和解密的实现途径和设计思路。
7.4.1 JavaScript编码和解码
为了方便开发,JavaScript在Global对象中预定义了6个编码和解码的方法,如表7-6所示。对于一些简单的信息,直接调用这些方法即可快速达到加密和解密的目的。
表7-6 JavaScript预定义编码和解码方法
1.escape()和unescape()方法
escape()是不完全编码的方法,它仅能将字符串中某些字符替换为十六进制的转义序列。具体说,就是除了ASCII字母、数字和标点符号(如@、*、_、+、-、\、(、)之外,所有字符都被转换为%xx或%uxxxx(x表示十六进制的数字)的转义序列。从\u0000到\u00ff的Unicode字符由转义序列%xx替代,其他所有Unicode字符由%uxxxx序列替代。
【示例1】下面的代码使用了escape()方法编码字符串。
var s = "javascript中国"; s = escape(s); alert(s); //返回字符串"javascript%u4E2D%u56FD"
可以使用该方法对Cookie值进行编码,避免与其他约定字符发生冲突,因为Cookie包含的标点符号是有限制的。
与escape()方法对应的是unescape()方法,它能够对escape()编码的字符串解码。该函数是通过找到形式为%xx和%uxxxx的字符序列(这里x表示十六进制的数字),使用Unicode字符\uOOxx和\uxxxx替换这样的字符序列进行解码的。
【示例2】下面的代码使用了unescape()方法解码被escape()方法编码的字符串。
var s = "javascript中国"; s=escape(s); //Unicode编码 alert(s); //返回字符串"javascript%u4E2D%u56FD" s=unescape(s); //Unicode解码 alert(s); //返回字符串"javascript中国"
【示例3】这种被解码的代码是不能够直接运行的,读者可以使用eval()方法来执行它。
var s=escape('alert("javascript中国"); ');//编码脚本 var s=unescape(s); //解码脚本 eval(s); //执行被解码的脚本
2.encodeURI()和decodeURI()方法
虽然ECMAScript 1.0版本标准化了escape()和unescape()方法,但是ECMAScript 3.0版本反对使用它们,提倡使用encodeURI()和encodeURIComponent()方法代替escape()方法,使用decodeURI()和decodeURIComponent()方法代替unescape()方法。
【示例4】encodeURI()方法能够把URI字符串进行转义处理。
var s = "javascript中国"; s = encodeURI(s); alert(s); //返回字符串"javascript%E4%B8%AD% E5%9B%BD"
通过结果可以看到,encodeURI()方法与escape()方法编码结果是不同的。但是,与escape()方法相同,对于ASCII的字母、数字和ASCII标点符号(如-、_、.、! 、~、*、'、(、))来说,也不会被编码。
相对来说,encodeURI()方法会更加安全。它能够将字符转换为UTF-8编码字符,然后用十六进制的转义序列(形式为%xx)对生成的一个、两个或3个字节的字符编码。在这种编码模式中,ASCII字符由一个%xx转义字符替换,在\u0080到\u07ff之间编码的字符由两个转义序列替换,其他的16位Unicode字符由3个转义序列替换。使用decodeURI()方法可以对上面结果进行解码操作。
【示例5】下面的代码演示了如何对URL字符串进行编码和解码操作。
var s = "javascript中国"; s=encodeURI(s); //URI编码 alert(s); //返回字符串"javascript%E4%B8%AD% E5%9B%BD" s=decodeURI(s); //URI解码 alert(s); //返回字符串"javascript中国"
在ECMAScript 3之前,可以使用escape()和unescape()方法执行相似的编码解码操作。
3.encodeURIComponent()和decodeURIComponent()
encodeURI()仅是一种简单的URI字符编码方法,如果使用该方法编码URI字符串,必须确保URI组件(如查询字符串)中不含有URI分隔符。如果组件中含有这些分隔符,就必须使用encodeURIComponent()方法分别对各个组件编码。
encodeURIComponent()方法与encodeURI()方法不同。它们的主要区别就在于,encodeURIComponent()方法假定参数是URI的一部分,例如,协议、主机名、路径或查询字符串。因此,它将转义用于分隔URI各个部分的标点符号。而encodeURI()方法仅把它们视为普通的ASCII字符,并没有转换。
【示例6】下面的代码比较了URL字符串被encodeURIComponent()方法编码前后的不同。
var s = "http://www.mysite.cn/navi/search.asp? keyword=URI"; a = encodeURI(s); document.write(a); document.write("<br />"); b = encodeURIComponent(s); document.write(b);
输出显示为:
http://www.mysite.cn/navi/search.asp? keyword=URI http%3A%2F%2Fwww.mysite.cn%2Fnavi%2Fsearch.asp%3Fkeyword%3DURI
第一行字符串是encodeURI()方法编码的结果,而第二行字符串是encodeURIComponent()方法编码的结果。同encodeURI()方法一样,encodeURIComponent()方法对于ASCII的字母、数字和部分标点符号(如-、_、.、! 、~、*、'、(、))不编码。而对于其他字符(如/、:、#)这样用于分隔URI各种组件的标点符号,都由一个或多个十六进制的转义序列替换。
【示例7】对于encodeURIComponent()方法编码的结果进行解码,可以使用decodeURIComponent()方法来快速实现:
var s = "http://www.mysite.cn/navi/search.asp? keyword=URI"; b = encodeURIComponent(s); b = decodeURIComponent(b) document.write(b);
7.4.2 案例:Unicode编码
所谓Unicode编码就是根据字符在Unicode字符表中的编号对字符进行简单的编码,从而实现对信息进行加密。例如,字符“中”的Unicode编码为20013,如果在网页中使用Unicode编码显示,则可以输入“中 ”。因此,把文本转换为Unicode编码之后在网页中显示,能够实现加密信息的效果。
String对象提供了预定charCodeAt()方法,该方法能够把指定的字符串转换为Unicode编码。所以,该方法的设计思路是,利用replace()方法逐个字符地进行匹配、编码转换,最后返回以网页显示的编码格式的信息。
【示例】下面的代码利用了字符串的charCodeAt()方法对字符串进行自定义编码。
var toUnicode = String.prototype.toUnicode = function(){ //对字符串进行编码方法 var _this = arguments[0] || this; //判断是否存在参数,如果存在则使用静态方法调用参数值,否则作为字符串对象的 //方法来处理当前字符串对象 function f(){ //定义替换文本函数 return "&#" + arguments[0].charCodeAt(0) + "; "; //以网页编码格式显示被编码的字符串 } return _this.replace(/[^\u00-\uFF]|\w/gmi, f); //使用replace()方法执行匹配、替换操作 };
简单说明一下,toUnicode()是一个全局静态方法,同时也是一个String对象的方法,为此在函数体内首先判断方法的参数值,以决定执行操作的方式。在replace()字符替换方法中,借助文本替换函数来完成被匹配字符的转码操作。如下所示:
var s="javascript中国"; //定义字符串直接量 s=toUnicode(s); //以静态方式调用toUnicode()方法 alert(s); //返回Unicode编码字符串 “j a v a s c r i p t 中 国 ” document.write(s); //在网页中显示字符串为“javascript中国”
以String对象方法调用的形式如下:
var s = "javascript中国"; s=s.toUnicode(); //以String对象的方法调用toUnicode()方法 alert(s); document.write(s);
7.4.3 案例:Unicode解码
与Unicode编码操作相对应,可以设计Unicode解码方法,设计思路和代码实现基本相同。
【示例1】下面的代码使用了字符串的charCodeAt()方法定义了一个字符串解码函数。
var fromUnicode = String.prototype.fromUnicode = function(){ //对Unicode编码进行解码操作 var _this = arguments[0] || this; //判断是否存在参数,如果存在则使用静态方法调用参数值,否则作为字符串对象的方法来处理当前字符串对象 function f(){ //定义替换文本函数 return String.fromCharCode(arguments[1]); //把第一个子表达式值(Unicode编码)转换为字符 } return_this.replace(/&#(\d*); /gmi, f); //使用replace()匹配并替换Unicode编码为字符 };
关于Unicode编码和解码操作,应该注意正则表达式的设计,对于ASCII字符来说,其Unicode编码在\u00~\uFF(十六进制)之间;而对于双字节的汉字来说,则应该是大于\uFF编码的字符集,因此在判断时要考虑到不同的字符集合。
【示例2】利用上面定义的方法尝试把被toUnicode()方法编码的字符进行解码:
var s="javascript中国"; //定义字符串直接量 s=s.toUnicode(); //对字符串进行Unicode编码 alert(s); //返回字符串 “j a v a s c r i p t 中 国 ” s=s.fromUnicode(); //对被编码的字符串进行解码 alert(s); //返回字符串“javascript中国”
7.4.4 综合实战:自定义加密和解密方法
加密和解密的关键是算法设计,通俗地说就是设计一个函数式,输入字符串之后,经过复杂的函数处理,返回一组看似杂乱无章的字符串。对于常人来说,输入的字符串是可以阅读的信息,但是被函数打乱或编码之后显示的字符串就变成无意义的垃圾信息。要想把这些垃圾信息变为有意义的信息,还需要使用相反的算法把它们逆转回来。
【示例】假设把字符串“中”进行自定义加密。可以考虑利用JavaScript预定义的charCodeAt()方法获取该字符的Unicode编码:
var s = "中"; var b=s.charCodeAt(0); //返回值20013
然后以36为倍数不断取余数:
b1=b % 36; //返回值33,求余数 b=(b-b1)/36; //返回值555,求倍数 b2=b % 36; //返回值15,求余数 b=(b-b2)/36; //返回值15,求倍数 b3=b % 36; //返回值15,求余数
那么不断求得的余数,可以通过下面公式反算出原编码值:
var m = b3 * 36 * 36 + b2 * 36 + b1; //返回值20013,反求字符“中”的编码值
有了这种算法,就可以实现字符与加密数值之间的相互转换。如果定义一个密钥:
var key = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
把余数定位到密钥中某个下标值相等的字符上,这样就实现了加密效果。反过来,如果知道某个字符在密钥中的下标值,然后反算出被加密字符的Unicode编码值,就可以逆推出被加密字符的原信息。
我们设定密钥是以36个不同的数值和字母组成的字符串。不同密钥,加密解密的结果是不同的,加密结果以密钥中的字符作为基本元素。
具体加密字符串的方法如下:
var toCode=function(str){ //加密字符串 var key = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //定义密钥、36个字母和数字 var l=key.length; //获取密钥的长度 var a=key.split(""); //把密钥字符串转换为字符数组 var s="", b, b1, b2, b3; //定义临时变量 for(var i=0; i<str.length; i++){ //遍历字符串 b=str.charCodeAt(i); //逐个提取每个字符,并获取Unicode编码值 b1=b % l; //求Unicode编码值的余数 b=(b-b1)/l; //求最大倍数 b2=b % l; //求最大倍数的余数 b=(b-b2)/l; //求最大倍数 b3=b % l; //求最大倍数的余数 s+=a[b3]+a[b2]+a[b1]; //根据这些余数值映射到密钥中对应下标值的字符 } return s; //返回这些映射的字符 }
解密字符串的方法如下:
var fromCode=function(str){ //解密toCode()方法加密的字符串 var key = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //定义密钥、36个字母和数字 var l=key.length; //获取密钥的长度 var b, b1, b2, b3, d=0, s; //定义临时变量 s = new Array(Math.floor(str.length / 3)) //计算加密字符串可能包含的字符数,并定义数组 b=s.length; //获取数组的长度 for(var i = 0; i < b; i ++ ){ //以数组的长度为循环次数,遍历加密字符串 b1 = key.indexOf(str.charAt(d)) //截取周期内第一个字符,并计算它在密钥中的下标值 d ++ ; b2 = key.indexOf(str.charAt(d)) //截取周期内第二个字符,并计算它在密钥中的下标值 d ++ ; b3 = key.indexOf(str.charAt(d)) //截取周期内第三个字符,并计算它在密钥中的下标值 d ++ ; s[i] = b1 * l * l + b2 * l + b3 //利用上面下标值(约数值),反算被加密字符的Unicode编码值 } b=eval("String.fromCharCode("+s.join(', ')+")"); //调用fromCharCode()方法算出对应的字符串 return b; //返回被解密的字符串 }
最后,利用上面自定义的加密和解密方法来进行试验:
var s="javascript中国"; //字符串直接量 s=toCode(s); //加密字符串 alert(s); //返回字符串"02Y02P03A02 P03702R03602X034038FFXH6L" s=fromCode(s); //解密被加密的字符串 alert(s); //返回字符串"javascript中国"