一 简介

正常访问目标小程序,抓包,发现请求参数和返回包都是加密的

img

这种情况下肯定没法进一步测试的,这块可以小程序反编译看看了。

二 加解密逻辑

1、使用unveilr反编译后,微信开发者工具打开:

image.png

直接打开时这种乱码的,可读性太差了。

可以使用在线JS美化工具( https://coding.tools/cn/javascript-beautifier ),将代码格式化下,这样读起来就方便多了。

img

2、观察数据包,会发现在请求和响应中有2个参数:“akey”和“data”:

image.png

3、直接在代码中全局检索一下关键字 “akey”:

image.png

4、可以看到和关键字关联的4个函数:

  • encodeAES_ECB
  • decodeAES_ECB
  • encodeRSA_module
  • decodeRSA_module

image.png

var u = {
getRandomStr: function(t) {
for (var e = [], n = 0; n < t; n++) {
var r = Math.floor(26 * Math.random()),
i = String.fromCharCode(97 + r);
e.push(i.toUpperCase())
}
return e.join("")
},
encodeAES_ECB: function(t, e) {
"string" != typeof t && (t = JSON.stringify(t));
var n = i.default.AES.encrypt(t, i.default.enc.Utf8.parse(e), {
mode: i.default.mode.ECB,
padding: i.default.pad.Pkcs7
});
return i.default.enc.Base64.stringify(n.ciphertext)
},
decodeAES_ECB: function(t, e) {
"string" != typeof t && (t = JSON.stringify(t));
var n = i.default.AES.decrypt(t, i.default.enc.Utf8.parse(e), {
mode: i.default.mode.ECB,
padding: i.default.pad.Pkcs7
});
return i.default.enc.Utf8.stringify(n)
},
encodeRSA_module: function(t, e, n) {
var r = new s.default;
return r.setPublic(e, n),
function(t) {
var e, n, r = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
i = "";
for (e = 0; e + 3 <= t.length; e += 3) n = parseInt(t.substring(e, e + 3), 16), i += r.charAt(n >> 6) + r.charAt(63 & n);
for (e + 1 == t.length ? (n = parseInt(t.substring(e, e + 1), 16), i += r.charAt(n << 2)) : e + 2 == t.length && (n = parseInt(t.substring(e, e + 2), 16), i += r.charAt(n >> 2) + r.charAt((3 & n) << 4));
(3 & i.length) > 0;) i += "=";
return i
}(r.encrypt(t))
},
decodeRSA_module: function(t, e, n, r) {
var i = new s.default;
return i.setPrivate(e, n, r), i.decrypt(function(t) {
var e, n = "",
r = 0,
i = 0;
for (e = 0; e < t.length && "=" != t.charAt(e); ++e) {
var o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(t.charAt(e));
o < 0 || (0 == r ? (n += a(o >> 2), i = 3 & o, r = 1) : 1 == r ? (n += a(i << 2 | o >> 4), i = 15 & o, r = 2) : 2 == r ? (n += a(i), n += a(o >> 2), i = 3 & o, r = 3) : (n += a(i << 2 | o >> 4), n += a(15 & o), r = 0))
}
return 1 == r && (n += a(i << 2)), n
}(t))
},
decodeRSA: function(t, e) {
var n = new o.default;
return n.setPrivateKey(e), n.decrypt(t)
}
};
e.default = u

加密逻辑

1、根据上面找到的函数encodeRSA_module,查找引用的位置。找到请求头中akey的生成逻辑

这块可以看到请求头中的 akey 生成逻辑:

①先生成一个16位的随机字符串作为AES加密的密钥

②调用c.default.encodeRSA_module函数,使用RSA公钥对生成的AES密钥加密

r = c.default.getRandomStr(16)
i = c.default.encodeRSA_module(r, s.default.getters.currentConfigs.modulus, s.default.getters.currentConfigs.publicExponent)

token: s.default.getters.currentToken
akey: i

image.png

2、根据函数encodeAES_ECB,查找引用位置。找到请求参数 data 的生成逻辑:

利用前面生成的随机字符串作为密钥,对传入的参数进行加密得到data

data: {
data: u ? s.default.encodeAES_ECB({
service: "adequacy.101",
json: i.default.getters._personalInfo
}, n) : {
service: "adequacy.101",
json: i.default.getters._personalInfo
}
},

image.png

、到这里基本就可以明文请求包中的加密逻辑了(数字信封):

  • akey:使用RSA公钥对随机的AES密钥进行加密
  • data:使用AES-ECB进行对称加密

解密逻辑

同理,和上面分析加密算法的时候一样,可以判断出解密逻辑如下:

  • akey:服务端返回的参数中带有AES的密钥,需要使用RSA私钥解密后才可得到
  • data:利用从akey中获取的AES密钥解密

image.png

三 AES密钥获取

前面说到AES密钥的加密和解密是通过RSA公私钥来实现的,那这块RSA公私钥从哪儿来呢???

1、查看代码中的encodeRSA_module函数的参数中提到了几个参数:

  • i.default.getters.currentConfigs.modulus
  • i.default.getters.currentConfigs.publicExponent

image.png

2、decodeRSA_module函数的参数中提到了几个参数:

  • s.default.getters.currentConfigs.modulus2
  • s.default.getters.currentConfigs.publicExponent
  • s.default.getters.currentConfigs.privateExponent

img

3、这几个参数具体是什么作用???

查看请求数据包中,发现在刚开始打开程序的时候,有一个返回包中是有携带这些参数的:

image.png

这快也就意味着,每次启动小程序,服务端都会返回RSA密钥的相关参数,用来在客户端生成RSA公私钥。

RSA模数和指数原理

这块放在后面详细说一下。。。

Python代码实现

# -*- utf-8 -*-
# @Time: 2024-06-12 9:44
# @Author: muhe Jing
# @File: RSA_gen.py
# @Software: PyCharm
from Crypto.PublicKey import RSA

# 输入已知的公钥模N和公钥指数E
N = int(
"a38f39068534ccf3187917604a857285333dee0cf01b6e26f78be0cb563763fa0b028d01bbe8e9bf6e5c707e40a7bdd237c7f6b2e93de1cbc8fb3e818112fa81e78a3614f812e4dcbed201aefb1cb7be6c8546be0f86d0d782ee47c028daf969e0131564171c7dd34b075a947bd388af07e62a64f23c11f314bb2e2cb929df0b",
16)
E = int("10001", 16)

# 创建RSA公钥对象
public_key = RSA.construct((N, E))

# 输出公钥
print("Public Key:")
print(public_key.export_key().decode())

N = int(
"9ee7f2b994fa16b86a1d01ad82663b24d488f2ea6ad2b60264badb6311906674048e70ebbae222744eb8b93d58258eb4dc30d11d4a9408e4ae8eb6d34a4e175a131eaa2484269041947c0d1e4c1652a0992ea952d590ef9836e0144197f30ef837d2df2ea0ba534f3f03ed88e8fe8311f7b6138b254e9b3395132db4f17ee0f3",
16)
E = int("10001", 16)
D = int(
"710bb39aa0c835a7a61dd296bc20bc4c9c427d05954f279a964c744c8b2f3e23a5262c6117074ed98d334edcbc3ba3c538ac25e7ffa946966134380e225b61b25bd38f1d6a2a29edfe2ab59707a6ebdcc31bcb7bf41a853d75a01630098fb3b22387a8fcb1785fd4850c831fa68d7cea93cff945112f0deba069c6716096f651",
16)

# 创建RSA私钥对象
private_key = RSA.construct((N, E, D))

# 输出私钥
print("Private Key:")
print(private_key.export_key().decode())

image.png

Public Key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjjzkGhTTM8xh5F2BKhXKFMz3u
DPAbbib3i+DLVjdj+gsCjQG76Om/blxwfkCnvdI3x/ay6T3hy8j7PoGBEvqB54o2
FPgS5Ny+0gGu+xy3vmyFRr4PhtDXgu5HwCja+WngExVkFxx900sHWpR704ivB+Yq
ZPI8EfMUuy4suSnfCwIDAQAB
-----END PUBLIC KEY-----


Private Key:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCe5/K5lPoWuGodAa2CZjsk1Ijy6mrStgJkuttjEZBmdASOcOu6
4iJ0Tri5PVgljrTcMNEdSpQI5K6OttNKThdaEx6qJIQmkEGUfA0eTBZSoJkuqVLV
kO+YNuAUQZfzDvg30t8uoLpTTz8D7Yjo/oMR97YTiyVOmzOVEy208X7g8wIDAQAB
AoGAcQuzmqDINaemHdKWvCC8TJxCfQWVTyealkx0TIsvPiOlJixhFwdO2Y0zTty8
O6PFOKwl5/+pRpZhNDgOIlthslvTjx1qKint/iq1lwem69zDG8t79BqFPXWgFjAJ
j7OyI4eo/LF4X9SFDIMfpo186pPP+UURLw3roGnGcWCW9lECQQDbSn4gpFNbWAqW
JmIqZB3qytyHOGCO3W0MPDUgzhkYXCdU0dewKZv9KCoPCJaqn9Zt6qvH+ijz6zvZ
lAp9yIz5AkEAuYG3VEUUIq/waI1OizsGLUxLXX/QoZ9M04OGqKZnZpivsfy7u5bU
zTd/3h2yWde2YCeGX6fETNEsWf9AUbM0SwJBANMUakGrSx1SqqYK/lTFl9KIQCvc
hX0fADdVwOH54EgFdEp9znqgIqlUvt9HqhdE0Zdt2rocPs1DMZHmoQ6gO6kCQDlt
6BSlohsu/q1Pec5uwnXJlddnV26Bw6YDpO+XPJGtmU7v4CquAklZ8jMQXIOl3Wcd
NWNtq77gTuu5su7YHMsCQGL61drNeJJ48F69q0Qv1+d1ANSNgZB2MuOidUilNOkr
J/b0eAKwUC2a3Rl6okFxTppgzsNYtZ4RpZST/2ftiDs=
-----END RSA PRIVATE KEY-----

使用RSA公私钥校验工具检测,发现生成的公私钥并不是一对的:

image.png

这也就意味着:加密和解密的密钥不同的两对:

①客户端->服务端:客户端加密的时候,使用RSA-1的公钥对随机对称密钥加密

②服务端:收到客户端密文后,使用RSA-1的私钥进行解密,获取到对称加密密钥,然后解密

③服务端->客户端:服务端使用RSA-2的公钥对响应体的密钥进行加密

④客户端:收到服务端密文,使用RSA-2的私钥进行解密,获取到AES的key,然后再对data进行解密。


所以,这块要实现在线小程序加解密就只能调试好微信小程序后,将AES密钥固定死,才可以。

但是,这块反编译后,在微信开发者工具里面小程序跑不起来,寄!

四 效果验证

利用模值和指数,生成了一个公钥和私钥。分别为:

RSA-1的公钥

RSA-2的私钥

所以,这块可以使用RSA-2的私钥来验证一下:

image.png

RSA解密akey获取到AES密钥

image.png

AES-ECB解密data获取返回包中明文

image.png

五 RSA模数和指数原理

这块详细的介绍和代码均参考了下面这篇文章:

https://blog.csdn.net/chenhao0568/article/details/136484183

一般的工具都是这种的,D、P、Q、DP、DQ、InvQ、N、E,这些参数都是用来生成RSA公钥和私钥需要

如果只想要知道公钥和私钥,这就需要相互转换

image.png

RSA 知道公钥模N 公钥指数E 算出公钥

当知道RSA公钥的模N和公钥指数E时,你可以使用Python中的rsa模块来计算RSA公钥

from Crypto.PublicKey import RSA

# 输入已知的公钥模N和公钥指数E
N = int(input("Enter the modulus (N): "))
E = int(input("Enter the public exponent (E): "))

# 创建RSA公钥对象
public_key = RSA.construct((N, E))

# 输出公钥
print("Public Key:")
print(public_key.export_key().decode())

上面这块需要注意一下,输入模和指数的时候,要求输入的是10进制数字,如果是非十进制,比如16进制,需要转换一下

N = int(input("Enter the modulus (N): "),16)
E = int(input("Enter the public exponent (E): "),16)

img

在这个示例中,使用了Crypto模块中的RSA类来构建RSA公钥对象,并将输入的模N和公钥指数E传递给RSA.construct()方法。最后,我们输出了生成的公钥。

反过来:知道公钥,计算公钥的模 公钥的指数N

如果已经有了RSA公钥,但不知道公钥模N和公钥指数E,你可以通过以下方式从公钥中提取这些参数。Python的 Crypto 模块提供了这样的功能。

from Crypto.PublicKey import RSA

# 输入已知的公钥
public_key_pem = input("Enter the public key in PEM format: ")

# 从PEM格式的公钥中提取RSA对象
public_key = RSA.import_key(public_key_pem)

# 获取公钥模N和公钥指数E
N = public_key.n
E = public_key.e

# 输出公钥模N和公钥指数E
print("Modulus (N):", N)
print("Public Exponent (E):", E)

img

在这个示例中,首先输入了PEM格式的公钥字符串,然后使用 RSA.import_key() 方法将其转换为RSA对象。接着,从RSA对象中提取了模N和公钥指数E,并将它们输出。

RSA知道私钥参数(P、Q、E、D)算出私钥

可以使用Python中的rsa模块来构建RSA私钥对象。

from Crypto.PublicKey import RSA

# 输入已知的私钥参数
P = int(input("Enter the prime factor P: "))
Q = int(input("Enter the prime factor Q: "))
E = int(input("Enter the public exponent (E): "))
D = int(input("Enter the private exponent (D): "))

# 计算模N
N = P * Q

# 创建RSA私钥对象
private_key = RSA.construct((N, E, D, P, Q))

# 输出私钥
print("Private Key:")
print(private_key.export_key().decode())

在这个示例中,首先输入了私钥的参数:两个素数因子P和Q、公钥指数E以及私钥指数D。然后,计算了模N,并使用RSA.construct()方法构建了RSA私钥对象。最后,输出了生成的私钥。

  • P 和 Q:这两个质数用于计算模数 N,它们是RSA算法的基础。模数 N 是公钥和私钥的一个重要组成部分,通常非常大(例如,2048位或更大)。
  • E:公钥指数,通常是一个较小的整数,且与 (P-1) * (Q-1) 互质。公钥指数用于加密数据。
  • D:私钥指数,与公钥指数 E 和模数 N 有关,用于解密数据。私钥指数是一个大整数,它是通过扩展欧几里得算法计算得出的,确保 D * E % ((P-1) * (Q-1)) 等于 1。

img

知道私钥,算出D,P等所有参数

如果已经有了RSA私钥,但不知道私钥的所有参数(如P、Q、D等),可以从私钥中提取这些参数。Python的Crypto模块提供了这样的功能。

from Crypto.PublicKey import RSA

# 输入已知的私钥
private_key_pem = input("Enter the private key in PEM format: ")

# 从PEM格式的私钥中提取RSA对象
private_key = RSA.import_key(private_key_pem)

# 获取私钥参数
N = private_key.n
E = private_key.e
D = private_key.d
P = private_key.p
Q = private_key.q

# 输出私钥参数
print("Modulus (N):", N)
print("Public Exponent (E):", E)
print("Private Exponent (D):", D)
print("Prime Factor P:", P)
print("Prime Factor Q:", Q)

在这个示例中,首先输入了PEM格式的私钥字符串,然后使用 RSA.import_key() 方法将其转换为RSA对象。接着,从RSA对象中提取了模N、公钥指数E、私钥指数D以及素数因子P和Q,并将它们输出。

img

生成一对RSA密钥对

from Crypto.PublicKey import RSA

# 生成RSA密钥对
key = RSA.generate(2048) # 生成2048位的RSA密钥对,可以根据需要选择密钥长度

# 导出公钥和私钥
public_key = key.publickey().export_key()
private_key = key.export_key()

# 输出公钥和私钥
print("Public Key:")
print(public_key.decode())
print("\nPrivate Key:")
print(private_key.decode())

在这个示例中,使用了Crypto模块中的RSA.generate()函数来生成一对2048位的RSA密钥对。然后,我们分别导出公钥和私钥,并打印它们。

RSA生成一对公私钥,要输出格式为16进制

from Crypto.PublicKey import RSA

# 生成RSA密钥对
key_pair = RSA.generate(2048)

# 获取公钥和私钥的16进制字符格式
public_key_hex = key_pair.publickey().export_key(format='DER').hex()
private_key_hex = key_pair.export_key(format='DER').hex()

# 输出公钥和私钥的16进制字符格式
print("Public Key (Hex):")
print(public_key_hex)

print("\nPrivate Key (Hex):")
print(private_key_hex)

在这个示例中,使用export_key()函数并指定format=’DER’参数来获取公钥和私钥的DER编码格式,然后使用.hex()方法将字节串转换为16进制字符格式,并输出它们。

RSA加密、解密、签名、验签示例

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256

def generate_key_pair():
key = RSA.generate(2048)
return key

def encrypt(message, public_key):
cipher = PKCS1_OAEP.new(public_key)
ciphertext = cipher.encrypt(message.encode())
return ciphertext

def decrypt(ciphertext, private_key):
cipher = PKCS1_OAEP.new(private_key)
plaintext = cipher.decrypt(ciphertext)
return plaintext.decode()

def sign(message, private_key):
hash_obj = SHA256.new(message.encode())
signature = pkcs1_15.new(private_key).sign(hash_obj)
return signature

def verify(message, signature, public_key):
hash_obj = SHA256.new(message.encode())
try:
pkcs1_15.new(public_key).verify(hash_obj, signature)
return True
except (ValueError, TypeError):
return False

# 示例
message = "Hello, world!"
key_pair = generate_key_pair()
public_key = key_pair.publickey()
private_key = key_pair

# 加密
ciphertext = encrypt(message, public_key)
print("Ciphertext:", ciphertext.hex())

# 解密
plaintext = decrypt(ciphertext, private_key)
print("Plaintext:", plaintext)

# 签名
signature = sign(message, private_key)
print("Signature:", signature.hex())

# 验证签名
is_valid = verify(message, signature, public_key)
print("Signature is valid:", is_valid)