摘要算法

JavaScript 中和 Python 中的基本实现方法,遇到 JS 加密的时候可以快速还原加密过程,有的网站在加密的过程中可能还经过了其他处理,但是大致的方法是一样的。

消息摘要算法/签名算法:MD5、SHA、HMAC

1. MD5

简介:全称 MD5 消息摘要算法,又称哈希算法、散列算法,由美国密码学家罗纳德·李维斯特设计,于 1992 年作为 RFC 1321 被公布,用以取代 MD4 算法。摘要算法是单向加密的,也就是说明文通过摘要算法加密之后,是不能解密的。摘要算法的第二个特点密文是固定长度的,它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。之所以叫摘要算法,它的算法就是提取明文重要的特征。所以,两个不同的明文,使用了摘要算法之后,有可能他们的密文是一样的,不过这个概率非常的低。

1.1 JavaScript 实现

地址:https://www.autohome.com.cn/changsha/

安装对应的模块

1
2
// 在依赖项中添加包: --save
npm install crypto-js --save

使用案例

1
2
3
4
5
6
7
8
9
// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function MD5Test() {
var text = "I love python!"
return CryptoJS.MD5(text).toString()
}

console.log(MD5Test())

1.2 Python 实现

1
2
3
4
5
6
7
8
9
import hashlib

def md5_test2():
md5 = hashlib.md5()
md5.update('python'.encode('utf-8'))
print(md5.hexdigest())

if __name__ == '__main__':
md5_test2()

总结:MD5哈希视为字符串,而是将其视为十六进制数, MD5哈希长度为128位,通常由32个十六进制数字表示。

2. SHA

地址:https://www.51job.com/

简介:全称安全哈希算法,由美国国家安全局(NSA)所设计,主要适用于数字签名标准里面定义的数字签名算法,SHA 通常指 SHA 家族的五个算法,分别是 SHA-1、SHA-224、SHA-256、SHA-384、SHA-512SHA 是比 MD5 更安全一点的摘要算法,MD5 的密文是 32 位,而 SHA-1 是 40 位,版本越强,密文越长,代价是速度越慢。

2.1 JavaScript 实现

1
2
3
4
5
6
7
8
9
10
// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function SHA1Encrypt() {
var text = "I love python!"
return CryptoJS.SHA1(text).toString();
}

console.log(SHA1Encrypt())

2.2 Python 实现

1
2
3
4
5
6
7
8
9
import hashlib

def sha1_test2():
sha1 = hashlib.sha1()
sha1.update('I love python!'.encode('utf-8'))
print(sha1.hexdigest())

if __name__ == '__main__':
sha1_test2()

2.3 sha系列特征

sha1:23c02b203bd2e2ca19da911f1d270a06d86719fb

sha224:1ffeffcbe2707dc5d1c10df619203c1a3b620c70394b3c4c106d92e6

sha256:c3a845a318cd654749ea4db6f4d5f9cb5c6e5b0cade46d9dc04af46d32049c7c

sha512:af47f324b77a4885748bfc3f0d9b5a846c0153c589852bb3f185ab6e7a600547b818ab994776e8d24584457f9aac84246b0de971584cebbdd96aa1aee6630f9f

总结:根据长度进行定位、主要还是要去JavaScript里面下断点调试分析

3. HMAC

简介:全称散列消息认证码、密钥相关的哈希运算消息认证码,于 1996 年提出,1997 年作为 RFC 2104 被公布,HMAC 加密算法是一种安全的基于加密 Hash 函数和共享密钥的消息认证协议,它要求通信双方共享密钥 key、约定算法、对报文进行 Hash 运算,形成固定长度的认证码。通信双方通过认证码的校验来确定报文的合法性。

参考资料:

3.1 JavaScript 实现

1
2
3
4
5
6
7
8
9
10
11
// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function HMACEncrypt() {
var text = "I love python!"
var key = "secret" // 密钥文件
return CryptoJS.HmacMD5(text, key).toString();
// return CryptoJS.HmacSHA1(text, key).toString();
// return CryptoJS.HmacSHA256(text, key).toString();
}
console.log(HMACEncrypt())

3.2 Python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import hmac

def hmac_test1():
message = b'I love python!'
key = b'secret'
md5 = hmac.new(key, message, digestmod='MD5')
print(md5.hexdigest())


def hmac_test2():
key = 'secret'.encode('utf8')
sha1 = hmac.new(key, digestmod='sha1')
sha1.update('I love '.encode('utf8'))
sha1.update('Python!'.encode('utf8'))
print(sha1.hexdigest())

if __name__ == '__main__':
hmac_test1() # 9c503a1f852edcc3526ea56976c38edf
hmac_test2() # 2d8449a4292d4bbeed99ce9ea570880d6e19b61a

4. 实战案例

4.1 案例 md5加密逆向

4.1.1 逆向目标
4.1.2 逆向分析
  1. 先进行抓包,可以看到有一个签名信息 code

    这里推荐下xhr断点调试进行分析、可以发现有一个拦截器

    image-20230224165835043

  1. 数据加密位置,可以在这儿进行分析

image-20230224165938759

4.1.3 python代码模拟
1
2
3
4
5
6
7
8
9
10
11
12
import hashlib,time
// o()(n + "9527" + n.substr(0, 6))

def md5_test2():
n = str(int(time.time())*1000)
value = n + "9527" + n[0:6]
md5 = hashlib.md5()
md5.update(value.encode('utf-8'))
print(md5.hexdigest())

if __name__ == '__main__':
md5_test2()

4.2 案例sha256系列

逆向目标

4.2.1 抓包分析:
  1. 通过对比,可以发现这个参数每次都会切换

image-20220815142259011

4.2.2 调试加密地地点
  1. 打开全局搜索 sign关键字

image-20220815143019563

  1. 参数加密地点

image-20220815143143609

4.2.3 python代码实现
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
import urllib3,requests,time,json
urllib3.disable_warnings()
import hashlib

months = input("请输入查询月份:")
days = input("请输入查询日期,2天以内:")
times = str(int(time.time()) * 1000)
params = {"no":"dy0002","data":{"days":1,"rankType":5,"liveDay":f"2023-{months.zfill(2)}-{days.zfill(2)}"}}
print(params)
dd = json.dumps(params)
def get_sign():
data = f'param={dd}&timestamp={times}&tenant=1&salt=kbn%&)@<?FGkfs8sdf4Vg1*+;`kf5ndl$' # 要进行加密的数据
data_sha = hashlib.sha256(data.encode('utf-8')).hexdigest()
return data_sha

def get_data():
headers = {
"Content-Type": "application/json;charset=UTF-8",
"Host": "ucp.hrdjyun.com:60359",
"Origin": "http://www.hh1024.com",
"Pragma": "no-cache",
"sec-ch-ua": "\"Google Chrome\";v=\"107\", \"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
}
session = requests.session()
s = get_sign()
t = "这里面是登陆后的token值"
datas = {"param":dd,"sign":s,"tenant":"1","timestamp":times,"token":t}
url = 'https://ucp.hrdjyun.com:60359/api/dy'
res = session.post(url,headers=headers,data=json.dumps(datas))
if res.json().get('status') == 0:
data = res.json().get('data')['rankList']
for d in data:
items = {}
items['抖音名'] = d.get('anchorName')
items['带货销量'] ='%.2f' % (d.get('salesVolume') / 10000) + '万'
print(items)

if __name__ == '__main__':
reads = """
本接口只开放抖音带货销量日榜
可以根据日期查询
--- 夏洛
"""
print(reads)
get_data()

4.3 案例Hmac系列

4.3.1 逆向目标
4.3.2 逆向分析

可以发现随着请求变化的数据仅有 headers 里的一对键值,且后台也仅对该变动键值做校验。由于其看起来很像 Hash,索性就叫 hashKey:hashValue

JavaScript 中的赋值语句通常为 headers[key] = value,搜索 headers[ 即可。

image-20230226202137516

在断点这里可以发现 i 就是key, l就是value,对这两个参数分析即可

1 key值进行分析
1
2
key = (0,a.default)(t, e.data)
a.default(path,undefined)
1
arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}

解析 && 解析

image-20230226203104798

key运算的结果、可以发现后边进行了分割

image-20230226203356797

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(0,o.default)(t + n, (0, a.default)(t)).toLowerCase().substr(8, 20)
o.default(t + n, a.default(t)).toLowerCase().substr(8, 20)

// 循环解析
for (var e = '/api/datalist/changelist?keyno=5dffb644394922f9073544a08f38be9f&pageindex=2', t = e + e, n = "", i = 0; i < t.length; ++i)
{
// t 150的长度 迭代149次 从0开始
console.log(t);
console.log(i);
// charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。
// python里面可以使用 ord("/") chr(47) 是相反的意思
var a = t[i].charCodeAt() % o.default.n; // % 求余计算 o.default 可以写死
// 从里面取参数进行拼接
n += o.default.codes[a]
}

这里面最后会走到hmac算法进行加密

2 value值进行分析

image-20230226205845979

操作方式同步key的方式、就是参数做了拼接

4.3.3 python操作结果

image-20230226212427286

对称加密

对称加密(加密解密密钥相同):DES、3DES、AES、RC4

简介

对称式加密就是加密和解密使用同一个密钥。信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行加解密了。对称加密算法用来对敏感数据等信息进行加密。

image-20220816202802353

常见算法归纳

DES:56位密钥,由于密钥太短,被逐渐被弃用。

AES:有128位、192位、256位密钥,现在比较流行。密钥长、可以增加破解的难度和成本。

工作模式归纳

  • ECB模式 全称Electronic Codebook模式,译为电子密码本模式,每个数据块独立进行加/解密

  • CBC模式 全称Cipher Block Chaining模式,译为密文分组链接模式

    • ```
      先将明文切分成若干小块,然后每个小块与初始块或者上一段的密文段进行逻辑异或运算后,再用密钥进行加密。第一个明文块与一个叫初始化向量的数据块进行逻辑异或运算
      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

      - **CFB模式** 全称Cipher FeedBack模式,译为密文反馈模式

      - **OFB模式** 全称Output Feedback模式,译为输出反馈模式。

      - **CTR模式** 全称Counter模式,译为计数器模式。

      **iv:** 防止同样的明文块、加密成同样的密文块

      参考:https://zhuanlan.zhihu.com/p/252551522

      ### 1. DES算法

      实例地址:https://bqcm0.cavip1.com/

      ![image-20221116133758840](https://s1.ax1x.com/2023/07/10/pCR46VU.png)

      简介:**DES**是一种分组**加密算法**,他以`64`位为分组对数据加密。`64`位一组的明文从算法的一端 输入,`64`位的密文从另一端输出。**DES**是一个对称算法:加密和解密用的是同一个算法(除 密钥编排不同以外)。

      密钥的长度为`56`位(密钥通常表示为`64`位的数,但每个第8位都用作奇偶检验,可以忽 略)。密钥可以是任意的`56`位数,且可以在任意的时候改变。

      **DES**算法的入口参数有3个:`Key,Data,Mode`。其中`Key`为8个字节共64位,是**DES**算法 的工作密钥;`Data`也为8个字节64位,是要被加密或解密的数据:Mode为**DES**的工作方式,有 两种:加密或解密。

      DES算法的工作过程:若Mode为加密,则用Key对数据Data进行加密,生成Data的密码 形式(64位)作为DES的输出结果;若Mode为解密,则用Key对密码形式的数据Data解密,还 原为Data的明码形式(64位)作为DES的输出结果。
         简单地说,算法只不过是加密的一种基本技术,DES基本组建分组是这些技术的一种组合 ,他基于密钥作用于明文,这是众所周知的轮(round)。DES有16轮,这意味着要在明文分 组上16次实施相同的组合技术。

      - mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。
      - padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

      **参考资料:**

      - RFC 4772:https://datatracker.ietf.org/doc/rfc4772/
      - DES 维基百科:https://en.wikipedia.org/wiki/Data_Encryption_Standard

      #### 1.1 JavaScript 实现

      `DES`算法的入口参数有3个

      + `key、DATA、Mode、padding`
      + `key`为`7个字节`共56位,是DES算法的工作密钥
      + `Data`为`8个字节`64位,是要被加密或被解密的数据
      + `Mode`为`DES`的工作方式
      + `padding`为填充模式,如果加密后密文长度如果达不到指定整数倍(8个字节,16个字节),填充

      ```JavaScript
      // 引用 crypto-js 加密模块
      var CryptoJS = require('crypto-js')

      function desEncrypt() {
      var key = CryptoJS.enc.Utf8.parse(desKey),
      iv = CryptoJS.enc.Utf8.parse(desIv),
      srcs = CryptoJS.enc.Utf8.parse(text),
      // CBC 加密模式,Pkcs7 填充方式
      encrypted = CryptoJS.DES.encrypt(srcs, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
      });
      return encrypted.toString();
      }

      function desDecrypt() {
      var key = CryptoJS.enc.Utf8.parse(desKey),
      iv = CryptoJS.enc.Utf8.parse(desIv),
      srcs = encryptedData,
      // CBC 加密模式,Pkcs7 填充方式
      decrypted = CryptoJS.DES.decrypt(srcs, key, {
      iv: iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
      });
      return decrypted.toString(CryptoJS.enc.Utf8);
      }

      var text = "I love Python!" // 待加密对象
      var desKey = "6f726c64f2c2057" // 密钥
      var desIv = "0123456789ABCDEF" // 初始向量

      var encryptedData = desEncrypt()
      var decryptedData = desDecrypt()

      console.log("加密字符串: ", encryptedData)
      console.log("解密字符串: ", decryptedData)

      // 加密字符串: +ndbEkWNw2QAfIYQtwC14w==
      // 解密字符串: I love Python!

1.2 Python 实现

1
pip install pyDes
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
import binascii
# 加密模式 CBC,填充方式 PAD_PKCS5
from pyDes import des, CBC, PAD_PKCS5

def des_encrypt(key, text, iv):
k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(text, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)

def des_decrypt(key, text, iv):
k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(text), padmode=PAD_PKCS5)
return de

if __name__ == '__main__':
secret_key = '12345678' # 密钥
text = 'hello world' # 加密对象
iv = secret_key # 偏移量
secret_str = des_encrypt(secret_key, text, iv)
print('加密字符串:', secret_str)
clear_str = des_decrypt(secret_key, secret_str, iv)
print('解密字符串:', clear_str)


# 加密字符串:b'302d3abf2421169239f829b38a9545f1'
# 解密字符串:b'I love Python!'

总结:https://www.processon.com/view/link/6374f0e10e3e742ce7bd597b

1.3 实操练习1

地址:http://www.91118.com/Passport/Account/Login

1.4 实操练习2

1.3.1 逆向目标

首页:https://www.endata.com.cn/BoxOffice/BO/Month/oneMonth.html

数据:https://www.endata.com.cn/API/GetData.ashx

逆向:加密数据

1.3.2 逆向分析
  • 使用xhr断点数据地址,进行单步调试

image-20220816214111191

  • 调试对应的数据

image-20220816214102754

总结:可以看到他是对数据用webInstace.shell进行了解密

2. AES算法

环境安装

1
pip install pycryptodome -i pip源

2.1 算法简介

​ 简介:全称高级加密标准(英文名称:Advanced Encryption Standard),在密码学中又称 Rijndael 加密法,由美国国家标准与技术研究院 (NIST)于 2001 年发布,并在 2002 年成为有效的标准,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用,它本身只有一个密钥,即用来实现加密,也用于解密。

  • mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。
  • padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

参考资料:

参数定义:

  1. key length(密钥位数,密码长度)AES128,AES192,AES256(128 位、192 位或 256 位)
  2. key (密钥,密码)key指的就是密码了,AES128就是128位的,如果位数不够,某些库可能会自动填充到128
  3. IV (向量)IV称为初始向量,不同的IV加密后的字符串是不同的,加密和解密需要相同的IV。
  4. mode (加密模式)AES分为几种模式,比如ECB,CBC,CFB等等,这些模式除了ECB由于没有使用IV而不太安全,其他模式差别并没有太明显。
  5. padding (填充方式)对于加密解密两端需要使用同一的PADDING模式,大部分PADDING模式为PKCS5, PKCS7, NOPADDING

加密原理:

​ AES加密算法采用分组密码体制,每个分组数据的长度为128位16个字节,密钥长度可以是128位16个字节192位或256位,一共有四种加密模式,我们通常采用需要初始向量IV的CBC模式,初始向量的长度也是128位16个字节

2.2 JavaScript 实现

类似网站:https://www.dns.com/login.html

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
// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function tripleAesEncrypt() {
var key = CryptoJS.enc.Utf8.parse(aesKey),
iv = CryptoJS.enc.Utf8.parse(aesIv),
srcs = CryptoJS.enc.Utf8.parse(text),
// CBC 加密方式,Pkcs7 填充方式
encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}

function tripleAesDecrypt() {
var key = CryptoJS.enc.Utf8.parse(aesKey),
iv = CryptoJS.enc.Utf8.parse(aesIv),
srcs = encryptedData,
// CBC 加密方式,Pkcs7 填充方式
decrypted = CryptoJS.AES.decrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}

var text = "I love Python!" // 待加密对象
var aesKey = "6f726c64f2c2057c" // 密钥,16 倍数
var aesIv = "0123456789ABCDEF" // 偏移量,16 倍数

var encryptedData = tripleAesEncrypt()
var decryptedData = tripleAesDecrypt()

console.log("加密字符串: ", encryptedData)
console.log("解密字符串: ", decryptedData)

// 加密字符串: dZL7TLJR786VGvuUvqYGoQ==
// 解密字符串: I love Python!

2.3 实际案例

2.3.1 逆向目标
2.3.2 抓包分析

1、抓包处理

根据抓包结果可以分析,此接口数据是16进制编码的数据,如果需要采集,就需要使用算法进行分析

2、对于数据加密的网站,先看启动器分析JS文件,然后全局搜索json.parse(,一般后台返回的加密数据,会进行类型转换。

image-20221118143437038

3、数据确认

image-20221118144020008

从这里可以发现t.data 是后台返回的数据,hJavaScript里面的方法,数据经过h变成了明文,所以需要先分析h方法

4、h函数判断

image-20221118144201390

经过查看分析,h方法是一个算法 ,可以先使用标准算法进行尝试处理

2.3.3 逆向结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var CryptoJS = require('crypto-js');

function h(t) {

f = CryptoJS.enc.Utf8.parse("jo8j9wGw%6HbxfFn")
m = CryptoJS.enc.Utf8.parse("0123456789ABCDEF");

var key = e = CryptoJS.enc.Hex.parse(t)
n = CryptoJS.enc.Base64.stringify(e)

var decrypt = CryptoJS.AES.decrypt(n, f,{
iv: m,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypt.toString(CryptoJS.enc.Utf8).toString();
}

非对称加密

1 非对称简介

​ 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

  • 常见非对称加密算法 RSADSA
  • 非对称加密算法私钥由数据接收方持有,不会在网络上传递,保证了密钥的安全。
  • 非对称加密算法通常比对称加密算法计算复杂,性能消耗高。
  • 非对称加密算法可用于数字签名。

image-20220817145413422

注意:

  • 使用时都是使用公钥加密使用私钥解密,公钥可以公开,私钥自己保留。
  • 算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使加密解密速度慢于对称加密

2 非对称特征

常见JavaScript调试算法

  • 搜索关键词 new JSEncrypt()JSEncrypt 等,一般会使用 JSEncrypt 库,会有 new 一个实例对象的操作;
  • 搜索关键词 setPublicKeysetKeysetPrivateKeygetPublicKey 等,一般实现的代码里都含有设置密钥的过程。

RSA 的私钥、公钥、明文、密文长度也有一定对应关系,也可以从这方面初步判断:

私钥长度 公钥长度 明文长度 密文长度
428 128 1~53 88
812 216 1~117 172
1588 392 1~245 344

2.1 JavaScript 实现

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
// npm install node-rsa --save
// 引用 node-rsa 加密模块
var NodeRSA = require('node-rsa');

function rsaEncrypt() {
pubKey = new NodeRSA(publicKey,'pkcs8-public');
var encryptedData = pubKey.encrypt(text, 'base64');
return encryptedData
}

function rsaDecrypt() {
priKey = new NodeRSA(privatekey,'pkcs8-private');
var decryptedData = priKey.decrypt(encryptedData, 'utf8');
return decryptedData
}

var key = new NodeRSA({b: 512}); //生成512位秘钥
var publicKey = key.exportKey('pkcs8-public'); //导出公钥
var privatekey = key.exportKey('pkcs8-private'); //导出私钥
var text = "I love Python!"

var encryptedData = rsaEncrypt()
var decryptedData = rsaDecrypt()

console.log("公钥:\n", publicKey)
console.log("私钥:\n", privatekey)
console.log("加密字符串: ", encryptedData)
console.log("解密字符串: ", decryptedData)

2.2 Python 实现

模块:rsa

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
# -*- coding: utf-8 -*-
# @File : 02-demo.py
# @VX : tl210329
import rsa
import base64

def rsa_encrypt(pu_key, t):
# 公钥加密
rsas = rsa.encrypt(t.encode("utf-8"), pu_key)
return base64.b64encode(rsas)

def rsa_decrypt(pr_key, t):
# 私钥解密
rsas = rsa.decrypt(base64.b64decode(t), pr_key).decode("utf-8")
return rsas

if __name__ == "__main__":
public_key, private_key = rsa.newkeys(512) # 生成公钥、私钥
print('公钥:', public_key)
print('私钥:', private_key)
text = 'I love Python!' # 加密对象
encrypted_str = rsa_encrypt(public_key, text)
print('加密字符串:', encrypted_str)
decrypted_str = rsa_decrypt(private_key, encrypted_str)
print('解密字符串:', decrypted_str)

3 案例实战

3.1 抓包分析

随便输入一个账号密码,点击登陆,抓包定位到登录接口为 https://passport.fang.com/login.api ,POST 请求,Form Data 里,密码 pwd 被加密处理了。

图片

3.2 参数逆向

加密参数只有一个 pwd,直接全局搜索,出现一个 loginbypassword.js,很明显就是加密的 JS,这个JS贴心的写上了中文注释,直接来到登录模块,埋下断点:

图片

关键代码:

1
2
3
4
uid: that.username.val(),
pwd: encryptedString(key_to_encode, that.password.val()),
Service: that.service.val(),
AutoLogin: that.autoLogin.val()

这里主要用到了 encryptedString 这个函数和 key_to_encode 参数,鼠标放到 encryptedString 函数上面,可以看到这个函数实际上是在一个叫做 RSA.min.js 的加密 JS 文件里,很明显的RSA加密,我们跟进这个函数,直接将所有加密函数剥离下来进行本地调试即可:

图片

key_to_encode 这个参数是可以直接在首页搜到,可以看到是向 RSAKeyPair 函数传入参数得到的:

图片

根据以上分析,我们可以把加密的主要步骤重写并封装成一个函数:

1
2
3
4
function getEncryptedPassword(pwd, n, i, t) {
var key_to_encode = new RSAKeyPair(n, i, t);
return encryptedString(key_to_encode, pwd)
}

其中 pwd 就是明文密码,n,i,t 是用来获取 key_to_encode 的参数,它们三个的值都可以在主页中找到。

3.3 JS代码

  • setMaxDigits()貌似是生成密文的最大位数, 计算公式 n ** 2 / 16。其中n为密钥长度
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
function setMaxDigits(n) {}

function BigInt(n) {}

function biFromDecimal(n) {}

// 此处省略 N 个函数

function twoDigit(n) {}

function encryptedString(n, t) {}

function decryptedString(n, t) {}

var biRadixBase = 2, biRadixBits = 16, bitsPerDigit = biRadixBits, biRadix = 65536, biHalfRadix = biRadix >>> 1,
biRadixSquared = biRadix * biRadix, maxDigitVal = biRadix - 1, maxInteger = 9999999999999998, maxDigits, ZERO_ARRAY,
bigZero, bigOne, dpl10, lr10, hexatrigesimalToChar, hexToChar, highBitMasks, lowBitMasks;
setMaxDigits(20);
dpl10 = 15;
lr10 = biFromNumber(1e15);
hexatrigesimalToChar = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
hexToChar = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
highBitMasks = [0, 32768, 49152, 57344, 61440, 63488, 64512, 65024, 65280, 65408, 65472, 65504, 65520, 65528, 65532, 65534, 65535];
lowBitMasks = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535];
setMaxDigits(129);

function getEncryptedPassword(pwd, n, i, t) {
var key_to_encode = new RSAKeyPair(n, i, t);
return encryptedString(key_to_encode, pwd)
}

// 测试样例
// console.log(getEncryptedPassword("16521689404", "010001", "", "978C0A92D2173439707498F0944AA476B1B62595877DD6FA87F6E2AC6DCB3D0BF0B82857439C99B5091192BC134889DFF60C562EC54EFBA4FF2F9D55ADBCCEA4A2FBA80CB398ED501280A007C83AF30C3D1A142D6133C63012B90AB26AC60C898FB66EDC3192C3EC4FF66925A64003B72496099F4F09A9FB72A2CF9E4D770C41"))

3.4 python代码

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import execjs
import requests


index_url = 'https://passport.fang.com/'
login_url = 'https://passport.fang.com/login.api'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
session = requests.session()


def get_key_to_encode():
headers = {'User-Agent': user_agent}
response = session.get(url=index_url, headers=headers)
key_to_encode = re.findall(r'RSAKeyPair\((.*)\);', response.text)[0].replace('"', '').split(', ')
return key_to_encode


def get_encrypted_password(key_to_encode, pwd):
n, i, t = key_to_encode[0], key_to_encode[1], key_to_encode[2]
with open('fang_encrypt.js', 'r', encoding='utf-8') as f:
fang_js = f.read()
encrypted_pwd = execjs.compile(fang_js).call('getEncryptedPassword', pwd, n, i, t)
return encrypted_pwd


def login(encrypted_password, uid):
headers = {
'User-Agent': user_agent,
'X-Requested-With': 'XMLHttpRequest',
'Host': 'passport.fang.com',
'Origin': 'https://passport.fang.com',
'Referer': 'https://passport.fang.com/?backurl=http%3a%2f%2fmy.fang.com%2f',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
data = {
'uid': uid,
'pwd': encrypted_password,
'Service': 'soufun-passport-web',
'AutoLogin': 1
}
response = session.post(url=login_url, data=data, headers=headers)
print(response.json())


def main():
# 16521689404
uid = input('请输入登录账号:')
pwd = input('请输入登录密码:')
rsa_key = get_key_to_encode()
encrypted_pwd = get_encrypted_password(rsa_key, pwd)
login(encrypted_pwd, uid)


if __name__ == '__main__':
main()

4. 案例实战

说明

1
本教程为教学使用,不承担任何风险,请自觉遵守法律法规 -- 微流云平台

4.1 逆向参数

  • 该案例是多层嵌套加密
1
2
3
地址:https://www.wei-liu.com/user/login.html

password: "ZfYhxYg6V6STQDo8FwTCeqpmtsWjh73R/nIotCh0RnkuJMgd7e72U+JVo/XQ6sY3qEVd+J+d6D

4.2 逆向分析

点击登录会加载2个地址

地址1:https://api.wei-liu.com/api/v1/Token/code

地址2:https://api.wei-liu.com/api/v1/Token

总结:地址2里面有携带参数 地址1返回公钥item2参数

4.3 JavaScript分析

image-20220817153828723

总结:从这里可以看出密码是属于加密的

XHR断点分析https://api.wei-liu.com/api/v1/Token,可以在以下位置发现参数

image-20220817154256383

4.4 密钥分析

  • 拉动滑块,获取后台公钥 item1 和带有时间的item2

image-20220817153949071

4.5 算法还原

4.5.1 用python模拟请求测试
1
2
3
4
5
6
7
8
9
10
11
12
13
def get_miyue():
session = requests.session()
header = {
'具体参数'
}
session.headers = header
url = 'https://api.wei-liu.com/api/v1/Token/code'
res = session.get(url)
if res.status_code == 200:
res1 = res.json().get('data')
item1 = res1.get('item1')
item2 = res1.get('item2')
return item1,item2
4.5.2 JavaScript测试代码
1
2
3
4
5
6
7
8
// 这里的jsencrypt.js是原网站自定义的脚本
JSEncrypt = require('jsencrypt');
function encrypt(module,pubCode,pwd){
var encrypt = new JSEncrypt();
encrypt.setPublicKey(module);
var result = encrypt.encrypt(pubCode + pwd);
return result;
}
4.5.3 密码获取
1
2
js_code = open('psd.js','r').read()
result = execjs.compile(js_code).call('encrypt',item1,item2,jm_sha256(''))

SM国秘系列

说明

1
本教程仅供学习交流使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,请各学员自觉遵守相关法律法规。

1 算法简介

事实上从 2010 年开始,我国国家密码管理局就已经开始陆续发布了一系列国产加密算法,这其中就包括 SM1、SM2、SM3 、SM4、SM7、SM9、ZUC(祖冲之加密算法)等,SM 代表商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。SM1SM7 的算法不公开,其余算法都已成为 ISO/IEC 国际标准。

在这些国产加密算法中,SM2、SM3、SM4 三种加密算法是比较常见的,在爬取部分网站时,也可能会遇到这些算法,所以作为爬虫工程师是有必要了解一下这些算法的,如下图所示某网站就使用了 SM2SM4 加密算法:

image-20221125150819456

1.1 算法分类

算法名称 算法类别 应用领域 特点
SM1 对称(分组)加密算法 芯片 分组长度、密钥长度均为 128 比特
SM2 非对称(基于椭圆曲线 ECC)加密算法 数据加密 ECC 椭圆曲线密码机制 256 位,相比 RSA 处理速度快,消耗更少
SM3 散列(hash)函数算法 完整性校验 安全性及效率与 SHA-256 相当,压缩函数更复杂
SM4 对称(分组)加密算法 数据加密和局域网产品 分组长度、密钥长度均为 128 比特,计算轮数多
SM7 对称(分组)加密算法 非接触式 IC 卡 分组长度、密钥长度均为 128 比特
SM9 标识加密算法(IBE) 端对端离线安全通讯 加密强度等同于 3072 位密钥的 RSA 加密算法
ZUC 对称(序列)加密算法 移动通信 4G 网络 流密码
1.1.1 SM2 椭圆曲线公钥加密算法

SM2 为椭圆曲线(ECC)公钥加密算法,非对称加密,SM2 算法和 RSA 算法都是公钥加密算法,SM2 算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换 RSA 算法,在不少官方网站会见到此类加密算法。我国学者对椭圆曲线密码的研究从 20 世纪 80 年代开始,目前已取得不少成果,SM2 椭圆曲线公钥密码算法比 RSA 算法有以下优势:

SM2 RSA
安全性 256 位 SM2 强度已超过 RSA-2048 一般
算法结构 基本椭圆曲线(ECC) 基于特殊的可逆模幂运算
计算复杂度 完全指数级 亚指数级
存储空间(密钥长度) 192-256 bit 2048-4096 bit
秘钥生成速度 较 RSA 算法快百倍以上
解密加密速度 较快 一般
1.1.2SM4分组加密算法

SM4 为无线局域网标准的分组加密算法,对称加密,用于替代 DES/AES 等国际算法,SM4 算法与 AES 算法具有相同的密钥长度和分组长度,均为 128 位,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。加密算法与密钥扩展算法都采用 32 轮非线性迭代结构,解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。

SM4 DES AES
计算轮数 32 16(3DES 为 16*3) 10/12/14
密码部件 S 盒、非线性变换、线性变换、合成变换 标准算术和逻辑运算、先替换后置换,不含线性变换 S 盒、行移位变换、列混合变换、圈密钥加变换(AddRoundKey)

2 算法还原

2.1 JavaScript还原

在 JavaScript 中已有比较成熟的实现库,这里推荐 sm-crypto[4],目前支持 SM2、SM3 和 SM4,需要注意的是,SM2 非对称加密的结果由 C1、C2、C3 三部分组成,其中 C1 是生成随机数的计算出的椭圆曲线点,C2 是密文数据,C3 SM3 的摘要值,最开始的国密标准的结果是按 C1C2C3 顺序的,新标准的是按 C1C3C2 顺序存放的,sm-crypto 支持设置 cipherMode,也就是 C1C2C3 的排列顺序。

SM2 算法为例,实现如下(其他算法和详细用法可参考其官方文档):

SM2 加密(encrypt)和解密(decrypt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// npm install sm-crypto --save

const sm2 = require('sm-crypto').sm2

// 1 - C1C3C2,0 - C1C2C3,默认为1
const cipherMode = 1

// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey // 公钥
let privateKey = keypair.privateKey // 私钥

let msgString = "this is the data to be encrypted"
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果

console.log("encryptData: ", encryptData)
console.log("decryptData: ", decryptData)

2.2 python还原

在 Python 里面并没有比较官方的库来实现国密算法,这里仅列出了其中两个较为完善的第三方库,需要注意的是,SM1 和 SM7 算法不公开,目前大多库仅实现了 SM2、SM3、SM4 三种密算法。

若要使用 SM9 算法,可下载 gmssl-python 源码手动安装。

1
pip install gmssl
2.2.1 python实现功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from gmssl import sm2

# 16 进制的公钥和私钥
private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

# 待加密数据和加密后数据为 bytes 类型
data = b"this is the data to be encrypted"
enc_data = sm2_crypt.encrypt(data)
dec_data = sm2_crypt.decrypt(enc_data)

print('enc_data: ', enc_data.hex())
print('dec_data: ', dec_data)

3 实战讲解

说明: 此案例只为技术探讨

3.1 逆向目标

3.2 逆向过程

3.2.1 参数分析
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"data": {
"data": {
"encData": "3DFBCA4667B978F639BB23B95DCE4CC74CE34C33DC32F1068E9E23CA546C9EA8CCD20943B4DAE96380B41164D761DE9742C84A985FE3BABC31CB352556BB87C9C1495DB24A29AB6BC3A85AB7FCA00F338EE714ACFC4C924F01CF575098AEF16755EE6C2B00989F3CBDACE061021CBD577D334AF16D66C5F4C5E72412B915CB27"
},
"appCode": "T98HPCGN5ZVVQBS8LZQNOAEXVI9GYHKQ",
"version": "1.0.0",
"encType": "SM4",
"signType": "SM2",
"timestamp": 1669361336,
"signData": "nfFr5+lrQSmWeQ76hi59hE668wZNt9sU49qutYn3FKnuS8a+XWfOb/HILAyGZvtOGIjoJUG1BiP1DFfBo1G57g=="
}
}

来到公共查询页面,点击翻页,就可以看到一个 POST 请求,Request Payload 的参数部分是加密的,主要是 appCodeencDatasignData 参数,同样返回的数据也有这些参数,其加密解密方法是一样的,其中 encTypesignType 分别为 SM4 SM2,所以大概率这是国密算法了

此外请求头还有 x-tif-noncex-tif-signature 参数

1
2
x-tif-signature: 1d25fd180718ba4218f55f558a666b6c26acb1d14feaa9c109c11d0435d99ba8
x-tif-nonce: R6IbYccR
3.2.2 JavaScript分析

直接全局搜索 encDatasignData,搜索结果仅在 app.1691****.js 有,非常明显,上面还有设置 header 的地方,所有参数都在这里,埋下断点,可以看到这里就是加密的地方,如下图所示:

image-20221125154517110

头部参数处理

  • 解决 x-tif-noncex-tif-signature
1
2
链接:https://pan.baidu.com/s/1pZ9lGbCtDGj8JKlqtKjAsg 
提取码:1234

encData参数处理

image-20221125161739241

signdata处理

image-20221125162554041

4 实践

地址:https://idaas.yundasys.com:10443/frontend/login#/login

参数:passowrd