JavaScript 网页编程从入门到精通 (清华社"视频大讲堂"大系·网络开发视频大讲堂)
上QQ阅读APP看书,第一时间看更新

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编码显示,则可以输入“&#20013; ”。因此,把文本转换为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编码字符串
          “&#106; &#97; &#118; &#97; &#115; &#99; &#114; &#105; &#112; &#116;
          &#20013; &#22269; ”
      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);                                     //返回字符串
      “&#106; &#97; &#118; &#97; &#115; &#99; &#114; &#105; &#112; &#116;
      &#20013; &#22269; ”
      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中国"