1.在登录接口开启全局监听事件 1.1.开启全局监听事件的断点 此时只要我们鼠标点击任何按钮,代码会停止运行,此时可进入调试模式。
1.2.狂按F9进行单步调试(接近200次) 由于我并不熟悉该系统代码逻辑,因此我不能跳过任何一个函数,以防错过关键信息。
1.3.经过长时间的单步代码调试来到了user.js的代码逻辑中 这里有我想要的关键信息,通过响应体里获取到了关键的publicKey,通过变量名我们大致可以判断,是非对称加密方式,因此我们大致可以猜测是SM2,或者是RSA。
1.4.密码算法判断 由于SM2通常是512位的公钥,而RSA密钥长度至少1024位以上,这长度明显比512位更长因此再次确认位rsa公钥。
在密码加密逻辑中变量命令为sm3?
1.6.为了进一步确认,我需要更加严谨的判断,再次进行单步调试。
1.7.将密码转换为字节数组
该函数的逻辑我们知道了,那么按F11跳出这个函数继续下一步。
1.8.通过对密码算法的具体判断 通过对之后密码算法的具体分析,确实是SM3加密,
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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 function sm3 (array ) { let len = array.length * 8 let k = len % 512 k = k >= 448 ? 512 - (k % 448 ) - 1 : 448 - k - 1 const kArr = new Array ((k - 7 ) / 8 ) const lenArr = new Array (8 ) for (let i = 0 , len = kArr.length ; i < len; i++) kArr[i] = 0 for (let i = 0 , len = lenArr.length ; i < len; i++) lenArr[i] = 0 len = len.toString (2 ) for (let i = 7 ; i >= 0 ; i--) { if (len.length > 8 ) { const start = len.length - 8 lenArr[i] = parseInt (len.substr (start), 2 ) len = len.substr (0 , start) } else if (len.length > 0 ) { lenArr[i] = parseInt (len, 2 ) len = '' } } const m = new Uint8Array ([...array, 0x80 , ...kArr, ...lenArr]) const dataView = new DataView (m.buffer , 0 ) const n = m.length / 64 const V = new Uint32Array ([0x7380166f , 0x4914b2b9 , 0x172442d7 , 0xda8a0600 , 0xa96f30bc , 0x163138aa , 0xe38dee4d , 0xb0fb0e4e ]) for (let i = 0 ; i < n; i++) { W.fill (0 ) M.fill (0 ) const start = 16 * i for (let j = 0 ; j < 16 ; j++) { W[j] = dataView.getUint32 ((start + j) * 4 , false ) } for (let j = 16 ; j < 68 ; j++) { W[j] = (P1 ((W[j - 16 ] ^ W[j - 9 ]) ^ rotl (W[j - 3 ], 15 )) ^ rotl (W[j - 13 ], 7 )) ^ W[j - 6 ] } for (let j = 0 ; j < 64 ; j++) { M[j] = W[j] ^ W[j + 4 ] } const T1 = 0x79cc4519 const T2 = 0x7a879d8a let A = V[0 ] let B = V[1 ] let C = V[2 ] let D = V[3 ] let E = V[4 ] let F = V[5 ] let G = V[6 ] let H = V[7 ] let SS1 let SS2 let TT1 let TT2 let T for (let j = 0 ; j < 64 ; j++) { T = j >= 0 && j <= 15 ? T1 : T2 SS1 = rotl (rotl (A, 12 ) + E + rotl (T, j), 7 ) SS2 = SS1 ^ rotl (A, 12 ) TT1 = (j >= 0 && j <= 15 ? ((A ^ B) ^ C) : (((A & B) | (A & C)) | (B & C))) + D + SS2 + M[j] TT2 = (j >= 0 && j <= 15 ? ((E ^ F) ^ G) : ((E & F) | ((~E) & G))) + H + SS1 + W[j] D = C C = rotl (B, 9 ) B = A A = TT1 H = G G = rotl (F, 19 ) F = E E = P0 (TT2 ) } V[0 ] ^= A V[1 ] ^= B V[2 ] ^= C V[3 ] ^= D V[4 ] ^= E V[5 ] ^= F V[6 ] ^= G V[7 ] ^= H } const result = [] for (let i = 0 , len = V.length ; i < len; i++) { const word = V[i] result.push ((word & 0xff000000 ) >>> 24 , (word & 0xff0000 ) >>> 16 , (word & 0xff00 ) >>> 8 , word & 0xff ) } return result }
1.9.确实是SM3加密,为什么会有公钥? 通过直接对结果分析确实是SM3,且只进行了SM3加密,未进行RSA(代码逻辑与测试时代码逻辑不同),改完的代码逻辑虽然请求获取到了公钥但是公钥全程都没有使用。
1.10.思考 这样只进行SM3加密没有多大意义,攻击者截获数据包之后,完全不需要知道原密码是什么,只需要抓包改包即可,还不如RSA加密,因为RSA加密至少每次解密结果都不同。而且这种SM3哈希摘要完全可以把加密串在本地使用彩虹表爆破。最安全的方式应该是先进行SM3然后再进行一次RSA加密,反正原本的代码逻辑就有RSA加密,只是中间加了一个SM3而已,这种修改方式即安全又方便。
(该环境明显不是生产环境而是开发环境,真实的生产环境中JS代码都是压缩之后的代码很难看懂代码含义,这个代码逻辑太清晰了,是开发环境,实际测试大多情况chrome前端可以看到的代码都是压缩后的代码,因此不是每次都可以成功获取到密码加密逻辑。)