在某次安全研究中。需要手工验证下某重要文件MD5withRSA的数字签名是否正确。于是写了个脚本。现记录下来,供大家学习讨论。
1. 数字签名基础知识
数字签名:
一旦选择足够安全的散列算法, 那么就能够使用其实现数字签名系统。数字签名基础结构具有两个明显的目标:
1.数字化的签名消息可以向接收方保证: 消息确实来自己声明的发送者, 并且实施了不可否认性(也就是说, 排除了发送者之后声称消息是伪造的情况)。
2.数字化的签名消息可以向接收方保证: 消息在发送方和接收方之间进行传输的过程中不会被改变。这种方法确保消息不会受到恶意的修改(第三方想要修改消息的含义)以及无意识的修改(由通信过程中的故障造成,如电磁干扰) 。
数字签名算法的基础是我的一篇博客中讲到的两个重要概念: 公钥密码学和散列函数。
CISSP考试要求里的“应用密码学”内容辅助记忆趣味串讲_晓翔仔的博客-CSDN博客_cissp密码学
(公钥密码学=非对称密码学)
如果Alice 想要数字化签名一条发送给Bob 的消息,那么她会执行下列动作:
(1) Alice 使用一种足够安全的散列算法(如SHA-512)生成原始明文消息的消息摘要。
(2) 然后, A1ice 使用她的私钥只对消息摘要进行加密。加密的消息摘要便是数字签名。
(3) Alice 将签名的消息摘要添加到明文消息中。
(4) Alice 将完成添加的消息传送给Bob。
当Bob 接收到数字化签名的消息时,他会逆向完成如下过程:(这就是签名验证需要做的!)
(1) Bob 使用Alice 的公钥解密数字签名。
(2) Bob 使用相同的散列函数, 生成从Alice 那里接收到的完整明文消息的消息摘要。
(3) 然后, Bob 将从Alice那里接收到的已解密的消息摘要与自己计算得到的消息摘要进行比较。
如果两个消息摘要匹配, 那么Bob 就能够确认接收到的消息是由Alice 发送的。如果这两个消息摘要不匹配, 那么这条消息有可能不是Alice 发送的, 也有可能在传输过程中被修改了。
需要注意的是,数字签名过程本身并不提供任何隐私保护。数字签名只是确保满足加密目标中的完整性和不可否认性。然而,如果Alice 想保证发送给Bob 的消息的隐私性,那么她就要在消息生成的过程中增加额外的步骤。在将己签名的消息摘要添加到明文消息中以后,Alice可以用Bob的公钥加密整条消息。当Bob接收到消息时,他会用自己的私钥在上述所列的步骤之前对消息进行解密。
数字签名不仅仅用于消息,软件供应商经常使用数字签名技术对从互联网上下载的编码分发(例如,applet和软件补丁)进行身份认证。
2. 签名文件的产生
2.1 生成一对公私钥
RSA密钥对的生成方式很多,这里为了方便,利用在线工具生成了一对公私钥。
公钥:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCr0erlUoNR/7WNp3RxIBM3UQVE/e7kbS8rKeybytjxPyNKta9/nlMWm3Yt9t8DQ0Tc5D18Uj1pmBGpplYCVKSxHF5VEzCK7+gMCJnieeZ/WLjGHqZJjIOwg3y5H52012jvQ5EcE7JIXRTOzIj7+2+J50JmDBmSUB4Vq+vAtdeA+wIDAQAB
-----END PUBLIC KEY-----
私钥:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCr0erlUoNR/7WNp3RxIBM3UQVE/e7kbS8rKeybytjxPyNKta9/
nlMWm3Yt9t8DQ0Tc5D18Uj1pmBGpplYCVKSxHF5VEzCK7+gMCJnieeZ/WLjGHqZJ
jIOwg3y5H52012jvQ5EcE7JIXRTOzIj7+2+J50JmDBmSUB4Vq+vAtdeA+wIDAQAB
AoGAAzjbi9NhTQ9biz2BrIFqetYVIDcW3MfDY67UlhNWwCdr9QG1OdJxQnJF5BTG
F9ySFQZetM8iAbHtWt4YrNCDihrNNN8jhAK3l/QmqUjDmJuf34KRMWm1v0v3LE6C
5NOTreq5RDw2xEvPu9SDprhues9fVz0FCNagasI4l3UMM3ECQQDuCSjJOsjg9d8V
YUwNKlB7renL4laeO+qCwgkmuApX5nw3cPZAr6XlJDuy6oV0x16gdZfISU0BghL4
IdmArf1ZAkEAuMl43wWOxKphfqdP7lJ/besrzU6uNlGP56KtHy61cJ1s7Dexs46B
b89SRG2sCq1TgledPUynsepAOdxM7tQCcwJBAKZivKG8d4HYG7GkKzbgpxVswYoE
kitVEl2IKEFqT8CVmapfSaJ5pOqA40Hy8IpqediLmickJbGyLHRJpPFvrTECQEMR
xVBVaRNSsB5TRfy/bscLo5NaKntGsU+myOVXg72DNiSqtlHwuGRHwJlboSUi8fuO
J2E3cGpwFq2pmzbYNqcCQQCp6HwOLqsZ3GKPgX45txlESpHqeb4bNKm4G7M/b3nw
C0wdkZ3UHtbnc04m++atZB7re3eiQ/sYdMcN0RXXDBqv
-----END RSA PRIVATE KEY-----
2.2 用私钥对重要文件的哈希做加密
新建文件,写了重要内容。计算文件的MD5是
2da853603413c8823df8454ed579cd64
用上一步生成的私钥对MD5信息进行加密,加密结果用BASE64编码显示,是:
PXhWLNkyGRwBiuONnaeDXFv4HD6kvCIFO61jpjC9gX61qGfplMPrbcBn+co2eImNO10gAaXMXB1rxWnkxTETU9otaUO0I/SorqWbqiE4eOu1vlmuUZ0rXlxHGkLzfzJov25iFc8Lgt/qGbwuDL2p+yvjPNljhK2K3idOzO2v4I8=
用一个单独文件存放密文。这个就是该重要文件的数字签名。
3. 签名验证
3.1 利用RSA公钥对signature做解密。
熟悉RSA算法的你一定知道:RSA解密计算的时候,需要密文,模和指数。这密文就是数字签名signature,模(m)和指数(e)都存在与RSA公钥文件里面。
RSA公钥文件的主体部分是:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCr0erlUoNR/7WNp3RxIBM3UQVE/e7kbS8rKeybytjxPyNKta9/nlMWm3Yt9t8DQ0Tc5D18Uj1pmBGpplYCVKSxHF5VEzCK7+gMCJnieeZ/WLjGHqZJjIOwg3y5H52012jvQ5EcE7JIXRTOzIj7+2+J50JmDBmSUB4Vq+vAtdeA+wIDAQAB
RSA公钥文件的主体部分做BASE64解码后是:
30819f300d06092a864886f70d010101050003818d0030818902818100abd1eae5528351ffb58da77471201337510544fdeee46d2f2b29ec9bcad8f13f234ab5af7f9e53169b762df6df034344dce43d7c523d699811a9a6560254a4b11c5e5513308aefe80c0899e279e67f58b8c61ea6498c83b0837cb91f9db4d768ef43911c13b2485d14cecc88fbfb6f89e742660c1992501e15abebc0b5d780fb0203010001
褐色部分是模(m),紫色部分是指数(e)。
这里需要注意的是RSA公钥文件是由不同格式的。一种是“BEGIN RSA PUBLIC KEY”,另一种是“BEGIN PUBLIC KEY”。两种格式下,模(m)在公钥文件中所处的位置不一样(注意看python代码“# 找到模数和指数的开头结束位置”),指数(e)则永远都在公钥文件的结尾。
如何在两种公钥格式之间进行转换,一种是“BEGIN RSA PUBLIC KEY”,另一种是“BEGIN PUBLIC KEY” - 码客
根据上述分析,编写python3代码,用RSA公钥对signature做解密
import rsa
import base64
class DecryptByPublicKey:
"""
先产生模数因子
然后生成rsa公钥
再使用rsa公钥去解密传入的加密str
"""
def __init__(self, encrypt_text, pub_key):
missing_padding = 4 - len(encrypt_text) % 4
if missing_padding:
encrypt_text += '=' * missing_padding
self._encrypt_text = encrypt_text.replace(" ", "+")
self._pub_string_key = pub_key
# 使用公钥字符串求出模数和因子
self._modulus = None # 模数
self._exponent = None # 因子
# 使用PublicKey(模数,因子)算出公钥
self._pub_rsa_key = None
def _gen_modulus_exponent(self, s):
# 对字符串解码, 解码成功返回 模数和指数
b_str = base64.b64decode(s)
# print("len(b_str):", len(b_str))
# if len(b_str) < 162:
# print("_gen_modulus_exponent return false")
# # return False
hex_str = b_str.hex()
# print("hex_str is: ", hex_str)
# 找到模数和指数的开头结束位置 BEGIN PUBLIC KEY格式的开头位置
if pub_key_type == 'BEGIN PUBLIC KEY':
m_start = 29 * 2
e_start = 159 * 2
m_len = 128 * 2
e_len = 3 * 2
elif pub_key_type == 'BEGIN RSA PUBLIC KEY':
m_start = 7 * 2
e_start = 137 * 2
m_len = 128 * 2
e_len = 3 * 2
else:
print("pubkey type not recognized")
self._modulus = int(hex_str[m_start:m_start + m_len], 16)
self._exponent = int(hex_str[e_start:e_start + e_len], 16)
# print("exponent is:", hex(self._exponent))
# print("modulus is:", hex(self._modulus))
def _gen_rsa_pubkey(self):
# 将pub key string 转换为 pub rsa key
try:
rsa_pubkey = rsa.PublicKey(self._modulus, self._exponent)
# 赋值到_pub_rsa_key
self._pub_rsa_key = rsa_pubkey.save_pkcs1()
except Exception as e:
raise e
def decode(self):
"""
decrypt msg by public key
"""
public_key = rsa.PublicKey.load_pkcs1(self._pub_rsa_key)
b64decoded_encrypt_text = base64.b64decode(self._encrypt_text)
length = len(b64decoded_encrypt_text)
# print("b64decoded_encrypt_text is: ", b64decoded_encrypt_text)
# print("length of b64decoded_encrypt_text is: ", length)
length_max = 128
decryptDataText = []
data = b''
if length >= 128:
for i in range(0, length, length_max):
encrypted = rsa.transform.bytes2int(b64decoded_encrypt_text[i:i + length_max])
print("encrypted data(signature):", hex(encrypted))
print("public_key.e:", public_key.e)
print("public_key.n:", public_key.n)
decrypted = rsa.core.decrypt_int(encrypted, public_key.e, public_key.n)
decrypted_bytes = rsa.transform.int2bytes(decrypted)
# print("decrypted_bytes: ",decrypted_bytes)
print("decrypted data(hex):", hex(decrypted))
else:
encrypted = rsa.transform.bytes2int(b64decoded_encrypt_text)
decrypted = rsa.core.decrypt_int(encrypted, public_key.e, public_key.n)
decrypted_bytes = rsa.transform.int2bytes(decrypted)
try:
raw_info = decrypted_bytes[decrypted_bytes.find(b'\x00') + 1:]
except Exception as e:
p.error(e)
raise e
print("decrypt sign by public key is: ", raw_info.decode("utf-8"))
return 'TRUE'
def decrypt(self):
"""
先产生模数因子
然后生成rsa公钥
再使用rsa公钥去解密
"""
self._gen_modulus_exponent(self._pub_string_key)
self._gen_rsa_pubkey()
ret = self.decode()
print("ret is:", ret)
return ret
if __name__ == "__main__":
# 在线生成
pub_key_type = 'BEGIN PUBLIC KEY'
pub_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCr0erlUoNR/7WNp3RxIBM3UQVE/e7kbS8rKeybytjxPyNKta9/nlMWm3Yt9t8DQ0Tc5D18Uj1pmBGpplYCVKSxHF5VEzCK7+gMCJnieeZ/WLjGHqZJjIOwg3y5H52012jvQ5EcE7JIXRTOzIj7+2+J50JmDBmSUB4Vq+vAtdeA+wIDAQAB'
encrypt_text = 'PXhWLNkyGRwBiuONnaeDXFv4HD6kvCIFO61jpjC9gX61qGfplMPrbcBn+co2eImNO10gAaXMXB1rxWnkxTETU9otaUO0I/SorqWbqiE4eOu1vlmuUZ0rXlxHGkLzfzJov25iFc8Lgt/qGbwuDL2p+yvjPNljhK2K3idOzO2v4I8=' # encrypt_text 是被私钥加密后的密文
result = DecryptByPublicKey(encrypt_text, pub_key).decrypt()
print(result)
备注:借鉴了这篇博客,特感谢
【Python3】RSA公钥解密_Parkour1990的博客-CSDN博客_python rsa 公钥解密
计算输出是:
decrypt sign by public key is: 2da853603413c8823df8454ed579cd64
3.2 哈希对比
对重要文件计算MD5,并与上一步python3解密计算的输出对比,发现是一致的,因此我们可以说本次数字证书验证通过。
4.总结
使用python进行RSA公钥加密私钥解密,网上方法很多。但是当你需要验证某数字签名的时候,发现很多博客的代码都无法直接拿过来用,我的python3签名验证代码亲测通过,希望我的这篇博客能够帮助到你。