490 lines
16 KiB
TypeScript
490 lines
16 KiB
TypeScript
export default class XXTea
|
|
{
|
|
private static readonly DELTA: number = 0x9E3779B9;
|
|
private static readonly base64EncodeChars: string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
|
private static readonly base64DecodeChars: number[] = [
|
|
-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
|
|
];
|
|
|
|
private static btoa(str: string): string
|
|
{
|
|
let stringLength = str.length;
|
|
let r = stringLength % 3;
|
|
stringLength = stringLength - r;
|
|
let bufferLength = (stringLength / 3) << 2;
|
|
if (r > 0)
|
|
{
|
|
bufferLength += 4;
|
|
}
|
|
let buffer: string[] = new Array(bufferLength);
|
|
let i = 0, j = 0;
|
|
let charCode: number;
|
|
while (i < stringLength)
|
|
{
|
|
charCode = str.charCodeAt(i++) << 16 |
|
|
str.charCodeAt(i++) << 8 |
|
|
str.charCodeAt(i++);
|
|
buffer[j++] = XXTea.base64EncodeChars[charCode >> 18] +
|
|
XXTea.base64EncodeChars[charCode >> 12 & 0x3f] +
|
|
XXTea.base64EncodeChars[charCode >> 6 & 0x3f] +
|
|
XXTea.base64EncodeChars[charCode & 0x3f];
|
|
}
|
|
if (r == 1)
|
|
{
|
|
charCode = str.charCodeAt(i++);
|
|
buffer[j++] = XXTea.base64EncodeChars[charCode >> 2] +
|
|
XXTea.base64EncodeChars[(charCode & 0x03) << 4] +
|
|
"==";
|
|
}
|
|
else if (r == 2)
|
|
{
|
|
charCode = str.charCodeAt(i++) << 8 |
|
|
str.charCodeAt(i++);
|
|
buffer[j++] = XXTea.base64EncodeChars[charCode >> 10] +
|
|
XXTea.base64EncodeChars[charCode >> 4 & 0x3f] +
|
|
XXTea.base64EncodeChars[(charCode & 0x0f) << 2] +
|
|
"=";
|
|
}
|
|
return buffer.join('');
|
|
};
|
|
|
|
private static atob(str: string): string
|
|
{
|
|
let stringLength = str.length;
|
|
if (stringLength % 4 !== 0)
|
|
{
|
|
return '';
|
|
}
|
|
if (/[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+\/\=]/.test(str))
|
|
{
|
|
return '';
|
|
}
|
|
let r: number = 0;
|
|
if (str.charAt(stringLength - 2) == '=')
|
|
{
|
|
r = 1;
|
|
}
|
|
else if (str.charAt(stringLength - 1) == '=')
|
|
{
|
|
r = 2;
|
|
}
|
|
let bufferLength = stringLength;
|
|
if (r > 0)
|
|
{
|
|
bufferLength -= 4;
|
|
}
|
|
bufferLength = (bufferLength >> 2) * 3 + r;
|
|
let outBuffer: string[] = new Array(bufferLength);
|
|
|
|
let i = 0, j = 0;
|
|
while (i < stringLength)
|
|
{
|
|
// c1
|
|
let charCode1 = XXTea.base64DecodeChars[str.charCodeAt(i++)];
|
|
if (charCode1 == -1) break;
|
|
|
|
// c2
|
|
let charCode2 = XXTea.base64DecodeChars[str.charCodeAt(i++)];
|
|
if (charCode2 == -1) break;
|
|
|
|
outBuffer[j++] = String.fromCharCode((charCode1 << 2) | ((charCode2 & 0x30) >> 4));
|
|
|
|
// c3
|
|
let charCode3 = XXTea.base64DecodeChars[str.charCodeAt(i++)];
|
|
if (charCode3 == -1) break;
|
|
|
|
outBuffer[j++] = String.fromCharCode(((charCode2 & 0x0f) << 4) | ((charCode3 & 0x3c) >> 2));
|
|
|
|
// c4
|
|
let charCode4 = XXTea.base64DecodeChars[str.charCodeAt(i++)];
|
|
if (charCode4 == -1) break;
|
|
|
|
outBuffer[j++] = String.fromCharCode(((charCode3 & 0x03) << 6) | charCode4);
|
|
}
|
|
return outBuffer.join('');
|
|
};
|
|
|
|
private static toBinaryString(v: number[], includeLength: boolean): string
|
|
{
|
|
let length = v.length;
|
|
let n = length << 2;
|
|
if (includeLength)
|
|
{
|
|
let m = v[length - 1];
|
|
n -= 4;
|
|
if ((m < n - 3) || (m > n))
|
|
{
|
|
return null;
|
|
}
|
|
n = m;
|
|
}
|
|
|
|
let outBuffer: string[] = new Array(length);
|
|
for (let i = 0; i < length; i++)
|
|
{
|
|
outBuffer[i] = String.fromCharCode(
|
|
v[i] & 0xFF,
|
|
v[i] >>> 8 & 0xFF,
|
|
v[i] >>> 16 & 0xFF,
|
|
v[i] >>> 24 & 0xFF
|
|
);
|
|
}
|
|
let result = outBuffer.join('');
|
|
if (includeLength)
|
|
{
|
|
return result.substring(0, n);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static toUint32Array(bs: string, includeLength: boolean): number[]
|
|
{
|
|
let length = bs.length;
|
|
let n = length >> 2;
|
|
if ((length & 3) !== 0)
|
|
{
|
|
++n;
|
|
}
|
|
let v: number[];
|
|
if (includeLength)
|
|
{
|
|
v = new Array(n + 1);
|
|
v[n] = length;
|
|
}
|
|
else
|
|
{
|
|
v = new Array(n);
|
|
}
|
|
for (let i = 0; i < length; ++i)
|
|
{
|
|
v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
private static int32(i: number): number
|
|
{
|
|
return i & 0xFFFFFFFF;
|
|
}
|
|
|
|
private static mx(sum: number, y: number, z: number, p: number, e: number, k: number[]): number
|
|
{
|
|
return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
|
|
}
|
|
|
|
private static fixk(k: number[]): number[]
|
|
{
|
|
if (k.length < 4) k.length = 4;
|
|
return k;
|
|
}
|
|
|
|
private static encryptUint32Array(v: number[], k: number[]): number[]
|
|
{
|
|
let length = v.length;
|
|
let n = length - 1;
|
|
let y: number, e: number, p: number;
|
|
let z = v[n];
|
|
let sum = 0;
|
|
for (let q = Math.floor(6 + 52 / length) | 0; q > 0; --q)
|
|
{
|
|
sum = XXTea.int32(sum + XXTea.DELTA);
|
|
e = sum >>> 2 & 3;
|
|
for (p = 0; p < n; ++p)
|
|
{
|
|
y = v[p + 1];
|
|
z = v[p] = XXTea.int32(v[p] + XXTea.mx(sum, y, z, p, e, k));
|
|
}
|
|
y = v[0];
|
|
z = v[n] = XXTea.int32(v[n] + XXTea.mx(sum, y, z, n, e, k));
|
|
}
|
|
return v;
|
|
}
|
|
|
|
private static decryptUint32Array(v: number[], k: number[]): number[]
|
|
{
|
|
let length = v.length;
|
|
let n = length - 1;
|
|
let y = v[0];
|
|
let q = Math.floor(6 + 52 / length);
|
|
for (let sum = XXTea.int32(q * XXTea.DELTA); sum !== 0; sum = XXTea.int32(sum - XXTea.DELTA))
|
|
{
|
|
let e = sum >>> 2 & 3;
|
|
let z: number;
|
|
for (let p = n; p > 0; --p)
|
|
{
|
|
z = v[p - 1];
|
|
y = v[p] = XXTea.int32(v[p] - XXTea.mx(sum, y, z, p, e, k));
|
|
}
|
|
z = v[n];
|
|
y = v[0] = XXTea.int32(v[0] - XXTea.mx(sum, y, z, 0, e, k));
|
|
}
|
|
return v;
|
|
}
|
|
|
|
public static utf8Encode(str: string): string
|
|
{
|
|
if (/^[\x00-\x7f]*$/.test(str))
|
|
{
|
|
return str;
|
|
}
|
|
let buf = [];
|
|
let n = str.length;
|
|
for (let i = 0, j = 0; i < n; ++i, ++j)
|
|
{
|
|
let codeUnit = str.charCodeAt(i);
|
|
if (codeUnit < 0x80)
|
|
{
|
|
buf[j] = str.charAt(i);
|
|
}
|
|
else if (codeUnit < 0x800)
|
|
{
|
|
buf[j] = String.fromCharCode(0xC0 | (codeUnit >> 6),
|
|
0x80 | (codeUnit & 0x3F));
|
|
}
|
|
else if (codeUnit < 0xD800 || codeUnit > 0xDFFF)
|
|
{
|
|
buf[j] = String.fromCharCode(0xE0 | (codeUnit >> 12),
|
|
0x80 | ((codeUnit >> 6) & 0x3F),
|
|
0x80 | (codeUnit & 0x3F));
|
|
}
|
|
else
|
|
{
|
|
if (i + 1 < n)
|
|
{
|
|
let nextCodeUnit = str.charCodeAt(i + 1);
|
|
if (codeUnit < 0xDC00 && 0xDC00 <= nextCodeUnit && nextCodeUnit <= 0xDFFF)
|
|
{
|
|
let rune = (((codeUnit & 0x03FF) << 10) | (nextCodeUnit & 0x03FF)) + 0x010000;
|
|
buf[j] = String.fromCharCode(0xF0 | ((rune >> 18) & 0x3F),
|
|
0x80 | ((rune >> 12) & 0x3F),
|
|
0x80 | ((rune >> 6) & 0x3F),
|
|
0x80 | (rune & 0x3F));
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
throw new Error('Malformed string');
|
|
}
|
|
}
|
|
return buf.join('');
|
|
}
|
|
|
|
private static utf8DecodeShortString(bs: string, n: number): string
|
|
{
|
|
let charCodes = new Array(n);
|
|
let i = 0, off = 0;
|
|
for (let len = bs.length; i < n && off < len; i++)
|
|
{
|
|
let unit = bs.charCodeAt(off++);
|
|
switch (unit >> 4)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
charCodes[i] = unit;
|
|
break;
|
|
case 12:
|
|
case 13:
|
|
if (off < len)
|
|
{
|
|
charCodes[i] = ((unit & 0x1F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
case 14:
|
|
if (off + 1 < len)
|
|
{
|
|
charCodes[i] = ((unit & 0x0F) << 12) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
case 15:
|
|
if (off + 2 < len)
|
|
{
|
|
let rune = (((unit & 0x07) << 18) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 12) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F)) - 0x10000;
|
|
if (0 <= rune && rune <= 0xFFFFF)
|
|
{
|
|
charCodes[i++] = (((rune >> 10) & 0x03FF) | 0xD800);
|
|
charCodes[i] = ((rune & 0x03FF) | 0xDC00);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Character outside valid Unicode range: 0x' + rune.toString(16));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error('Bad UTF-8 encoding 0x' + unit.toString(16));
|
|
}
|
|
}
|
|
if (i < n)
|
|
{
|
|
charCodes.length = i;
|
|
}
|
|
return String.fromCharCode.apply(String, charCodes);
|
|
}
|
|
|
|
private static utf8DecodeLongString(bs: string, n: number): string
|
|
{
|
|
let buf: string[] = [];
|
|
let charCodes: number[] = new Array(0x8000);
|
|
let i = 0, off = 0;
|
|
for (let len = bs.length; i < n && off < len; i++)
|
|
{
|
|
let unit = bs.charCodeAt(off++);
|
|
switch (unit >> 4)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
charCodes[i] = unit;
|
|
break;
|
|
case 12:
|
|
case 13:
|
|
if (off < len)
|
|
{
|
|
charCodes[i] = ((unit & 0x1F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
case 14:
|
|
if (off + 1 < len)
|
|
{
|
|
charCodes[i] = ((unit & 0x0F) << 12) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
case 15:
|
|
if (off + 2 < len)
|
|
{
|
|
let rune = (((unit & 0x07) << 18) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 12) |
|
|
((bs.charCodeAt(off++) & 0x3F) << 6) |
|
|
(bs.charCodeAt(off++) & 0x3F)) - 0x10000;
|
|
if (0 <= rune && rune <= 0xFFFFF)
|
|
{
|
|
charCodes[i++] = (((rune >> 10) & 0x03FF) | 0xD800);
|
|
charCodes[i] = ((rune & 0x03FF) | 0xDC00);
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Character outside valid Unicode range: 0x' + rune.toString(16));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new Error('Unfinished UTF-8 octet sequence');
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error('Bad UTF-8 encoding 0x' + unit.toString(16));
|
|
}
|
|
if (i >= 0x7FFF - 1)
|
|
{
|
|
let size = i + 1;
|
|
charCodes.length = size;
|
|
buf[buf.length] = String.fromCharCode.apply(String, charCodes);
|
|
n -= size;
|
|
i = -1;
|
|
}
|
|
}
|
|
if (i > 0)
|
|
{
|
|
charCodes.length = i;
|
|
buf[buf.length] = String.fromCharCode.apply(String, charCodes);
|
|
}
|
|
return buf.join('');
|
|
}
|
|
|
|
// n is UTF16 length
|
|
public static utf8Decode(bs: string, n?: number): string
|
|
{
|
|
if (n === undefined || n === null || (n < 0)) n = bs.length;
|
|
if (n === 0) return '';
|
|
if (/^[\x00-\x7f]*$/.test(bs) || !(/^[\x00-\xff]*$/.test(bs)))
|
|
{
|
|
if (n === bs.length) return bs;
|
|
return bs.substr(0, n);
|
|
}
|
|
return ((n < 0x7FFF) ?
|
|
XXTea.utf8DecodeShortString(bs, n) :
|
|
XXTea.utf8DecodeLongString(bs, n));
|
|
}
|
|
|
|
public static encrypt(data: string, key: string): string
|
|
{
|
|
if (data === undefined || data === null || data.length === 0)
|
|
{
|
|
return data;
|
|
}
|
|
data = XXTea.utf8Encode(data);
|
|
key = XXTea.utf8Encode(key);
|
|
return XXTea.toBinaryString(XXTea.encryptUint32Array(XXTea.toUint32Array(data, true), XXTea.fixk(XXTea.toUint32Array(key, false))), false);
|
|
}
|
|
|
|
public static encryptToBase64(data: string, key: string): string
|
|
{
|
|
return XXTea.btoa(XXTea.encrypt(data, key));
|
|
}
|
|
|
|
public static decrypt(data: string, key: string): string
|
|
{
|
|
if (data === undefined || data === null || data.length === 0)
|
|
{
|
|
return data;
|
|
}
|
|
key = XXTea.utf8Encode(key);
|
|
return XXTea.utf8Decode(XXTea.toBinaryString(XXTea.decryptUint32Array(XXTea.toUint32Array(data, false), XXTea.fixk(XXTea.toUint32Array(key, false))), true));
|
|
}
|
|
|
|
public static decryptFromBase64(data: string, key: string): string
|
|
{
|
|
if (data === undefined || data === null || data.length === 0)
|
|
{
|
|
return data;
|
|
}
|
|
return XXTea.decrypt(XXTea.atob(data), key);
|
|
}
|
|
} |