Skip to content

加密算法

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

有时候网页请求的参数和服务器的响应内容都是经过加密,由于 HTML、CSS 都不具备加密解密的功能,那么浏览器只能通过 JS 进行加密解密,然后将加密的参数进行请求,或者将解密的数据通过前端进行展示。我们所编写的爬虫,如果不能对参数进行加密,直接请求接口,是请求不到数据的。同样的,服务器返回的是加密数据,如果不能对其进行解密,那么服务器响应的数据也就没有任何作用了,因此学习数据的加密解密也是 JS 逆向的一项重要技能。

安装加密库

数据的加解密过程中需要使用到 pycryptodome 第三方库,通过它我们可以实现各种加密算法,安装命令如下:

python
pip install pycryptodome

建议

若使用报错,进入包路径 \lib\site-packages 目录下,找到crypto文件夹名称改为Crypto(小写c改为大写C)。

凯撒加密

凯撒加密:通过对应字符的替换,实现对明文进行简单加密的一种方式。

python
# 凯撒加密方式的对照表(把每个字母向后移3个位置,偏移量为3)
abcdefghijklmnopqrstuvwxyz  # 明文(对照表)
defghijklmnopqrstuvwxyzabc  # 密文(对照表)
# 凯撒密码这里的解密就是把每个字母向前移3个位置(偏移量为3)
dwwdfn dw gdzq  # 密文
attack at dawn  # 明文

实现凯撒加密,我们可以使用字符串中的 maketranstranslate 方法:

python
# 明文
message = 'attack at dawn'
# maketrans生成字符串转换的对照表
table = str.maketrans(
    'abcdefghijklmnopqrstuvwxyz', 
    'defghijklmnopqrstuvwxyzabc'
)
# translate转译字符串输出密文
print(message.translate(table))  # 输出:dwwdfn dw gdzq

对称加密

对称加密:信息的加密和解密都使用同一个密钥,也称为单密钥加密。如果信息、密钥其中之一发生变化,那么加密出来的密文也会发生变化;如果信息、密钥都没变化,那么加密出来的密文也不会改变。

加密模式

对称加密算法通常拥有不同的加密模式,不同模式加密出来的密文不一样,这里介绍几种常用模式。

ECB电子密码本

优点:简单、有利于并行计算、误差不会被传送;

缺点:不能隐藏明文的模式、可能对明文进行主动攻击。

1ECB

CBC密码分组链接

优点:不容易主动攻击、安全性好于 ECB、适合传输长度长的报文;

缺点:不利于并行计算、误差传递、需要初始化向量IV。

2CBC

CFB密文反馈

优点:隐藏了明文模式、分组密码转化为流模式、可以及时加密传送小于分组的数据;

缺点:不利于并行计算、一个明文单元损坏影响多个单元、唯一的 IV。

3CFB

OFB输出反馈

优点:隐藏了明文模式、分组密码转化为流模式、可以及时加密传送小于分组的数据;

缺点:不利于并行计算、对明文的主动攻击是可能的、一个明文单元损坏影响多个单元。

4OFB

冗余填充

**加密算法在加密数据时,会对数据进行一个冗余操作,这样做虽牺牲了数据长度,但更方便于数据的加密,以及更为灵活透明的去解包数据。但我们要知道数据加密,并不是一下子将全部数据加密,而是将数据切分成固定大小 blockSize,然后开始并行计算,对每个数据块进行加密。**在切分的过程中会出现两种情况:

  1. 数据的长度不是 blockSize 的整数倍时,就会对最后一块数据进行冗余填充,以达到 blockSize 的大小;
  2. 数据的长度是 blockSize 的整数倍时,填充的长度反而是最大的,要填充 blockSize 长度的字符在数据尾部,原因在于接收端总是通过数据包的最后一个字符得到填充的数据长度。

PKCS7填充

**PKCS7 是当下各大加密算法都遵循的数据填充算法,填充长度是 1~255 bytes。**假如 blockSize 为 8bytes,即数据始终会被切割成 8 个字节的数据块,填充规律如下:

# 一个数据块
h<0x07><0x07><0x07><0x07><0x07><0x07><0x07>
he<0x06><0x06><0x06><0x06><0x06><0x06>
hel<0x05><0x05><0x05><0x05><0x05>
hell<0x04><0x04><0x04><0x04>
hello<0x03><0x03><0x03>
hello <0x02><0x02>
hello w<0x01>
# 两个数据块
hello wo<0x08><0x08><0x08><0x08><0x08><0x08><0x08><0x08>
hello wor<0x07><0x07><0x07><0x07><0x07><0x07><0x07>
hello worl<0x06><0x06><0x06><0x06><0x06><0x06>
hello world<0x05><0x05><0x05><0x05><0x05>

明白上面的填充规律,我们可以总结出以下三点经验:

  1. 随着填充长度 paddingSize 改变,填充的字符 paddingChar 也在发生变化,对应关系为 paddingChar = chr(paddingSize) 即用长度对应的 ASCII 码字符对数据进行冗余填充。
  2. 如果已经满足了8的整倍数,仍然需要在尾部填充8个字节,并且内容是 0x08,因为拆包时会按协议取最后一个字节所表征的数值长度作为数据填充长度,如果因真实数据长度恰好为 8 的整数倍而不进行填充,则拆包时会导致真实数据丢失。
  3. 当我们拿到一串 PKCS7 填充的数据时,取其最后一个字符 paddingChar,此字符的 ASCII 码的十进制 ord(paddingChar) 就是数据的填充长度paddingSize,还原数据时去掉填充长度即可。

PKCS5填充

PKCS5 填充是 PKCS7 填充的一个子集,当PKCS7填充的 blockSize 为 8bytes 时,PKCS5 与 PKCS7 填充是一样的,其他情况下,PKCS5 与 PKCS7 填充都是不同的。

Zero填充

zeropadding 填充方式,它是使用“0”作为填充数据的填充方式,也就是说在分组时,最后一组明文的长度没有达到分组长度,那么就用“0”来补足。

DES数据加密标准

**DES 数据加密标准(Data Encryption Standard),是一种使用密钥加密的对称加密算法。它的加密算法和解密算法几乎是一模一样的,仅仅是密钥的使用顺序不同。在输入输出上如果是十六进制的字符,则要求是 16 的倍数。**具体使用的参数如下:

  • Data,数据,由于 DES 的 block_size 长度为 8bytes,因此数据将会被冗余为 8 个字节的倍数;
  • Key,密钥,由于 DES 的 key_size 长度为 8bytes,因此密钥长度必须为 8 个字节(逆向常用);
  • Mode,模式,DES 算法的运行模式,常见的有 ECB 模式、CBC 模式;
  • IV,偏移量,长度必须为 8 个字节,只有 ECB 模式不需要。

20190602192904220

ECB模式加解密

ECB(电子密码本方式)是最简单的加密方式,加密前根据加密块大小将数据分成若干块,最后一段不足一个加密块大小时,按要求补足一个加密块大小进行计算,每块使用相同的密钥单独加密,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。解密同理。相对其他模式没有偏移量的设置,简单点,安全性差点。

python
import base64
import binascii
# 导入DES加密算法
from Crypto.Cipher import DES
# 从Crypto.Cipher模块中导入pad填充
from Crypto.Util.Padding import pad

# 加密函数
def DES_Encrypt(data, key):
    # 设置ECB模式
    des = DES.new(key.encode(), DES.MODE_ECB)
    # 设置填充方式为pkcs7填充
    pad_pkcs7 = pad(data.encode(), DES.block_size, style='pkcs7')
    # 加密补位后的明文
    encrypt_data = des.encrypt(pad_pkcs7)
    # 返回bytes对象(密文)
    return encrypt_data

# 解密函数
def DES_Decrypt(data, key):
    # 设置ECB模式
    des = DES.new(key.encode(), DES.MODE_ECB)
    # 解密解码后的密文
    decrypt_data = des.decrypt(data)
    # 将解密的byte字符串解码为
    detext = decrypt_data.decode()
    # 因为在加密函数中明文补位的原因,这里要返回等长度解密后的明文(要理解填充补位)
    return detext[:-ord(detext[-1])]

# 明文"Hero never die!",密匙"DEa412C2",对象bytes(密文)
data = "Hero never die!"
key = "DEa412C2"
encrypt_data = DES_Encrypt(data, key)
# 密文编码一:十六进制字符串
hex_en_data = binascii.b2a_hex(encrypt_data).decode()
print(hex_en_data)  # 输出:b61f67d25a9a2d0f499500f567f72346
hex_de_data = DES_Decrypt(binascii.a2b_hex(hex_en_data), key)
print(hex_de_data)  # 输出:Hero never die!
# 密文编码二:标准base64编码字符串
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)  # 输出:th9n0lqaLQ9JlQD1Z/cjRg==
utf_de_data = DES_Decrypt(base64.standard_b64decode(utf_en_data), key)
print(utf_de_data)  # 输出:Hero never die!

CBC模式加解密

CBC(密码分组链接方式)相比 ECB(电子密码本方式)多了一个 IV(偏移量),也叫初始化向量,在 CBC 模式在对明文分组加密时,会将明文分组与前一个密文分组进行 XOR 运算(即异或运算),但是加密第一个明文分组时不存在“前一个密文分组”,因此需要事先准备一个与分组长度相等的比特序列来代替,这个比特序列就是偏移量。

python
import base64
import binascii
# 导入DES加密算法
from Crypto.Cipher import DES
# 从Crypto.Cipher模块中导入pad填充
from Crypto.Util.Padding import pad

# 加密函数
def DES_Encrypt(data, key, iv):
    # 设置CBC模式
    des = DES.new(key.encode(), DES.MODE_CBC, iv.encode())
    # 设置填充方式为pkcs7填充
    pad_pkcs7 = pad(data.encode(), DES.block_size, style='pkcs7')
    # 加密补位后的明文
    encrypt_data = des.encrypt(pad_pkcs7)
    # 返回bytes对象(密文)
    return encrypt_data

# 解密函数
def DES_Decrypt(data, key, iv):
    # 设置CBC模式
    des = DES.new(key.encode(), DES.MODE_CBC, iv.encode())
    # 解密解码后的密文
    decrypt_data = des.decrypt(data)
    # 将解密的byte字符串解码为
    detext = decrypt_data.decode()
    # 因为在加密函数中明文补位的原因,这里要返回等长度解密后的明文(要理解填充补位)
    return detext[:-ord(detext[-1])]

# 明文"Hero never die!",密匙"DEa412C2",偏移量"01020304",对象bytes(密文)
data = "Hero never die!"
key = "DEa412C2"
iv = "01020304"
encrypt_data = DES_Encrypt(data, key, iv)
# 密文编码一:十六进制字符串
hex_en_data = binascii.b2a_hex(encrypt_data).decode()
print(hex_en_data)  # 输出:2d3c082db959e31253a4bedc00a84e9a
hex_de_data = DES_Decrypt(binascii.a2b_hex(hex_en_data), key, iv)
print(hex_de_data)  # 输出:Hero never die!
# 密文编码二:标准base64编码字符串
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)  # 输出:LTwILblZ4xJTpL7cAKhOmg==
utf_de_data = DES_Decrypt(base64.standard_b64decode(utf_en_data), key, iv)
print(utf_de_data)  # 输出:Hero never die!

AES高级加密标准

**AES 高级加密标准(Advanced Encryption Standard),是一种目前比较流行的对称加密算法。它有三种加密等级 AES-128(默认)、AES-192、AES-256,分别对应三种密钥长度 128bits(16字节,逆向常用)、192bits(24字节)、256bits(32字节),密钥越长,安全性越高,加解密花费时间也越长。**具体使用的参数如下:

  • Data,数据,由于 AES 的 block_size 长度为16bytes,因此数据将会被冗余为 16 个字节的倍数;
  • Key,密钥,如果是 AES-128 加密,那么 key_size 长度为 16bytes,因此密钥长度必须为 16 个字节(逆向常用);
  • Mode,模式,AES 算法的运行模式,常见的有 ECB 模式、CBC 模式;
  • IV,偏移量,长度必须为 16 个字节,除 ECB 模式外的其他模式都需要。

20190615171121865

ECB模式加解密

python
import base64
import binascii
# 导入AES加密算法
from Crypto.Cipher import AES
# 从Crypto.Cipher模块中导入pad填充
from Crypto.Util.Padding import pad

# 加密函数
def AES_Encrypt(data, key):
    # 设置ECB模式
    aes = AES.new(key.encode(), AES.MODE_ECB)
    # 设置填充方式为pkcs7填充
    pad_pkcs7 = pad(data.encode(), AES.block_size, style='pkcs7')
    # 加密补位后的明文
    encrypt_data = aes.encrypt(pad_pkcs7)
    # 返回bytes对象(密文)
    return encrypt_data

# 解密函数
def AES_Decrypt(data, key):
    # 设置ECB模式
    aes = AES.new(key.encode(), AES.MODE_ECB)
    # 解密解码后的密文
    decrypt_data = aes.decrypt(data)
    # 将解密的byte字符串解码为
    detext = decrypt_data.decode()
    # 因为在加密函数中明文补位的原因,这里要返回等长度解密后的明文(要理解填充补位)
    return detext[:-ord(detext[-1])]

# 明文"Hero never die!",密匙"0123456789ABCDEF",对象bytes(密文)
data = "Hero never die!"
key = "0123456789ABCDEF"
# 密文编码一:十六进制字符串
encrypt_data = AES_Encrypt(data, key)
hex_en_data = binascii.b2a_hex(encrypt_data).decode()
print(hex_en_data)  # 输出:77b7573e8472526eaf82aa519445cbab
hex_de_data = AES_Decrypt(binascii.a2b_hex(hex_en_data), key)
print(hex_de_data)  # 输出:Hero never die!
# 密文编码二:标准base64编码字符串
encrypt_data = AES_Encrypt(data, key)
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)  # 输出:d7dXPoRyUm6vgqpRlEXLqw==
utf_de_data = AES_Decrypt(base64.standard_b64decode(utf_en_data), key)
print(utf_de_data)  # 输出:Hero never die!

CBC模式加解密

python
import base64
import binascii
# 导入AES加密算法
from Crypto.Cipher import AES
# 从Crypto.Cipher模块中导入pad填充
from Crypto.Util.Padding import pad

# 加密函数
def AES_Encrypt(data, key, iv):
    # 设置CBC模式
    aes = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    # 设置填充方式为pkcs7填充
    pad_pkcs7 = pad(data.encode(), AES.block_size, style='pkcs7')
    # 加密补位后的明文
    encrypt_data = aes.encrypt(pad_pkcs7)
    # 返回bytes对象(密文)
    return encrypt_data

# 解密函数
def AES_Decrypt(data, key, iv):
    # 设置CBC模式
    aes = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    # 解密解码后的密文
    decrypt_data = aes.decrypt(data)
    # 将解密的byte字符串解码为
    detext = decrypt_data.decode()
    # 因为在加密函数中明文补位的原因,这里要返回等长度解密后的明文(要理解填充补位)
    return detext[:-ord(detext[-1])]

# 明文"Hero never die!",密匙"0123456789ABCDEF",偏移量"0102030405060708",对象bytes(密文)
data = "Hero never die!"
key = "0123456789ABCDEF"
iv = "0102030405060708"
# 密文编码一:十六进制字符串
encrypt_data = AES_Encrypt(data, key, iv)
hex_en_data = binascii.b2a_hex(encrypt_data).decode()
print(hex_en_data)  # 输出:23ca073e80a3d5b7020505a402bca22c
hex_de_data = AES_Decrypt(binascii.a2b_hex(hex_en_data), key, iv)
print(hex_de_data)  # 输出:Hero never die!
# 密文编码二:标准base64编码字符串
encrypt_data = AES_Encrypt(data, key, iv)
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)  # 输出:I8oHPoCj1bcCBQWkAryiLA==
utf_de_data = AES_Decrypt(base64.standard_b64decode(utf_en_data), key, iv)
print(utf_de_data)  # 输出:Hero never die!

Crypto.Util.Padding.pad 函数实际上不支持 'zero' 作为填充样式。它只支持两种填充方式:'pkcs7''x923'。如果你需要实现 0 填充(Zero Padding),需要自己编写逻辑,或者通过其他方式实现。

zero填充

python
import base64
from datetime import datetime
from Crypto.Cipher import AES

# Zero Padding 函数
def zero_pad(data, block_size):
    padding_length = block_size - (len(data) % block_size)
    return data + b'\x00' * padding_length

# 加密函数
def AES_Encrypt(data, key, iv):
    aes = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    padded_data = zero_pad(data.encode(), AES.block_size)
    encrypt_data = aes.encrypt(padded_data)
    return encrypt_data


pwd = "你的密码"
key = "9513724860215995"
now_date = datetime.now().strftime('%Y%m%d')
iv = f"dam{now_date}ab215"
encrypt_data = AES_Encrypt(pwd, key, iv)
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)

RC4流加密算法簇

**RC4 是一种在电子信息领域的对称加密算法,用于无线通信网络,是一种电子密码。**该算法的特点如下:

  1. 算法简单,运行速度快;
  2. 密钥长度是可变的,密钥长度范围为1-256字节。
python
import base64
import binascii
# 导入ARC4加密算法
from Crypto.Cipher import ARC4

# 加密函数
def RC4_Encrypt(data, key):
    rc4 = ARC4.new(key.encode())
    text = rc4.encrypt(data.encode())
    return text

# 解密函数
def RC4_Decrypt(data, key):
    rc4 = ARC4.new(key.encode())
    text = rc4.decrypt(data)
    return text.decode()

# 明文"Hero never die!",密匙"0123456789ABCDEF",对象bytes(密文)
data = "Hero never die!"
key = "0123456789ABCDEF"
# 密文编码一:十六进制字符串
encrypt_data = RC4_Encrypt(data, key)
hex_en_data = binascii.b2a_hex(encrypt_data).decode()
print(hex_en_data)  # 输出:a616f0ed7151b797c45727fd4987c6
hex_de_data = RC4_Decrypt(binascii.a2b_hex(hex_en_data), key)
print(hex_de_data)  # 输出:Hero never die!
# 密文编码二:标准base64编码字符串
encrypt_data = RC4_Encrypt(data, key)
utf_en_data = base64.standard_b64encode(encrypt_data).decode()
print(utf_en_data)  # 输出:phbw7XFRt5fEVyf9SYfG
utf_de_data = RC4_Decrypt(base64.standard_b64decode(utf_en_data), key)
print(utf_de_data)  # 输出:Hero never die!

非对称加密

非对称加密:信息的加密和解密使用两把不同的密钥,其中加密过程使用的密钥被称之为“公钥”,解密过程使用的密钥被称之为“私钥”。在日常生活中我们可以把“公钥”告诉其他人,其他人通过我们的“公钥”来加密发给我们的信息,我们再通过”私钥“解密信息,因此“私钥”就需要我们自己保护好,不要随意泄露。另外,相同的内容使用同一个“公钥”所加密出来的密文内容是会改变的,这是因为在加密过程中使用随机值来防止用统计学方法破解,所以密文内容不会是固定的,但是都能通过同一个配套的“私钥”解密出原始数据,所以非对称加密在算法复杂度相比对称加密算法要高,因此在时间上的开销往往比对称加密要大。

RSA加密算法

RSA(Rivest-Shamir-Adleman)是目前应用最广泛非对称加密算法,其中公钥是公开的,任何人都可以使用公钥加密信息,私钥是私密的,只有私钥的持有者才能解密。

公钥样式:-----BEGIN PUBLIC KEY----- 为起始,以 -----END PUBLIC KEY----- 为结束,中间内容为公钥。

20220801162405

私钥样式:-----BEGIN RSA PRIVATE KEY----- 为起始,以 -----END RSA PRIVATE KEY----- 为结束,中间内容为私钥。

20220801162046

[!ATTENTION]

无论是公钥还是私钥,里面的内容都不要去做任何的修改。

加解密函数

使用 pycryptodome 第三方库实现 RSA 加解密代码如下:

python
import base64
from Crypto.Cipher import PKCS1_v1_5
from Crypto import Random
# 导入RSA加密算法
from Crypto.PublicKey import RSA

# 生成密钥对
def create_rsa_pair(is_save=False):
    '''
    生成RSA密钥对
    :param is_save: default:False
    :return: public_key, private_key
    '''
    key = RSA.generate(2048)  # 实例化一个RSA对象,使用2048位字节的密钥长度。
    private_key = key.exportKey()  # 生成私钥
    public_key = key.publickey().exportKey()  # 生成公钥
    if is_save:  # 是否保存为文件
        with open("crypto_private_key.pem", "wb") as f:  # 保存私钥
            f.write(private_key)
        with open("crypto_public_key.pem", "wb") as f:  # 保存公钥
            f.write(public_key)
    return public_key, private_key

# 加密
def encryption(text: str, public_key: bytes):
    # 字符串指定编码(转为bytes)
    text = text.encode('utf-8')
    # 构建公钥对象
    cipher_public = PKCS1_v1_5.new(RSA.importKey(public_key))
    # 加密(bytes)
    text_encrypted = cipher_public.encrypt(text)
    # base64编码,并转为字符串
    text_encrypted_base64 = base64.b64encode(text_encrypted).decode()
    return text_encrypted_base64

# 解密
def decryption(text_encrypted_base64: str, private_key: bytes):
    # 字符串指定编码(转为bytes)
    text_encrypted_base64 = text_encrypted_base64.encode('utf-8')
    # base64解码
    text_encrypted = base64.b64decode(text_encrypted_base64)
    # 构建私钥对象
    cipher_private = PKCS1_v1_5.new(RSA.importKey(private_key))
    # 解密(bytes)
    text_decrypted = cipher_private.decrypt(text_encrypted, Random.new().read)
    # 解码为字符串
    text_decrypted = text_decrypted.decode()
    return text_decrypted

if __name__ == '__main__':
    # 生成密钥对
    public_key, private_key = create_rsa_pair()
    print('公钥:', public_key.decode())    # 公钥:-----BEGIN RSA PUBLIC KEY-----...
    print('私钥:', private_key.decode())   # 私钥:-----BEGIN RSA PRIVATE KEY-----...
    # 加密
    text = '123456'
    text_encrypted_base64 = encryption(text, public_key)
    print('密文:', text_encrypted_base64)  # 密文:sYm...
    # 解密
    text_decrypted = decryption(text_encrypted_base64, private_key)
    print('明文:', text_decrypted)         # 明文:123456

建议

Crypto.Cipher 中导入 PKCS1_v1_5,导入时有时要重命名一下,如 PKCS1_cipher ,因为在另一个模块 Crypto.Signature 中也有同名的类 PKCS1_v1_5,同时使用时不重命名会造成冲突。

指数和模数

在 RSA 中,公钥和私钥都包含模数(modulus)和指数(exponent)两个参数。

  • 模数(Modulus): 一个大的整数,通常用于密钥的长度,用于确定加密和解密操作中的数学运算。
  • 指数(Exponent): 一个小的整数,通常是固定的值,例如 65537,用于进行加密和解密的数学运算。

上面的例子中使用 RSA.generate() 实例化一个 RSA 对象,实际这个方法有三个属性,具体如下:

python
# 导入RSA加密算法
from Crypto.PublicKey import RSA

# bits字节大小,randfunc随机函数,默认为Crypto.Random.get_random_bytes,e指数,默认为65537(可选3、17、65537最常用)
key = RSA.generate(bits=2048, randfunc=None, e=65537)  # 实例化一个RSA对象,使用2048位字节的密钥长度。
print('公钥对象:', key.publickey().exportKey)
print('私钥对象:', key.exportKey)
'''
公钥对象:<bound method RsaKey.export_key of RsaKey(n=18922159274499885600929041255183949822227394168583171266402575131958925639604332292336293528656366183668040666093593871337181737399697134923003149471127809352527697921854587028527888006517942522290078588938011692313567422614347646537749346266480161373378979884290567660771602583427259242435199952437359062593570864338942856008467184549623567731098880372890972342035914241077292680522963690881344923947840970327677427603354993631032954433137903562856131205497561860199843910995454496654769322055071640340055569053280602167850275382805429796067496413480527472283652898460183107487166256543000263998191615054214402974493, e=65537)>
私钥对象:<bound method RsaKey.export_key of RsaKey(n=18922159274499885600929041255183949822227394168583171266402575131958925639604332292336293528656366183668040666093593871337181737399697134923003149471127809352527697921854587028527888006517942522290078588938011692313567422614347646537749346266480161373378979884290567660771602583427259242435199952437359062593570864338942856008467184549623567731098880372890972342035914241077292680522963690881344923947840970327677427603354993631032954433137903562856131205497561860199843910995454496654769322055071640340055569053280602167850275382805429796067496413480527472283652898460183107487166256543000263998191615054214402974493, e=65537, d=6577729108620418904746409171545855697544661701766358054094381290817221476745439954285448512593639232743721904495845943158882376999861149088543231323545993143862032348988986399803505570357077170801418594277824471280157666997269757288600418185802692164858323080456958854505068130302574424541232056951461222881737986153273015469519223855052658432316393773206737788451066087764042382408080838907198547397724587590210993705950951724555740827819695732282899492915811808636781062782006893565832067903480688612142065098715522655955369123446694693419384533473400352658021658652072453560514599878719597015953833949667393647881, p=134734925295200090886760285313947330408265704475295216733097276002787121608830089754525322894801555861629490922275736321337962884110610544411691914495010208021467405606649098286668337211502204518364158963398417842139057831082451776356939559539182895806376256972329271873419635982216273460341341223368816362547, q=140439898809028280591513115657786686410942176332015706563570270587979107510464303459315817328841525918086480509237122151578514214604529467982478792924088018492628085076963339566242665372477613787894793446281130497981122465983094334229641913626787517740169914989702997715605749160044537037957094322490219326319, u=74428425489380209990334990564351040803658428117557312711263978656405433873284257510851788203259469385825717960430416700713788251382635018944241879998374899115297494343052528498117817879442223848054494100921384606402564631826633108040567690027506276695840170586936661014097073326995674223573137276537265021053)>
'''

提醒

有人会问,为什么这里输出的公钥和私钥全是数值,没有 "-----BEGIN RSA PUBLIC KEY-----" 和 "-----BEGIN RSA PRIVATE KEY-----" 开头?这是因为输出的是密钥对象,而不是包含 PEM 格式的字符串,密钥对象包含了公钥和私钥的数值,因此在打印时显示的是这些数字。可以使用 密钥对象.save_pkcs1().decode() 将公钥和私钥转换为 PEM 格式的字符串。

**从上面例子的输出结果中可以看到,公钥对象由 n=1892... 大整数和 e=65537 小整数组成,它们对应的就是“模数”和“指数”,在私钥对象中也可以看到相同的 n=1892... 大整数和 e=65537 小整数,说明私钥对象和公钥对象使用同样的“模数”和“指数”,不过在私钥对象中还会包含一些私钥因子参数。在爬虫逆向 JS 代码的过程中有时会遇到 setPublic("00C...", "10001") 生成公钥来加密内容,其实这里的"10001"(有时会是"010001",都一样)就是常用指数"65537"的十六进制形式,这里的"00C..."就是模数的十六进制形式,我们可以将其转化为十进制,生成 RAS 公钥来加密内容。**具体代码如下:

20230701134714

python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64

# 字符串
message = "5cd901f0909a23a7"
# 十六进制指数
rsaExponent = int("10001", 16)  # 十六进制转十进制,就是65537
# 十六进制模数(长度为258位的十六进制字符串)
rsaModulus = int("00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81", 16)  # 十六进制转十进制
# 依据模数和指数生成RSA公钥
public_key = RSA.construct((rsaModulus, rsaExponent))
# 输出PEM格式的RSA公钥
print(public_key.exportKey().decode())  # 输出:-----BEGIN PUBLIC KEY-----...
# 加密字符串
cipher = PKCS1_v1_5.new(public_key)
crypto = cipher.encrypt(message.encode('utf-8'))
# 将二进制流数据编码为十六进制字符串
hex_crypto = base64.b16encode(crypto).decode()
print(hex_crypto)                       # 输出:1206202ce...
# 将二进制流数据编码为Base64编码的字符串
base64_crypto = base64.b64encode(crypto).decode()
print(base64_crypto)                    # 输出:EgYgLOKH9...

Base64公钥

在爬虫逆向 JS 代码的过程中有时会遇到 setPublicKey("Base64编码") 生成公钥来加密内容,我们可以使用下面代码来还原:

image-20231224143602635

python
import base64
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA

# Base64编码的公钥字符串
public_key_base64 = "Base64编码的公钥字符串"
# 将Base64编码的公钥转换为PEM格式
public_key_pem = base64.b64decode(public_key_base64)
public_key = RSA.import_key(public_key_pem)
# 生成加密器
cipher = PKCS1_v1_5.new(public_key)
# 要加密的数据
plaintext = "Hello, world!"
# 使用公钥进行加密
cryptotext = cipher.encrypt(plaintext.encode())
# 使用base64编码加密结果,再进行utf-8编码
base64text = base64.b64encode(cryptotext).decode('utf-8')
# 输出加密后的结果
print(base64text)