设计字符加密启迪(含各种编码函数的说明)

1,791 阅读9分钟

理解

所谓加密,就是通过设计算法来把字符串转化为看似杂乱无章的文本,让人不容易看穿想表达的意思。

而解密就是按照设计的算法,反推出原本的文本内容。

在这个算法,就是开发者自己所要设计的,既可简单又可复杂,全凭开发者自己的设计能力。

所需工具

而对js字符串进行加密,我们可以利用几个工具(方法)来辅助你进行字符串的转化,反复转化几次就能迷惑别人了。

但是在此之前,我希望你能对Unicode、ASCII、utf-8以及base64等涉及编码方面的,有所了解。可阅读此篇快速了解常见的字符集与编码方案的认识

编码,可以把浅显易懂的字符转化为难以直观理解的数字或者杂乱无章的字符,即能达到让别人摸不着头脑的加密效果了。

但是,单纯的利用Unicode或编码进行的字符串转化,这种形式的加密,太简单了,毕竟它们都是有广为人知的固定规则的,很容易让别人识破或反转化,毕竟,都是有一一对应的关系在那里。

因此,要巧妙地混用它们,设计出一个相对复杂的算法,就能达到基础的加密效果了。

对此,我们先认识一些关于字符串转化的相关函数方法

fromCharCode

按照UTF-16编码,把对应的数字码值转化对应的字符。是字符串的静态方法。

语法:

String.fromCharCode(n1[, n2, ...n])

参数为依据UTF-16编码的码值,任何进制表示都行,如十进制或十六进制

返回值为按码值顺序排列组合起来的对应字符形成的字符串,如

String.fromCharCode(68)
// 返回字符'D',D对应的Unicode就是68
String.fromCharCode(100)
// 返回字符'd',d对应的Unicode就是100
String.fromCharCode(68, 100)
// 返回字符串'Dd'

需要注意的是,UTF-16编码是依据Unicode编码的一种中间转化格式方案,在码值0~65535(0x0000 ~ 0xFFFF)范围内,即BMP零号平面下,UTF-16的码值和Unicode的码值代表的字符是一样的,都是1个16bit二进制代表一个字符;但是超出65535外的码值,UTF-16是用两个16bit二进制来表示一个字符,即用两个码值表示一个字符,称两个码值为代理对,这是与Unicode不同的。

所以该方法的参数,如果要转换超出65535码值外的字符,则参数是代理对,而不是直接的Unicode码值。如果用了BMP以上的码值,会直接对大于16bit的二进制高位部分进行截断处理,因为参数只能是16bit表示一个字符,虽然不会报错,但是明显输出结果变了,不是预期的结果。

// 如U+1F303,在Unicode里1F303是一个独立的码值,表示"Night with Stars"这个字符🌃
// 但是在UTF-16里,是用两个码值来表示该字符(0xD83C,0xDF03)
// 返回结果为 🌃
String.fromCharCode(0xD83C, 0xDF03)

// 超出0xFFFF,做二进制高位截断,会变成和
// String.fromCharCode(0xF303)的结果
String.fromCharCode(0x1F303)

charCodeAt

fromCharCode目的相反,它是把字符转化为UTF-16码值。但他它不同的是,他是实例方法,执行者是字符串对象实例。

语法:

string.charCodeAt(index)

参数为该字符串的某个字符所在位置的下标(取值范围是0 ~ string.length-1)

返回值

  • 为对应下标的字符的UTF-16码值。
  • 如果参数index不在取值范围内的数字那么返回值为NaN;如果是非数字类型或者空,那么默认是0;
  • 从上面的fromCharCode了解到,一个字符可以是用代理对表示的,那么这种情况下我们要知道,这种字符的length2而不是1,所以index的取值范围是0~1,0时返回代理对的第一个,1为第二个,不传时默认是0;
// '🌃'的代理对是55356,57091
'🌃'.length // 2
'🌃'.charCodeAt(0) // 返回55356
'🌃'.charCodeAt(1) // 返回57091
'i am a teacher'.charCodeAt(2);
// 返回第三个字符a的Unicode值,97

fromCodePoint

从上面的fromCharCode了解到,它是有缺陷的,因为可能需要用到代理对来进行转化,这就涉及到需要进行换算出代理对这么一个麻烦的过程了,为了避免进行不必要的计算,ES 2015有一个新的方法fromCodePoint

从名字都能直观了解到,它是直接基于码值进行的转化,即直接根据Unicode的码值来,而不必用UTF-16的代理对来表示一个字符。静态方法

语法:

String.fromCodePoint(n1[, n2, ...n]);

参数为Unicode的码值,即数字,什么进制都可以。

返回值为按照参数的顺序Unicode码值对应的字符拼接的字符串。

还是以字符'🌃'来举例说明,该字符在Unicode里用U+1F303表示,码值为0x1F303;在UTF-16里,是用代理对(0xD83C,0xDF03)表示

// 该方法就要用代理对转换
String.fromCharCode(0xD83C, 0xDF03)

// 该方法就可以直接用码值转换
String.fromCodePoint(0x1F303)

fromCodePointfromCharCode的区别就在于前者可用Unicode码值直接转换,但是IE不支持,兼容没后者好。

codePointAt

fromCodePoint相对应的,与charCodeAt相对比的,codePointAt的用途也很明显。

把字符转化Unicode码值,实例方法,同样是ES 2015的新方法。

语法:

string.codePointAt(index)

参数 为该字符串的某个字符所在位置的下标(取值范围是0 ~ string.length-1),要注意,如果字符是可以用代理对表示的,即65535码值外的字符,则直接传该字符所在下标的第一个位置,返回的就是该字符的实际Unicode码值,传第二个位置的话,就是代理对里的第二个码值。

'🌃'.codePointAt(0) // 返回127747,为该字符Unicode码值
'🌃'.codePointAt(1) // 返回57091,为该字符代理对(55356, 57091)的第二个码值

返回值

  • 为对应下标的字符的Unicode码值。
  • 如果参数index不在取值范围内(数字或其他类型),那么返回值为undefined
  • 但是如果是''nullundefinedfalse或不传,那么默认是0(chrome下的表现);

同样,IE不支持。

encodeURI && encodeURIComponent

关于这两个函数的说明,可以查看我这边文章URI编码的两方法异同与场景

btoa && atob

btoa,即binary to ascii,同理,atob就是ascii to binary。都是window对象下的方法。IE9及以下不支持。

从名字上大体了解基本作用,btoa作用就是把字符转化其ASCII值base64编码后的字符。而atob则是它的逆运算,解码base64编码后的字符,即还原原字符。

这里主要说btoa,因为atob很简单,就是把btoa的结果当做参数执行就好了。以下内容全是针对btoa所说。

语法

window.btoa(stringToEncode)

它会把参数的每一个字符都被视为一个二进制数据字节来进行转换。但是该方法所能识别的的码位是基于ASCII来的,所以超出 0x00 ~ 0xFF 范围,则会引发 InvalidCharacterError 异常。所以如果字符是Unicode字符就会报错了

参数如果是数字类型,那也会当成是String类型处理,如100,即对1字符,0字符,0字符分别base64编码处理。

很明显,该方法不能针对Uncode字符进行编码,即你不能对中文编码啦。

有对其进行增强的处理,以支持Unicode的编码:

// ucs-2 string to base64 encoded ascii
function utoa(str) {
    return window.btoa(encodeURIComponent(str));
}
// base64 encoded ascii to ucs-2 string
function atou(str) {
    return decodeURIComponent(window.atob(str));
}
// Usage:
utoa('✓ à la mode'); // 4pyTIMOgIGxhIG1vZGU=
atou('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

小结

介绍了好几种方法,先简单分类总结下,避免混淆。

  • fromCharCode & charCodeAt 是针对UTF-16编码的;
  • fromCodePoint & codePointAt是针对Unicode编码的;
  • encodeURI & encodeURIComponent是针对UTF-8编码的;
  • btoa & atob是针对base64编码的

模板示例

这里举个简单的例子,大家可以从这里拿到点启发,但是不太建议直接套用,还是自己动手做点改造,这样比较安全一点,还可以自己设计更复杂点。

例子仅为为你取得启发之用。

这里设计的算法比较简单:

我们针对要进行加密的字符串,将其每个字符先转化为Unicode值,然后基于该值做一些运算,然后得到一个新的数字,再将该数字转化为Unicode对应的字符。这样,一个新的字符串诞生了。为了更加复杂点,对其再做编码工作。

// 加密方法
function encryptChar(target) {
    let result = '';
    for (let i = 0, j = 0; j < target.length; j++, i++) {
        let code = target.codePointAt(j);
        result += String.fromCodePoint(code + i + j);
        // 超出BMP的码值,是两个码值表示一个字符
        if (code > 65535) { j += 1; }
    }
    return encodeURIComponent(result);
}

// 解密方法
function decryptChar(target) {
    let newTarget = decodeURIComponent(target);
    let result = '';
    for (let i = 0, j = 0; j < newTarget.length; j++, i++) {
        let code = newTarget.codePointAt(j);
        result += String.fromCodePoint(code - i - j);
        // 超出BMP的码值,是两个码值表示一个字符
        if (code > 65535) {
            j += 1;
        }
    }
    return result;
}

这里跟很多网上的资料(很多都是复制粘贴的吧?)不同,这里采用的是codePointAtfromCodePoint,而不是charCodeAtfromCharCode,因为后者不能针对Unicode进行编码。那么如果把范围考虑到Unicode字符的话,则会出现代理对表示一个字符的情况,就是该字符的length值为2,所以做循环的处理字符编码时,要用j来做实际字符的Unicode码值工作,codePointAt转化BMP外的字符传参为该字符所在下标的第一个即可得到Unicode码值,而i仅仅是表示字符的个数。

由此得出的方案,比网上的资料可靠多了(我自己也查了不少,来来去去都是那样,复制粘贴),起码这里弥补了它们的缺点所在。

总结

看了上述例子。你大概知道怎么混用以上的几个工具了。你可以设计出更加复杂的算法,各种运算加减乘除取余之类的。

建议还是好好消化一些编码方面的知识,以及我上面罗列的几个方法的优缺点,对你会有更大帮助哦。

对你有帮助的请点赞支持~

未经允许,请勿私自转载