python实现RSA加解密、RSA、ECC签名——《python全栈安全》第5章读书笔记

全文AI辅助生成,仅作为个人的读书笔记
《python全栈安全》第五章——非对称加密

1.1RSA加密

非对称加密优势:方便管理密钥、数字签名
可以在命令行生成公私钥(openssl\ssh-keygen),一般不会在python中实现

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out private_key.pem
//在当前目录生成private_key.pem文件,RSA密钥长度3072
openssl rsa -pubout -in private_key.pem -out public_key.pem
//根据私钥生成RSA公钥public_key.pem

本书使用python实现生成私钥、派生公钥、序列化到磁盘、反序列化读取、加解密
这是python的低级实现过程,在第六章会使用高级方案

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

# 生成RSA密钥对
private_key = rsa.generate_private_key(
    public_exponent=65537,  # 公钥指数,通常使用65537,公钥指数,65537是一个常用的值,因为它在计算上效率较高且安全性较好。
    key_size=2048,          # 密钥长度,建议至少2048位,密钥长度,2048位是目前推荐的最小长度,以确保足够的安全性。
)

public_key = private_key.public_key()
#从生成的私钥中提取公钥。公钥可以公开分发,用于加密数据。
# 序列化公钥和私钥
public_bytes = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,  # 使用PEM编码
    format=serialization.PublicFormat.SubjectPublicKeyInfo  # 使用标准格式
)
print(public_bytes.decode('utf-8'))  # 打印公钥的PEM格式
with open('public_key.pem','xb') as public_file:
    public_file.write(public_bytes) # 将公钥写入文件


private_bytes = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,  # 使用PEM编码
    format=serialization.PrivateFormat.PKCS8,  # 使用PKCS8格式
    encryption_algorithm=serialization.NoEncryption()  # 不使用加密算法.实际应用中建议使用加密算法保护私钥。
)
print(private_bytes.decode('utf-8'))  # 打印私钥的PEM格式
with open('private_key.pem','xb') as private_file:
    private_file.write(private_bytes) # 将私钥写入文件

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

# 加载、反序列化公钥
with open("public_key.pem", "rb") as key_file:
    public_key_pem = key_file.read()

loaded_public_key = serialization.load_pem_public_key(
    public_key_pem,
    backend=default_backend()
)
# 加载、反序列化私钥
with open("private_key.pem", "rb") as key_file:
    private_key_pem = key_file.read()

loaded_private_key = serialization.load_pem_private_key(
    private_key_pem,
    password=None,  # 如果私钥有密码保护,需要提供密码
    backend=default_backend()
)


from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes

# 要加密的数据
message = b"Hello, RSA!"

# 使用公钥加密数据
encrypted_message = loaded_public_key.encrypt(
# message为字节类型
    message,
# 指定使用OAEP(Optimal Asymmetric Encryption Padding,最优非对称加密填充)方案进行填充。
    padding.OAEP( 
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),#MGF1内部使用SHA-256哈希算法来生成掩码
        label=None#MGF1内部使用SHA-256哈希算法来生成掩码
    )
)

print(f"Encrypted message: {encrypted_message}")
# 使用私钥解密数据
decrypted_message = loaded_private_key.decrypt(
    encrypted_message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )#使用公钥对消息进行加密。padding.OAEP是填充模式,MGF1是掩码生成函数,SHA256是哈希算法。
)

print(f"Decrypted message: {decrypted_message.decode()}")

1.2AI为上述代码的提问与解答

问题1:为什么选择65537作为公钥指数?
答案:65537是一个常用的公钥指数,因为它在计算上效率较高且安全性较好
问题2:为什么需要序列化和反序列化密钥?
答案:序列化是为了将密钥转换为可存储或传输的格式,反序列化则是将存储的密钥还原为可使用的格式。
问题3:为什么使用2048位的密钥长度?
答案:2048位是目前推荐的最小密钥长度,以确保足够的安全性。虽然1024位的密钥在某些情况下仍然可以使用,但随着计算能力的提升,1024位的密钥已经不再被认为是安全的。
问题4:为什么需要将密钥序列化为PEM格式?
答案:PEM(Privacy Enhanced Mail)格式是一种常见的密钥存储格式,便于存储和传输。它将密钥以文本形式表示,方便在文件中保存或通过网络传输。
问题5:如何序列化私钥,并设置密码保护(在示例中没有选择密钥的加密方式)

password = b"your_password_here"  # 设置密码,必须是字节串
private_key_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,#指定私钥的格式为PKCS#8,这是一种通用的私钥格式。
    encryption_algorithm=serialization.BestAvailableEncryption(password)#使用提供的密码对私钥进行加密。BestAvailableEncryption会自动选择系统支持的最强加密算法。
)

问题3:什么是填充模式(Padding)?
解释:填充模式用于解决加密算法中数据长度不足的问题。在RSA中,填充模式可以防止某些类型的攻击:填充攻击、 重放攻击(结合其他机制如时间戳、随机数或序列号可以增强安全性)、、
问题4:什么是OAEP填充模式?
答案:OAEP(Optimal Asymmetric Encryption Padding)是一种安全的填充模式,结合了哈希函数和掩码生成函数(MGF),用于防止填充攻击。OAEP通过引入随机性和复杂的填充结构,使得相同的明文在不同加密过程中产生不同的密文,从而防止攻击者通过填充错误来获取信息。
问题5:为什么OAEP填充中需要使用哈希算法?
隐藏消息内容:掩码通过与消息的某些部分进行异或操作,将原始消息内容“隐藏”起来。即使攻击者能够获取加密后的消息,也难以直接从加密数据中恢复出原始消息。
增加随机性:掩码的生成依赖于哈希算法和随机数(如随机种子)。这种随机性使得每次加密相同的消息时,生成的密文都不同,从而防止了重放攻击和已知明文攻击。
问题6:什么是MGF1?
答案:MGF1(Mask Generation Function 1)是一种掩码生成函数,用于生成掩码数据。它通常结合哈希算法(如SHA256)使用。

2.1RSA签名

利用非对称加密的不可否认性

import json
message_bytes = b'RSA signature'
signature = private_key.sign(
     message_bytes,
     padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
         salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
message_b64 = base64.b64encode(message_bytes).decode('utf-8')
signature_b64 = base64.b64encode(signature).decode('utf-8')

# 打包为JSON对象
signed_msg = {
    'message': message_b64,
    'signature': signature_b64
}

# 将JSON对象转换为JSON字符串
output_msg_to_Alice = json.dumps(signed_msg)

# 打印JSON字符串
print("JSON output to Alice:")
print(output_msg_to_Alice)
from cryptography.exceptions import InvalidSignature
try:
    public_key.verify(
        base64.b64decode(signature_b64),
        message_bytes,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print('trust message!')
except InvalidSignature:
    print('Don\'t trust!')

2.2AI为上述代码的提问与解答

问题1:什么是盐值(salt)?
答案:盐值(salt)是一个随机生成的字节串,用于增加签名的随机性。在PSS填充中,盐值与消息的哈希值结合,生成掩码。盐值的长度可以指定为固定值或最大长度(padding.PSS.MAX_LENGTH),以确保签名的安全性。
问题2:InvalidSignature异常是什么?它在什么情况下会被抛出?
答案:InvalidSignature异常是cryptography库中的一个异常类型,当验证签名失败时会被抛出。这通常发生在以下几种情况:
签名与消息不匹配,即签名不是由对应的私钥生成的。
消息在传输过程中被篡改,导致签名验证失败。
使用了错误的公钥进行验证。
问题3:为什么在验证签名时需要使用padding.PSS和hashes.SHA256()?
答案:在验证签名时,必须使用与签名生成时相同的填充方案和哈希算法。这是因为签名是基于这些参数生成的,只有使用相同的参数,才能正确验证签名。padding.PSS是一种概率签名方案,它通过引入随机性来增强安全性,而hashes.SHA256()用于对消息进行哈希处理,确保消息的完整性。
问题4:如何确保签名验证的安全性?
答案:为确保签名验证的安全性,需要做到以下几点:
确保签名算法与验证算法一致。
确保签名数据在传输过程中未被篡改。
使用正确的公钥进行验证。
验证过程中捕获并处理InvalidSignature异常,避免程序因异常而崩溃。
问题5:为什么在验证签名时要对签名进行Base64解码?
答案:在签名生成时,签名通常被编码为Base64格式以便于传输和存储。因此,在验证签名之前,需要将Base64编码的签名解码为原始字节串,以便使用公钥进行验证。
问题6:为什么RSA加密往往使用OAEP而签名使用PSS填充
因为这两种填充方案分别针对加密和签名的特定需求进行了优化。OAEP:适用于RSA加密,通过引入随机性和复杂的填充结构,确保加密的安全性和语义安全性。
PSS:适用于RSA签名,通过概率性填充和随机盐值,确保签名的不可伪造性和完整性。

3.1ECC签名

使用ecc的原因:比RSA速度快、成本低、强度高
ecc256位密钥强度与RSA3072位密钥强度相当

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import utils
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidSignature

message=b'ecc signature'
private_key = ec.generate_private_key(ec.SECP384R1(), default_backend())#NIST认可的SECPR1或P-384椭圆曲线生成
signature = private_key.sign(
        message,
        ec.ECDSA(hashes.SHA256())
    )
public_key=private_key.public_key()
try:
    public_key.verify(signature,message,ec.ECDSA(hashes.SHA256()))
    print('trust message by ecc signature')
except InvalidSignature:
    pass

3.2AI为上述代码的提问与解答

问题1:ECC签名与RSA签名的主要区别是什么?
答案:
密钥长度:ECC使用较短的密钥长度即可达到与RSA相同的安全级别。例如,256位的ECC密钥相当于3072位的RSA密钥。
计算效率:ECC的计算效率更高,适合资源受限的环境。
安全性:ECC基于椭圆曲线离散对数问题,提供更高的安全性。
问题2:如何选择椭圆曲线参数?
答案:选择椭圆曲线参数时,应考虑以下因素:
安全性:选择经过广泛验证的安全曲线,如SECP256R1、SECP384R1等。
性能:根据应用场景选择合适的曲线,较短的密钥长度可以提高计算效率。
兼容性:确保所选曲线与使用的加密库和协议兼容。

posted @ 2025-02-02 00:08  十六块大西瓜  阅读(81)  评论(0)    收藏  举报