加密之RSA加密简述(二)

一、关于RSA

1. 简介

维基百科:RSA加密算法

阮一峰的网络日志:RSA算法原理(一)

阮一峰的网络日志:RSA算法原理(二)

RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

1973年,在英国政府通讯总部工作的数学家克利福德·柯克斯(Clifford Cocks)在一个内部文件中提出了一个与之等效的算法,但该算法被列入机密,直到1997年才得到公开。

RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

所以对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式破解。到当前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。

2. 如何生成

示例为常见的Linux或者mac平台

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 生成公钥 《默认生成PKCS1格式》
openssl genrsa -out rsa_pri_key.pem 2048
# 将PKCS1格式私钥转换成PKCS8格式
openssl pkcs8 -topk8 -inform PEM -in rsa_pri_key.pem -outform PEM -nocrypt -out rsa_pri_key_pkcs8.pem
# 将PKCS8格式私钥转换成PKCS1格式
openssl rsa -inform PEM -in rsa_pri_key_pkcs8.pem -outform PEM -out rsa_pri_key.pem

# 2. 根据私钥生成公钥 《默认生成PKCS8格式》
openssl rsa -in rsa_pri_key.pem -pubout -out rsa_pub_key.pem
# 将PKCS8格式公钥转换成PKCS1格式
openssl rsa -pubin -in rsa_pub_key.pem -RSAPublicKey_out -out rsa_pub_key_pkcs1.pem
# 将PKCS1格式公钥转换成PKCS8格式
openssl rsa -RSAPublicKey_in -in rsa_pub_key_pkcs1.pem -pubout -out rsa_pub_key.pem

3. 密钥格式

常见的为PKCS#8PKCS#1,除此之外还有其他的比如PKCS#5、PKCS#12等。

关于PKCS#8PKCS#1

  • PKC#8:定义了一种编码和传输密钥的方法,它并不特定于OpenSSL;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 公钥
    -----BEGIN PUBLIC KEY-----
    -----END PUBLIC KEY-----

    # 私钥
    -----BEGIN PRIVATE KEY-----
    -----END PRIVATE KEY-----

    # 私钥加密的格式
    -----BEGIN ENCRYPTED PRIVATE KEY-----
    -----END ENCRYPTED PRIVATE KEY-----
  • PKCS#1:定义了一种使用RSA密钥的方法(无论它是如何加载到应用程序中,是否使用PKCS#8)来执行和验证数据的数字签名。

    1
    2
    3
    4
    5
    6
    7
    # 公钥
    -----BEGIN RSA PUBLIC KEY-----
    -----END RSA PUBLIC KEY-----

    # 私钥
    -----BEGIN RSA PRIVATE KEY-----
    -----END RSA PRIVATE KEY-----

二、Python加密模块示例

RSA 有两种填充方式,一种是 PKCS1_v1_5,另一种是 PKCS1_OAEP

Python RSA文件加密

1. rsa模块

由python实现,封装得较深,使用简单,比如签名和验签,两行代码就搞定了,很符合python简约的语言理念。

  • 加密与解密

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import rsa
    import base64
    from urllib import request


    # 生成密钥
    def create_new_keys(len):
    (public_key, private_key) = rsa.newkeys(len)
    with open('public1.pem', 'wb') as f:
    f.write(public_key.save_pkcs1())
    with open('private1.pem', 'wb') as f:
    f.write(private_key.save_pkcs1())


    # 加密
    def rsa_encrypt(msg):
    with open(PUBLIC_FILE_PATH, 'rb') as public_file:
    # 加载pkcs8格式公钥
    public_key = rsa.PublicKey.load_pkcs1_openssl_pem(public_file.read())
    code = rsa.encrypt(msg.encode('utf-8'), public_key)
    code = base64.b64encode(code).decode('utf-8')
    code = request.quote(code)
    return code


    # 解密
    def rsa_decrypt(code):
    code = request.unquote(code)
    with open(PRIVATE_FILE_PATH, 'rb') as private_file:
    # 加载pkcs1格式公钥
    private_key = rsa.PrivateKey.load_pkcs1(private_file.read())
    code = base64.b64decode(code.encode('utf-8'))
    msg = rsa.decrypt(code, private_key).decode('utf-8')
    return msg
  • 签名与验签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 签名
    def sign(data):
    # 只能加载PKCS1格式的私钥,如果私钥是PKCS8格式的,需要将PKCS8转PKCS1再使用
    pri_key = rsa.PrivateKey.load_pkcs1(open('./pri.pem').read())
    signature = base64.b64encode(rsa.sign(data.encode('utf-8'), pri_key, 'MD5'))
    return signature


    # 验签
    def verify(signature, data):
    # 加载pkcs1格式公钥
    pub_key = rsa.PublicKey.load_pkcs1(open('./pub.pem').read())
    # 加载pkcs8格式公钥
    # pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(open('./pub.pem').read())
    return rsa.verify(data, base64.b64decode(signature), pub_key)

2. Crypto模块

pip install pycrypto

Python Cryptography Toolkit

  • 签名与验签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from Crypto.PublicKey import RSA
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.Hash import MD5
    import base64


    def RSA_sign(data):
    # key处理方式一:
    privateKey = '''
    MIGvAgEAAiIMzxsBkAn8f9vA5z8phs0z5JT9d9xWS+9hnrMurYY7yySRAgMBAAEC
    IgTmnOudI+UDOp51G/qUhCDtkYhYKvTgENlK1DsroFYXtN0CET4oCSZvz+1GEKsQ
    bE4Ny7y7AhE0wXdLJpQgJnD4cmGmQo8VIwIRNghBpAsw+nedB8gYDmZJxP8CEStE
    eQiDrXzoykKZ3Qi1EhCtAhE20ZvBe0I1fj48Pryi+0gtVA=='''
    private_keyBytes = base64.b64decode(privateKey)
    priKey = RSA.importKey(private_keyBytes)

    # key处理方式二:
    # privateKey = open('./pri.pem').read()
    # priKey = RSA.importKey(privateKey)

    signer = PKCS1_v1_5.new(priKey)
    # 哈希算法可以采用MD5,也可以用别的比如SHA
    # data需要字节化后才能传进MD5.new()中
    hash_obj = MD5.new(data.encode('utf-8'))
    signature = base64.b64encode(signer.sign(hash_obj))
    return signature


    def verify(signature, data):
    # key处理同样是上面两种方式
    publicKey = '''
    MD0wDQYJKoZIhvcNAQEBBQADLAAwKQIiDM8bAZAJ/H/bwOc/KYbNM+SU/XfcVkvv
    YZ6zLq2GO8skkQIDAQAB'''
    public_keyBytes = base64.b64decode(publicKey)
    pubKey = RSA.importKey(public_keyBytes)
    # pubKey = RSA.importKey(publicKey)
    hash_obj = MD5.new(data.encode('utf-8'))
    verifier = PKCS1_v1_5.new(pubKey)
    return verifier.verify(hash_obj, base64.b64decode(signature)) # bool

上面的签名代码做了如下几件事:

  1. 它将Base64解码为PKCS#8
  2. 它将PKCS#8解码为内存中的实际密钥(请注意,可能需要在此处提供密码)
  3. 它使用所述密钥将数据(使用SHA-1或者MD5等算法进行hash)执行PKCS#1 v1.5签名
  4. 它使用Base64将签名进行编码

3. pycryptodome模块

pip install pycryptodome

segmentfault:Crypto算法库详解

该模块和上面的pycrypto很像,但pycrypto最后一次更新是在13年,安装后引用方式是一样的,api也非常相似,安装其中一个就好了。

  • 加密与解密

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from Crypto.PublicKey import RSA
    from Crypto.Cipher import PKCS1_v1_5
    import base64


    # 生成密钥
    def create_new_keys(len):
    rsa = RSA.generate(2048) # 返回的是密钥对象

    public_pem = rsa.publickey().exportKey('PEM') # 生成公钥字节流
    private_pem = rsa.exportKey('PEM') # 生成私钥字节流

    with open('public.pem','wb') as f:
    f.write(public_pem)
    with open('private.pem','wb') as f:
    f.write(private_pem)


    # 加密
    def rsa_encrypt(plain):
    with open('public.pem','rb') as f:
    key = RSA.importKey(f.read())
    rsa = PKCS1_v1_5.new(key)
    cipher = rsa.encrypt(plain)
    return base64.b64encode(cipher)


    # 解密
    def rsa_decrypt(cipher):
    with open('private.pem','rb') as f:
    key = RSA.importKey(f.read())
    rsa = PKCS1_v1_5.new(key)
    plain = rsa.decrypt(base64.b64decode(cipher),'ERROR') # 'ERROR'必需
    return plain
  • 签名与验签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    from Crypto.Signature import pkcs1_15
    from Crypto.Hash import SHA256
    from Crypto.PublicKey import RSA


    # 签名
    def sign(data):
    key = RSA.import_key(open('private_key.pem').read())
    h = SHA256.new(data)
    rsa = pkcs1_15.new(key)
    signature = rsa.sign(h)


    # 验签
    def verify(signature, data):
    key = RSA.import_key(open('public_key.pem').read())
    hash_obj = SHA256.new(data)
    rsa = pkcs1_15.new(key)
    return rsa.verify(hash_obj, signature):

4. cryptography模块

  • 签名与验签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.exceptions import InvalidSignature


    # 签名
    def sign(data_file_name, private_key_file_name):
    # 从PEM文件中读取私钥数据
    pri_key = open(private_key_file_name, 'rb').read()
    key_file.close()

    # 从PEM文件数据中加载私钥
    private_key = serialization.load_pem_private_key(
    pri_key,
    password=None,
    backend=default_backend()
    )

    # 签名
    signature = private_key.sign(
    data,
    padding.PKCS1v15(), # 指定填充方式为PKCS1v15
    hashes.SHA256() # 指定hash方式为sha256
    )
    return signature


    # 验签
    def verify(data, signature, public_key_file_name):
    # 从PEM文件中读取公钥数据
    pub_key = open(public_key_file_name, 'rb').read()
    key_file.close()

    # 从PEM文件数据中加载公钥
    public_key = serialization.load_pem_public_key(
    pub_key,
    backend=default_backend()
    )

    verify_ok = False
    try:
    # 验签
    public_key.verify(
    signature,
    data,
    padding.PKCS1v15(), # 指定填充方式为PKCS1v15
    hashes.SHA256() # 指定hash方式为sha256
    )
    # 签名验证失败会触发名为InvalidSignature的exception
    except InvalidSignature:
    print('invalid signature!')
    else:
    verify_ok = True
    return verify_ok


    if __name__ == '__main__':
    data = 'fxx'

    # 指定签名的密钥
    private_key_file = r'Key.pem'
    public_key_file = r'Key_pub.pem'

    # 签名并返回签名结果
    signature = sign(data, private_key_file)
    print(signature)

    # 验证签名
    verify_ok = verify(data, signature, public_key_file)
    print(verify_ok)

5. 加密分块

由于RSA在加密过程中,每次加密只能加密最大长度的字符串,如果你的加密数据超长,在加密过程中需要分段加密,同理,解密也是分段解密的。

1024位的证书,加密时最大支持117个字节,解密时为128;

2048位的证书,加密时最大支持245个字节,解密时为256。

加密时支持的最大字节数:证书位数/8 -11(比如:2048位的证书,支持的最大加密字节数:2048/8 - 11 = 245,其中,11位字节为保留字节。

加密较长字符串时,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from Crypto.Cipher import PKCS1_v1_5


# 根据key长度计算分块大小
def get_block_size(rsa_key):
try:
# RSA仅支持限定长度内的数据的加解密,需要分块
# 分块大小block_reversed_size=11
reserve_size = block_reversed_size
key_size = rsa_key.size_in_bits()
if (key_size % 8) != 0:
raise RuntimeError('RSA 密钥长度非法')

# 密钥用来解密,解密不需要预留长度
if rsa_key.has_private():
reserve_size = 0

bs = int(key_size / 8) - reserve_size
except Exception as err:
print('计算加解密数据块大小出错', rsa_key, err)
return bs


# 返回块数据
def block_data(data, rsa_key):
bs = get_block_size(rsa_key)
for i in range(0, len(data), bs):
yield data[i:i + bs]


# 加密
def enc_bytes(self, data, key=None):
text = b''
try:
rsa_key = open(key, 'rb').read()
cipher = PKCS1_v1_5.new(rsa_key)
for dat in block_data(data, rsa_key):
cur_text = cipher.encrypt(dat)
text += cur_text
except Exception as err:
print('RSA加密失败', data, err)
return text


# 解密
def dec_bytes(self, data, key=None):
text = b''
try:
rsa_key = open(key, 'rb').read()

cipher = PKCS1_v1_5.new(rsa_key)
for dat in block_data(data, rsa_key):
# 如果选择 Cipher.PKCS1_OAEP
# cur_text = cipher.decrypt(dat)
cur_text = cipher.decrypt(dat, '解密异常')
text += cur_text
except Exception as err:
print('RSA解密失败', data, err)
return text
😊打 赏😊