/*
|
svn: http://tc-svn.tencent.com/pub/pub_TEAJS_rep/TEAJS_proj/trunk/tea.js
|
QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
|
我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块 f表示加密算法,d表示解密算法
|
那么从plain得到crypt的过程是: crypt = f(plain ^ preCrypt) ^ prePlain
|
所以,从crypt得到plain的过程自然是 plain = d(crypt ^ prePlain) ^ preCrypt
|
此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数 填充的字节数与原始明文长度有关,填充的方法是:
|
------- 消息填充算法 -----------
|
a = (明文长度 + 10) mod 8
|
if(a 不等于 0) a = 8 - a;
|
b = 随机数 & 0xF8 | a; 这个的作用是把a的值保存了下来, a的值小于8
|
plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
|
plain[1..a+2] = 随机数 & 0xFF; 这里用随机数填充明文的第1到第a+2个字节
|
plain[a+3..a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
|
plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
|
------- 消息填充算法 ------------
|
@author 马若劼
|
@author notXX
|
*/
|
var base64 = require("base64.js");
|
var __key = '',
|
__pos = 0,
|
__plain = [],
|
__prePlain = [],
|
__cryptPos = 0, // 当前密文块位置
|
__preCryptPos = 0, // 上一个密文块位置
|
__out = [], // 保存加密/解密的输出
|
__cipher = [], // 输出的密文
|
/*用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的,
|
但是最开始的8个字节没有反馈可用,所有需要标明这种情况*/
|
__header = true;
|
function __rand() {
|
return Math.round(Math.random()*0xffffffff);
|
}
|
/**
|
* 将数据转化为无符号整形
|
*/
|
function __getUInt(data, offset, len) {
|
if (!len || len > 4)
|
len = 4;
|
var ret = 0;
|
for (var i=offset; i<offset+len; i++) {
|
ret <<= 8;
|
ret |= data[i];
|
}
|
return (ret & 0xffffffff) >>> 0; // 无符号化
|
}
|
/**
|
把整形数据填充到数组里,要注意端序
|
*/
|
function __intToBytes(data, offset, value) {
|
data[offset+3] = (value >> 0) & 0xff;
|
data[offset+2] = (value >> 8) & 0xff;
|
data[offset+1] = (value >> 16) & 0xff;
|
data[offset+0] = (value >> 24) & 0xff;
|
}
|
function __bytesInStr(data) {
|
if (!data) return "";
|
var outInHex = "";
|
for (var i=0; i<data.length; i++) {
|
var hex = Number(data[i]).toString(16);
|
if (hex.length == 1)
|
hex = "0" + hex;
|
outInHex += hex;
|
}
|
return outInHex;
|
}
|
function __bytesToStr(data) {
|
var str = "";
|
for (var i=0; i<data.length; i+=2) // 输入的16进制字符串
|
str += String.fromCharCode(parseInt(data.substr(i, 2), 16));
|
return str;
|
}
|
function __strToBytes(str, unicode) {
|
if (!str) return "";
|
if (unicode) str = utf16ToUtf8(str);
|
|
var data = [];
|
for (var i=0; i<str.length; i++)
|
data[i] = str.charCodeAt(i);
|
return __bytesInStr(data);
|
}
|
|
//UTF-16转UTF-8
|
function utf16ToUtf8(s){
|
var i, code, ret = [], len = s.length;
|
for(i = 0; i < len; i++){
|
code = s.charCodeAt(i);
|
if(code > 0x0 && code <= 0x7f){
|
//单字节
|
//UTF-16 0000 - 007F
|
//UTF-8 0xxxxxxx
|
ret.push(s.charAt(i));
|
}else if(code >= 0x80 && code <= 0x7ff){
|
//双字节
|
//UTF-16 0080 - 07FF
|
//UTF-8 110xxxxx 10xxxxxx
|
ret.push(
|
//110xxxxx
|
String.fromCharCode(0xc0 | ((code >> 6) & 0x1f)),
|
//10xxxxxx
|
String.fromCharCode(0x80 | (code & 0x3f))
|
);
|
}else if(code >= 0x800 && code <= 0xffff){
|
//三字节
|
//UTF-16 0800 - FFFF
|
//UTF-8 1110xxxx 10xxxxxx 10xxxxxx
|
ret.push(
|
//1110xxxx
|
String.fromCharCode(0xe0 | ((code >> 12) & 0xf)),
|
//10xxxxxx
|
String.fromCharCode(0x80 | ((code >> 6) & 0x3f)),
|
//10xxxxxx
|
String.fromCharCode(0x80 | (code & 0x3f))
|
);
|
}
|
}
|
|
return ret.join('');
|
}
|
|
function __encrypt(data) {
|
__plain = new Array(8);
|
__prePlain = new Array(8);
|
__cryptPos = __preCryptPos = 0;
|
__header = true;
|
__pos = 0;
|
var len = data.length;
|
var padding = 0;
|
|
__pos = (len + 0x0A) % 8;
|
if (__pos != 0)
|
__pos = 8 - __pos;
|
__out = new Array(len + __pos + 10);
|
__plain[0] = ((__rand() & 0xF8) | __pos ) & 0xFF;
|
|
for (var i=1; i<=__pos; i++)
|
__plain[i] = __rand() & 0xFF;
|
__pos++;
|
|
for (var i=0; i<8; i++)
|
__prePlain[i] = 0;
|
|
padding = 1;
|
while (padding <= 2) {
|
if (__pos < 8) {
|
__plain[__pos++] = __rand() & 0xFF;
|
padding++;
|
}
|
if (__pos == 8)
|
__encrypt8bytes();
|
}
|
|
var i = 0;
|
while (len > 0) {
|
if (__pos < 8) {
|
__plain[__pos++] = data[i++];
|
len--;
|
}
|
if (__pos == 8)
|
__encrypt8bytes();
|
}
|
|
padding = 1;
|
while (padding <= 7) {
|
if (__pos < 8) {
|
__plain[__pos++] = 0;
|
padding++;
|
}
|
if (__pos == 8)
|
__encrypt8bytes();
|
}
|
|
return __out;
|
}
|
function __decrypt(data) {
|
var count = 0;
|
var m = new Array(8);
|
var len = data.length;
|
__cipher = data;
|
|
if (len % 8 != 0 || len < 16)
|
return null;
|
/* 第一个8字节,加密的时候因为prePlain是全0,所以可以直接解密,得到消息的头部,
|
关键是可以得到真正明文开始的位置
|
*/
|
__prePlain = __decipher(data);
|
__pos = __prePlain[0] & 0x7;
|
count = len - __pos - 10; // 真正的明文长度
|
if (count < 0)
|
return null;
|
|
// 临时的preCrypt, 与加密时对应,全0的prePlain 对应 全0的preCrypt
|
for (var i=0; i<m.length; i++)
|
m[i] = 0;
|
__out = new Array(count);
|
__preCryptPos = 0;
|
__cryptPos = 8; // 头部已经解密过,所以是8
|
__pos++; // 与解密过程对应,+1
|
|
/* 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
|
因为是解密下一块,所以我们有一个语句 m = data,下一块当然有preCrypt了,我们不再用m了
|
但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
|
所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充*/
|
var padding = 1;
|
while (padding <= 2) {
|
if (__pos < 8) {
|
__pos++;
|
padding++;
|
}
|
if (__pos == 8) {
|
m = data;
|
if (!__decrypt8Bytes())
|
return null;
|
}
|
}
|
|
/* 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
|
注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是data了*/
|
var i=0;
|
while (count != 0) {
|
if (__pos < 8) {
|
__out[i] = (m[__preCryptPos + __pos] ^ __prePlain[__pos]) & 0xff;
|
i++;
|
count--;
|
__pos++;
|
}
|
if (__pos == 8) {
|
m = data;
|
__preCryptPos = __cryptPos - 8;
|
if (!__decrypt8Bytes())
|
return null;
|
}
|
}
|
|
/*
|
明文已经解密完毕了,到这里剩下的只有尾部的填充,应该全是0,如果解密后非0,即出错了,返回null
|
*/
|
for (padding=1; padding<8; padding++) {
|
if (__pos < 8) {
|
if ((m[__preCryptPos + __pos] ^ __prePlain[__pos]) != 0)
|
return null;
|
__pos++;
|
}
|
if (__pos == 8) {
|
m = data;
|
__preCryptPos = __cryptPos;
|
if (!__decrypt8Bytes())
|
return null;
|
}
|
}
|
|
return __out;
|
}
|
function __encrypt8bytes() {
|
for (var i=0; i<8; i++) {
|
if (__header)
|
__plain[i] ^= __prePlain[i];
|
else
|
__plain[i] ^= __out[__preCryptPos + i];
|
}
|
var crypted = __encipher(__plain);
|
for (var i=0; i<8; i++) {
|
__out[__cryptPos+i] = crypted[i] ^ __prePlain[i];
|
__prePlain[i] = __plain[i];
|
}
|
|
__preCryptPos = __cryptPos;
|
__cryptPos += 8;
|
__pos = 0;
|
__header = false;
|
}
|
function __encipher(data) {
|
var loop = 16;
|
var y = __getUInt(data, 0, 4);
|
var z = __getUInt(data, 4, 4);
|
var a = __getUInt(__key, 0, 4);
|
var b = __getUInt(__key, 4, 4);
|
var c = __getUInt(__key, 8, 4);
|
var d = __getUInt(__key, 12, 4);
|
var sum = 0;
|
var delta = 0x9E3779B9 >>> 0;
|
|
while (loop-- > 0) {
|
sum += delta;
|
sum = (sum & 0xFFFFFFFF) >>> 0;
|
y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
|
y = (y & 0xFFFFFFFF) >>> 0;
|
z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
|
z = (z & 0xFFFFFFFF) >>> 0;
|
}
|
var bytes = new Array(8);
|
__intToBytes(bytes, 0, y);
|
__intToBytes(bytes, 4, z);
|
return bytes;
|
}
|
function __decipher(data) {
|
var loop = 16;
|
var y = __getUInt(data, 0, 4);
|
var z = __getUInt(data, 4, 4);
|
var a = __getUInt(__key, 0, 4);
|
var b = __getUInt(__key, 4, 4);
|
var c = __getUInt(__key, 8, 4);
|
var d = __getUInt(__key, 12, 4);
|
var sum = 0xE3779B90 >>> 0;
|
var delta = 0x9E3779B9 >>> 0;
|
|
while (loop-- > 0) {
|
z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
|
z = (z & 0xFFFFFFFF) >>> 0;
|
y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
|
y = (y & 0xFFFFFFFF) >>> 0;
|
sum -= delta;
|
sum = (sum & 0xFFFFFFFF) >>> 0;
|
}
|
|
var bytes = new Array(8);
|
__intToBytes(bytes, 0, y);
|
__intToBytes(bytes, 4, z);
|
return bytes;
|
}
|
function __decrypt8Bytes() {
|
var len = __cipher.length;
|
for (var i=0; i<8; i++) {
|
__prePlain[i] ^= __cipher[__cryptPos + i];
|
}
|
|
__prePlain = __decipher(__prePlain);
|
|
__cryptPos += 8;
|
__pos = 0;
|
return true;
|
}
|
/**
|
* 把输入字符串转换为javascript array
|
*/
|
function __dataFromStr(str, isASCII) {
|
var data = [];
|
if (isASCII) {
|
for (var i=0; i<str.length; i++)
|
data[i] = str.charCodeAt(i) & 0xff;
|
} else {
|
var k = 0;
|
for (var i=0; i<str.length; i+=2) // 输入的16进制字符串
|
data[k++] = parseInt(str.substr(i, 2), 16);
|
}
|
return data;
|
}
|
|
var TEA = {
|
encrypt: function(str, isASCII) {
|
var data = __dataFromStr(str, isASCII);
|
var encrypted = __encrypt(data);
|
return __bytesInStr(encrypted);
|
},
|
enAsBase64: function(str, isASCII) { // output base64 encoded
|
var data = __dataFromStr(str, isASCII);
|
var encrypted = __encrypt(data);
|
var bytes = "";
|
for (var i=0; i<encrypted.length; i++)
|
bytes += String.fromCharCode(encrypted[i]);
|
return base64.encode(bytes);
|
},
|
decrypt: function(str) {
|
var data = __dataFromStr(str, false);
|
var decrypted = __decrypt(data);
|
return __bytesInStr(decrypted);
|
},
|
initkey: function(key, isASCII) {
|
__key = __dataFromStr(key, isASCII);
|
},
|
bytesToStr: __bytesToStr,
|
strToBytes: __strToBytes,
|
bytesInStr: __bytesInStr,
|
dataFromStr: __dataFromStr
|
};
|
|
/**
|
* base64 兼容window.btoa window.atob
|
* if (!window.btoa) window.btoa = base64.encode
|
* if (!window.atob) window.atob = base64.decode
|
*/
|
var base64 = {};
|
base64.PADCHAR = '=';
|
base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
/*
|
base64.getbyte64 = function(s,i) {
|
// This is oddly fast, except on Chrome/V8.
|
// Minimal or no improvement in performance by using a
|
// object with properties mapping chars to value (eg. 'A': 0)
|
var idx = base64.ALPHA.indexOf(s.charAt(i));
|
if (idx == -1) {
|
throw "Cannot decode base64";
|
}
|
return idx;
|
}
|
|
base64.decode = function(s) {
|
// convert to string
|
s = "" + s;
|
var getbyte64 = base64.getbyte64;
|
var pads, i, b10;
|
var imax = s.length
|
if (imax == 0) {
|
return s;
|
}
|
|
if (imax % 4 != 0) {
|
throw "Cannot decode base64";
|
}
|
|
pads = 0
|
if (s.charAt(imax -1) == base64.PADCHAR) {
|
pads = 1;
|
if (s.charAt(imax -2) == base64.PADCHAR) {
|
pads = 2;
|
}
|
// either way, we want to ignore this last block
|
imax -= 4;
|
}
|
|
var x = [];
|
for (i = 0; i < imax; i += 4) {
|
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
|
(getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
|
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
|
}
|
|
switch (pads) {
|
case 1:
|
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6)
|
x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
|
break;
|
case 2:
|
b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
|
x.push(String.fromCharCode(b10 >> 16));
|
break;
|
}
|
return x.join('');
|
}
|
*/
|
|
base64.getbyte = function(s,i) {
|
var x = s.charCodeAt(i);
|
if (x > 255) {
|
throw "INVALID_CHARACTER_ERR: DOM Exception 5";
|
}
|
return x;
|
}
|
|
base64.encode = function(s) {
|
if (arguments.length != 1) {
|
throw "SyntaxError: Not enough arguments";
|
}
|
var padchar = base64.PADCHAR;
|
var alpha = base64.ALPHA;
|
var getbyte = base64.getbyte;
|
|
var i, b10;
|
var x = [];
|
|
// convert to string
|
s = "" + s;
|
|
var imax = s.length - s.length % 3;
|
|
if (s.length == 0) {
|
return s;
|
}
|
for (i = 0; i < imax; i += 3) {
|
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
|
x.push(alpha.charAt(b10 >> 18));
|
x.push(alpha.charAt((b10 >> 12) & 0x3F));
|
x.push(alpha.charAt((b10 >> 6) & 0x3f));
|
x.push(alpha.charAt(b10 & 0x3f));
|
}
|
switch (s.length - imax) {
|
case 1:
|
b10 = getbyte(s,i) << 16;
|
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
padchar + padchar);
|
break;
|
case 2:
|
b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
|
x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
|
alpha.charAt((b10 >> 6) & 0x3f) + padchar);
|
break;
|
}
|
return x.join('');
|
}
|
|
module.exports = TEA;
|