分享

根据腾讯公开的JS文件分析QQTEA算法

 黄三岁大爱人生 2018-09-13

前言

根据网上的众多分析来看,很多QQTEA算法要么是反编译分析的,要么是根据其他版本改写的,很少有直接根据源码分析改写的,而且很多分析体现在源码里,梳理性的内容不多。后面的人去看又要重新梳理。

一、寻找函数

这里根据腾讯公开的JS文件分析,比反编译简单一些,至少能直接看到源码,JS文件:c_login_2.js

首先,看到这个JS很多人可能无从下手,首先要做的就是下载下来然后格式化

需要注意的是,网上大多数在线格式化网站不能正确的处理自执行函数,建议使用eclipse或者webstorm

eclipse格式化之后,有5639行,那么怎么才能找到TEA算法所在函数呢,搜索即可,不过我已经找好了

在notepad 下,Alt 0折叠所有,找到带一个参数的自执行函数,展开就可以了,注意:这个js文件一共有两个自执行函数,找不准的搜索关键字tea吧


找到之后,我们把这个函数单独拷贝出来调试,具体内容这里就不贴了,太长了

二、调用函数

接下来我们来看一下这个函数的加密解密效果,可能有人不知道怎么调,这里介绍两种方法

F12控制台法:Chrome或者Firefox打开一个空页面,复制这个函数,直接粘贴到控制台,回车,

然后复制如下语句

  1. TEA.initkey('111');
  2. var jiamihou = TEA.encrypt('8888999911112222');
  3. console.log(jiamihou);
  4. var jiemihou = TEA.decrypt(jiamihou);
  5. console.log(jiemihou);
效果如下

JS文件法:新建一个js文件如tea.js,复制前面那个函数,然后在函数结束的分号后面再加一段

  1. TEA.initkey('111');
  2. var jiamihou = TEA.encrypt('8888999911112222');
  3. alert(jiamihou);
  4. var jiemihou = TEA.decrypt(jiamihou);
  5. alert(jiemihou);
之后新建一个空白的html页面,在页面的,head>标签里面引用这个js

<script src='qqtea.js'></script>
之后用浏览器打开这个页面就能看到两个弹窗效果了。


三、分析函数(加密篇)

上面只是演示这个函数的效果,下面才是重头戏,分析这个js的加密解密过程

加密方法:

  1. encrypt: function (E, D) {
  2. var C = q(E, D);
  3. var B = j(C);
  4. return y(B)
  5. },
其中q(E, D)方法是转码用的,方法如下

  1. //转码,如果E参数存在,直接转ascii码存入数组,如果不存在,每两位当做16进制转ASCII码
  2. //D数组的长度取决于传入F的长度
  3. function q(F, E) {
  4. var D = [
  5. ];
  6. if (E) {
  7. for (var C = 0; C < F.length; C ) {
  8. D[C] = F.charCodeAt(C) & 255 //取ASCII码,只取8位
  9. }
  10. } else {
  11. var B = 0;
  12. for (var C = 0; C < F.length; C = 2) {
  13. D[B ] = parseInt(F.substr(C, 2), 16) //把F当做16进制的字符串,每取两位存一下
  14. }
  15. }
  16. return D
  17. }
j(C)方法才是用来加密的

  1. //加密函数
  2. function j(D) {
  3. h = new Array(8);
  4. z = new Array(8);
  5. A = w = 0;
  6. p = true;
  7. a = 0;
  8. var B = D.length; //获取要加密字符串的长度
  9. var E = 0;
  10. a = (B 10) % 8;
  11. if (a != 0) {
  12. a = 8 - a
  13. }
  14. //a为需要填充的字符个数
  15. o = new Array(B a 10); //填充为8的整数倍数组
  16. h[0] = ((f() & 248) | a) & 255; //取f()的3~8位与a做或运算,这里把a存进去了,解密的时候可以推算出填充个数
  17. for (var C = 1; C <= a; C ) {
  18. h[C] = f() & 255 //取f()的8位存入,填充用的,可以不管
  19. }
  20. a ;
  21. for (var C = 0; C < 8; C ) { //初始化数组z
  22. z[C] = 0
  23. }
  24. E = 1;
  25. while (E <= 2) {
  26. if (a < 8) {
  27. h[a ] = f() & 255; //继续填充
  28. E
  29. }
  30. if (a == 8) {
  31. r() //每次执行r(),a会重置为0
  32. }
  33. }
  34. var C = 0;
  35. while (B > 0) {
  36. if (a < 8) {
  37. h[a ] = D[C ]; //把D数组的内容复制到h数组后面
  38. B--
  39. }
  40. if (a == 8) {
  41. r() //每次取出待加密数据的一个块,h[0]~h[7],送到r()处理
  42. }
  43. }
  44. E = 1;
  45. while (E <= 7) {
  46. if (a < 8) {
  47. h[a ] = 0; //填充0
  48. E
  49. }
  50. if (a == 8) {
  51. r()
  52. }
  53. }
  54. return o
  55. }
加密总体思路:填充算法-->QQCBC交织算法-->TEA算法,先看填充算法

(一)填充算法

参考这张图说明一下:


因为TEA加密只能加密定长的数据,所以QQ这里先分组,分组后给TEA加密,一个分组大小是8字节,所以填充完要是8字节的倍数

QQ在填充后的数据长度=原始数据长度 a 10

其中a的算法: 如果(B 10) % 8=0,则a=0,如果(B 10) % 8=!0则a=8-((B 10) % 8)

整个数据区概况:1个字节标识a的长度(低位填充a,高位填充随机数) a个字节的随机数填充 2位随机数填充 原始数据 7个字节0填充

数据区每取到8个字节就用r()函数加密一次


(二)、QQCBC交织算法

r()函数采用类似CBC模式

为什么是类似CBC,因为QQ的CBC模式比标准多了一步异或

QQCBC模式是这样的


与标准CBC多出来的异或在下图用红框标注出来了


QQCBC对应的代码如下:

  1. //交织算法CBC加密
  2. function r() {
  3. for (var B = 0; B < 8; B ) {
  4. if (p) {
  5. h[B] ^= z[B]
  6. } else {
  7. h[B] ^= o[w B]
  8. }
  9. }
  10. var C = l(h); //CBC算法使用的加密器,TEA加密
  11. for (var B = 0; B < 8; B ) {
  12. o[A B] = C[B] ^ z[B]; //这里比标准的CBC算法多了一步异或
  13. z[B] = h[B]
  14. }
  15. w = A;
  16. A = 8;
  17. a = 0;
  18. p = false
  19. }

图中的加密器使用的是16轮标准TEA加密,这个QQ没有改

(三)、TEA算法

标准TEA算法的图可以参考我之前写过的:TEA、XTEA、XXTEA加密解密算法

为了文章的完整性,这里也贴一下,图是一轮加密的描述


QQ的TEA用的16轮加密,标准用的32轮,常数delta=0x9e3779b9十进制就是2654435769,存储加密结果的时候使用的是网络序,代码如下

  1. //tea加密算法主体
  2. function l(B) { //u是CBC算法加密器对应的key,也就是tea对应的key
  3. var C = 16; //16轮
  4. var H = k(B, 0, 4); //取B[0]~B[3],形成字符串,B[0]在前,TEA算法左边v0
  5. var G = k(B, 4, 4); //TEA算法右边v1
  6. var J = k(u, 0, 4); //TEA秘钥k1
  7. var I = k(u, 4, 4); //TEA秘钥k2
  8. var F = k(u, 8, 4); //TEA秘钥k3
  9. var E = k(u, 12, 4); //TEA秘钥k4
  10. var D = 0;
  11. var K = 2654435769 >>> 0; //无符号右移,TEA算法的delta常数
  12. while (C-- > 0) {
  13. D = K;
  14. D = (D & 4294967295) >>> 0;
  15. H = ((G << 4) J) ^ (G D) ^ ((G >>> 5) I); //计算TEA左边
  16. H = (H & 4294967295) >>> 0;
  17. G = ((H << 4) F) ^ (H D) ^ ((H >>> 5) E);
  18. G = (G & 4294967295) >>> 0
  19. }
  20. var L = new Array(8);
  21. b(L, 0, H);
  22. b(L, 4, G);
  23. return L
  24. }


(四)、常见问题

1.加密同一个明文结果不一样
QQ在实现加密的时候用了随机数填充,填充之后的数据区不一样,加密的明文自然不一样,填充用到的f()产生随机数,部分网上的实现可能为了比较算法结果固定了这个随机数,这时候密文结果就唯一了。密文解密时这些填充进去的随机数不影响解密
2.末尾填充7个字节0的作用
解密时校验解密的字符串是否正确
3.tea算法里的delta,tea解密算法里的sum取值
delta=(2^32)*黄金分割数0.618
sum = delta << 3; //32轮运算,是2的5次方,delta左移4位;16轮运算,是2的4次方,delta左移3位;8轮运算,是2的3次方,delta左移2位

4.自匿名函数中几个全局变量的作用

  1. var u = '',a = 0, h = [],z = [], A = 0,w = 0,o = [], v = [], p = true;
  2. //u是初始秘钥,由initkey方法负责初始化,用于tea算法加密使用
  3. //z为交织算法CBC中的初始向量IV或上一个密文块
  4. //h为交织算法CBC中的明文块,计算过程中没有存所有明文h[0]~h[7]只存当前要计算的明文块
  5. //o为交织算法CBC中的密文,其中o[w 0] ~o[w 7]为第w个密文块
  6. //p标识是否为CBC中的明文块的第一块
5.自匿名函数的其他调用方法

TEA.initkey('111')可以改写为window.TEA.initkey('111'),其他以此类推。因为自匿名函数传了window参数进去,所以这些方法也是window对象的函数。






    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多