加密算法
本文主要介绍密码学中常见的两种加密算法—对称加密和非对称加密。
1. 散列(摘要)算法
在学习加密算法之前,我们先来了解一下散列算法(散列不是加密)。散列算法是通过一定方式对原文进行计算,产生一个哈希值,不管原始数据是什么样的,得到的哈希值都是固定长度的,其作用只是为了验证数据的完整性和唯一性,无法通过摘要解密得到原始数据。而对于加密算法来说,我们可以通过“秘钥”或者“解密算法”将加密后的结果还原成原文。
[!note]
常见的散列算法有:SM3、MD5、SHA-1
1.1. Example1
我们可以设计一个简单的对整数进行散列的算法
def integer_hash(x):
return x % 16
if __name__ == "__main__":
print(integer_hash(16), integer_hash(17), integer_hash(32))
>>> 0 1 0
我们采用对16取余的方案,可以看到对16和17进行hash后得到的摘要是不一样的。在实际传输过程中,我们可以通过同时传输原文和散列后的摘要,接收方通过对原文进行相同散列,比较得到的摘要和接收的摘要是否相同来验证文本的正确性。但是通过hash后的结果“0”或“1”并不能得到原文,而且不同的原文hash后的结果也可能相同,如16和32的hash结果都是0。当然,我们这里的hash算法过于简单,一个优秀的hash算法应该保证散列的随机性,尽量减少冲突。
1.2. Example2
import hashlib
str = "This text is hashed via md5"
hl = hashlib.md5()
hl.update(str.encode(encoding='utf-8'))
print("MD5散列前:" + str)
print("MD5散列后:" + hl.hexdigest())
>>> MD5散列前:This text is hashed via md5
>>> MD5散列后:5d3a325f6a7fd4c8e4d4dad3879cfcdd
我们常常可以看到网上有工具可以对MD5算法加密后的结果进行还原,那是由于MD5算法设计足够优秀,可以保证无冲突,因此我们可以保留很多<span style="background:#fff88f">“摘要-原文”字典,通过hash值来暴力求取原文。由于是通过字典暴力求解,所以一般缓存的都是常用的、较短的字符串,本例中的文本较长且不常见,因此将本例中的摘要通过工具进行解密往往不能成功。如果加“hello”的MD5结果加以解密,则很快能得到结果。很多网站常常用MD5来保存散列后的密码来避免明文存储,这时候可以通过加盐的方式进一步提高安全性。所谓加盐就是通过特定的方法将某一个固定的字符串或随机的字符串(需要保存)插入到原文中去,对原文进行改造。
2. 对称加密
对称加密指的是加、解密使用的同是一串秘钥,所以被称作对称加密。
[!note]
常见的对称加密算法有DES、AES等
对称算法又可分为两类:
- 序列算法或序列密码:一次只对明文中的单个位(有时对字节)运算的算法。
- 分组算法或分组密码:对明文的一组位进行运算,这些位组称为分组
现代计算机密码算法的典型分组长度为64位――这个长度既考虑到分析破译密码的难度,又考虑到使用的方便性。后来,随着破译能力的发展,分组长度又提高到128位或更长。
- 优点:加解密效率高,加密速度快。
- 缺点:秘钥保管困难,网络传输秘钥不安全
import binascii
from pyDes import des, CBC, PAD_PKCS5
def des_encrypt(str, secret_key):
k = des(secret_key, CBC, secret_key, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(str, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)
def des_descrypt(str, secret_key):
k = des(secret_key, CBC, secret_key, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(str), padmode=PAD_PKCS5)
return de
if __name__ == "__main__":
str_en = des_encrypt('hello world', "20200407")
print(str_en.decode('utf-8'))
str_de = des_descrypt(str_en, "20200407")
print("正确的key解密结果:" + str_de.decode('utf-8'))
str_de = des_descrypt(str_en, "20210407")
print("错误的key解密结果:" + str_de.decode('utf-8'))
>>> 886a9aca4d78c90016a81d3a296544ca
>>> 正确的key解密结果:hello world
>>> 错误的key解密结果:helmo world
3. 非对称加密
非对称加密指的是加、解密使用不同的秘钥,一把作为公钥、另一把作为私钥。公钥加密的信息,只有私钥才能解密,且私钥加密的信息,只有公钥才能解密。
- 优点:比较安全
- 缺点:效率较低
[!note]
常见非对称加密算法有:RSA、ECC(椭圆曲线加密算法)
3.1. RSA与ECC
- ECC(椭圆曲线密码学):是一种基于椭圆曲线数学理论实现的非对称加密算法。它通过椭圆曲线上的点运算来构建公钥和私钥,其安全性基于椭圆曲线上的离散对数难题,即给定椭圆曲线上的一个点和一个倍数,很难计算出这个倍数。
- RSA:同样是一种非对称加密算法,由 Ron Rivest、Adi Shamir 和 Leonard Adleman 在 1977 年提出。它的安全性基于大整数分解难题,也就是将一个大的合数分解为两个质数的乘积是非常困难的。
ECC优势
- 性能优势:在相同安全强度下,ECC 密钥长度比 RSA 短很多,例如 256 位的 ECC 密钥与 3072 位的 RSA 密钥具有相当的安全性。这使得 ECC 在加密和解密操作时速度更快,尤其是在移动设备和物联网等资源受限的环境中,ECC 能更好地适应,减少计算资源和能源的消耗。
- 安全特性:ECC 基于椭圆曲线上的离散对数难题,数学结构复杂,在面对量子计算机攻击时,被认为可能比 RSA 更具抵抗力。随着量子计算技术的发展,这一特性使 ECC 在未来的信息安全领域具有重要的战略意义。
RSA优势 - 历史悠久:RSA 是最早提出的非对称加密算法之一,在信息安全领域应用时间长,许多现有系统和标准都是基于 RSA 构建的,具有广泛的兼容性。
- 理解和实现相对容易:RSA 的数学原理相对直观,基于大整数的乘法和分解,在一些对安全性要求不是极高、资源较为充足且追求简单实现的场景中,仍然被广泛使用。
3.2. 密钥交换
- ECDH(椭圆曲线 Diffie - Hellman):基于ECC的密钥交换协议,用于在<span style="background:#fff88f">不安全的通信信道上,让双方安全地交换共享密钥。双方通过各自的私钥和对方的公钥,在椭圆曲线上进行计算,最终得到相同的共享密钥。
- RSA也可以用于密钥交换。
3.2.1. ECDH协商简要过程
- 参数选择:通信双方(如 A 和 B)首先需要选择一个共同的椭圆曲线参数,包括椭圆曲线方程、基点G以及曲线的阶n等。这些参数是公开的,可通过标准组织或预定义的方式获取。
- 密钥生成
- 参与者A随机选择一个整数a作为自己的私钥,计算$A=aG$作为自己的公钥,其中G是椭圆曲线的基点。
- 参与者B同样随机选择一个整数b作为自己的私钥,计算$B=bG$作为自己的公钥。
- 公钥交换:A和B通过不安全的信道交换各自的公钥A和B。
- 共享密钥计算
- 参与者 A 使用自己的私钥a和收到的B的公钥 B,计算共享密钥 K=aB。
- 参与者 B 使用自己的私钥b和收到的A的公钥 A,计算共享密钥 K=bA。
由于 aB=a(bG)=b(aG)=bA,所以双方计算得到的共享密钥是相同的。
[!note]
- 密钥协商过程中,私钥是本地随机生成的,私钥没有参与过网络传输,没有被窃取的可能。
- 私钥是每次通信临时生成(通过随机数a、b)的,用完即销毁,也不用担心后面私钥会泄露的问题!
- 加密使用的对称秘钥也是各自本地计算得到的,没有在网络中传输
3.2.2. 随机生成非对称密钥的必要性
如果仅使用服务端证书中的公钥来加密对称密钥(如TLS早期的RSA 密钥交换模式),那么这种方式无法提供前向安全性(Forward Secrecy,FS)。
- 如果未来某个攻击者获取了服务器的私钥(比如通过黑客攻击、量子计算等方式破解),则所有过去的通信数据都可以被解密。
- 但如果使用 ECDH(或者传统的 DH),每次通信的密钥都是临时协商的,服务器的私钥即使泄露,也无法解密过去的通信数据。
[!note]
现代的HTTPS协议(如 TLS 1.2 及 TLS 1.3)广泛采用 ECDHE(Ephemeral ECDH) 来保证前向安全性,使得每次通信都有独立的临时密钥,即使私钥泄露也不会影响之前的会话。在 TLS 1.3 中,RSA 密钥交换已经被移除,所有密钥交换必须使用 ECDHE 或 DHE,原因正是 前向安全性 和 更高效的加密性能。
[!note]
可以使用临时会话级RSA密钥来实现前向安全性,但它有以下缺点:
- 计算开销过大(临时 RSA 密钥的生成和解密计算成本高,影响服务器性能)
- 额外的公钥传输成本(临时 RSA 公钥较长,导致握手数据增加)。
3.3. 签名
- ECDSA(椭圆曲线数字签名算法):基于ECC的数字签名算法,用于对消息进行签名和验证签名的有效性。签名者使用自己的私钥对消息进行签名,验证者使用签名者的公钥来验证签名是否有效。
- RSA也可以用于签名
3.4. 应用示例
3.4.1. 应用场景一(加密)
(1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
(2)甲方获取乙方的公钥,然后用它对信息加密。
(3)乙方得到加密后的信息,用私钥解密。
3.4.2. 应用场景二(验证签名)
(1)甲方生成两把秘钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
(2)甲方用散列算法生成文件摘要,然后用私钥对摘要进行加密生成签名。
(3)乙方拿到文件文件后,用相同的散列算法生成摘要,用公钥对签名进行解密,比较两份摘要是否相同
[!tip]
数字签名可以保证甲方发给乙方的文件是完整的、没有被篡改过的。由于只有甲方拥有私钥,篡改者无法通过私钥进行加密,也就无法篡改签名。因此接收方如果通过公钥对签名进行解密得到的摘要与在接收方对原文进行散列得到的摘要不相同的话,就可以断定原文已经被修改过了。
示例:
import rsa
import hashlib
def create_keys():
"""
生成秘钥对
"""
(pubkey, prikey) = rsa.newkeys(1024)
pub = pubkey.save_pkcs1()
pri = prikey.save_pkcs1()
with open('rsa.pub', 'wb+') as f:
f.write(pub)
with open('rsa.pri', 'wb+') as f:
f.write(pri)
def encrypt(original_text):
"""
公钥加密
"""
with open('rsa.pub', 'rb') as f:
pub = f.read()
pubkey = rsa.PublicKey.load_pkcs1(pub)
encrypted_text = rsa.encrypt(original_text.encode('utf-8'), pubkey)
return encrypted_text
def decrypt(encrypted_text):
"""
私钥解密
"""
with open('rsa.pri', 'rb') as f:
pri = f.read()
prikey = rsa.PrivateKey.load_pkcs1(pri)
original_text = rsa.decrypt(encrypted_text, prikey).decode("utf-8")
return original_text
def sign(message):
"""
私钥签名
"""
with open('rsa.pri', 'rb') as f:
pri = f.read()
prikey = rsa.PrivateKey.load_pkcs1(pri)
# 使用MD5所有散列算法生成摘要
signed_message = rsa.sign(message.encode('utf-8'), prikey, hash_method='MD5')
return signed_message
def verify(message, signed_message):
"""
公钥验证
"""
with open('rsa.pub', 'rb') as f:
pub = f.read()
pubkey = rsa.PublicKey.load_pkcs1(pub)
return rsa.verify(message.encode('utf-8'), signed_message, pubkey)
if __name__ == "__main__":
text = "This is encrypted by rsa"
create_keys()
text = encrypt(text)
print("加密结果:", text)
text = decrypt(text)
print("解密结果:", text)
signed_message = sign(text)
print("签名结果:", signed_message)
print("验签结果:", verify(text, signed_message))
>>> MD5加密前:This text is hashed via md5
>>> MD5加密后:5d3a325f6a7fd4c8e4d4dad3879cfcdd
>>> 签名结果: b'\x10^\xa53\x8a\xda\xf0"e}\x7f\x8dtg\x0f\x96\x08\xd2\xa2L\x16<span class="tag">#c</span>\xc6\xad*C\xa6\x84\x11A\xe9\t\xf9\\\xce\x03\x99\xf1\xb4\xfa\xdf\x83\xa4[\x1d\xbff\x9eO\x08\x92\xf4\x05E4"&v\xcfTD\xbbq\x87\x9f\xdb\xc0u\x9b(\x13\\\x0b<j*Y\x15\x97!\x0b\x17q1h\xcd\xb8\xe0\x98\xbb\xe7\xbb3\xbc\xb8\xef\xbf\xe1\xda$0:\xa0z\xbdP\xd9\xe65\xf4\x17X\xf5H\xc3\x15\x8f\xc67\xbb\x8e<span class="tag">#Ba</span>\xd8\'\x7f'
>>> 验签结果:MD5
4. 对称加密与非对称加密对比
- 对称加密多用户通信秘钥管理困难,N个人两两之间需要保管一个秘钥,共$C_N^2$个秘钥对。
- 非对称加密,N个用户,共有N个私钥和N个公钥。
- 在实际运用中可以将两种加密方式混合使用,将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。