[1075]OpenSSL和Python实现RSA Key公钥加密私钥解密

2023-10-27


基于非对称算法的RSA Key主要有两个用途,数字签名和验证(私钥签名,公钥验证),以及非对称加解密(公钥加密,私钥解密)。本文提供一个基于OpenSSL和Python进行非对称加解密的例子。

1. OpenSSL实现非对称加解密

1.1 生成私钥,并导出公钥

生成2048 bit的PEM格式的RSA Key:Key.pem

$ openssl genrsa -out Key.pem -f4 2048
Generating RSA private key, 2048 bit long modulus
.+++
...................................................................+++
e is 65537 (0x10001)

从私钥导出公钥:Key_pub.pem

$ openssl rsa -in Key.pem -pubout -out Key_pub.pem
writing RSA key

1.2 准备测试数据

为了简便起见,这里将字符串”Hello Rocky!”存放到文件msg.bin作为测试数据:

$ echo -n "Hello Rocky!" > msg.bin
$ hexdump -Cv msg.bin 
00000000  48 65 6c 6c 6f 20 52 6f  63 6b 79 21              |Hello Rocky!|
0000000c

1.3 公钥加密

使用公钥Key_pub.pem对测试数据msg.bin进行加密生成msg.bin.enc,并查看加密后的数据:

$ openssl rsautl -in msg.bin -out msg.bin.enc -inkey Key_pub.pem -pubin -encrypt -pkcs
$ hexdump -Cv msg.bin.enc 
00000000  8d a3 c8 7f fd 4c 32 ee  29 58 c8 38 56 bd 8b 78  |.....L2.)X.8V..x|
00000010  cc eb ae f5 fa 1f 79 bb  4c 9c f1 39 34 75 94 62  |......y.L..94u.b|
00000020  97 59 c7 28 b3 c4 6a 0c  41 18 d6 2d 04 45 6d e1  |.Y.(..j.A..-.Em.|
00000030  3f 03 94 74 fa ac 02 f1  fb 10 1a a2 6b 6b 57 56  |?..t........kkWV|
00000040  39 a4 cb 7f e0 34 a6 b1  68 c7 2b 67 20 ee 31 70  |9....4..h.+g .1p|
00000050  1f c4 da 37 af 20 d6 49  1a f1 56 4f e2 37 80 39  |...7. .I..VO.7.9|
00000060  ab 85 9b c8 d0 33 57 1e  64 cd ea 43 c8 3e 3d 21  |.....3W.d..C.>=!|
00000070  a8 0f 95 ec e3 60 45 43  80 55 c6 7f d9 ad 6e 4c  |.....`EC.U....nL|
00000080  df 51 4e 70 ea c7 89 24  55 6b ba d0 cc e4 32 1f  |.QNp...$Uk....2.|
00000090  88 80 d2 7e 72 ea d9 4a  6b ac d4 df c8 83 25 57  |...~r..Jk.....%W|
000000a0  d0 a3 f2 53 f1 40 bd 99  bf c7 a1 57 54 e2 da 2f  |...S.@.....WT../|
000000b0  73 e0 ef 96 4c c8 1e d9  87 6b c4 0a 3a d5 fc 8b  |s...L....k..:...|
000000c0  98 ab 35 1c 8e 6d 6d 38  9a d0 70 2e 26 0d dc f4  |..5..mm8..p.&...|
000000d0  8f ff e1 22 20 70 d5 83  7d 02 89 13 67 e5 e6 34  |..." p..}...g..4|
000000e0  53 95 b1 25 9e 43 a3 40  f3 1b 21 31 4d 96 24 91  |S..%.C.@..!1M.$.|
000000f0  28 2d b3 1e 60 e3 5e 04  82 fc 48 55 38 3e ae de  |(-..`.^...HU8>..|
00000100

这里使用:

  • -in 选项指定原始数据文件msg.bin
  • -out 选项指定加密后的输出文件msg.bin.enc
  • -inkey 选项指定用于加密的公钥Key_pub.pem,由于输入是公钥,所以需要使用选项-pubin来指出
  • -encrypt 选项表明这里是进行加密操作
  • -pkcs 选项指定加密处理过程中数据的填充方式,对于填充,可选项有:-pkcs, -oaep, -ssl, -raw,默认是-pkcs,即按照PKCS#1 v1.5规范进行填充

1.4 私钥解密

使用私钥Key.pem对加密后的数据msg.bin.enc进行解密,并将结果存放到msg.bin.dec文件中:

$ openssl rsautl -in msg.bin.enc -out msg.bin.dec -inkey Key.pem -decrypt -pkcs
$ hexdump -Cv msg.bin.dec 
00000000  48 65 6c 6c 6f 20 52 6f  63 6b 79 21              |Hello Rocky!|
0000000c

这里使用:

  • -in 选项指定待解密的数据文件msg.bin.enc
  • -out 选项指定解密后的输出文件msg.bin.dec
  • -inkey 选项指定用于解密的私钥Key.pem,由于输入是私钥,所以不再需要使用选项-pubin
  • -decrypt 选项表明这里是进行解密操作
  • -pkcs 选项指定解密处理过程中数据的填充方式,对于填充,可选项有:-pkcs, -oaep, -ssl, -raw,默认是-pkcs,即按照PKCS#1 v1.5规范进行填充

从上面hexdump的结果可见,已经成功解密,另外也可以通过对原始数据和解密后的数据计算md5校验和来确定:

$ md5sum msg.bin msg.bin.dec 
53fdc7c239dbd79fe76cb9525fadcd85  msg.bin
53fdc7c239dbd79fe76cb9525fadcd85  msg.bin.dec

显然,msg.bin和msg.bin.dec的md5校验和是一样的额。

2. Python实现非对称加解密

本文的加解密基于Python3下的cryptograhpy库,所以需要预先安装:

$ sudo pip3 install cryptography

由于cryptography依赖于cffi库,安装中可能会出错,此时只需要先安装libcffi-dev,再重新安装就好了。

$ sudo apt-get install libffi-dev

cryptograhpy库的官方文档: https://cryptography.io/en/latest/

为了和使用openssl rsault命令得到的结果进行对比,这里使用同样的Key和数据,包括:

  • Key:Key.pem和Key_pub.pem
  • 数据:msg.bin

这里不再将加密和解密分成两个文件进行讲解,而是将加密和解密都放到同一个文件中,先对数据msg.bin进行加密得到msg.bin.encrypted文件,然后再对加密后的数据进行解密,将解密的结果输出到文件msg.bin.decrypted中,代码文件rsa-enc-dec.py的内容如下:

#!/usr/bin/env python3

# 导入cryptography库的相关模块和函数
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

from cryptography.hazmat.primitives.asymmetric import padding

# 定义辅助函数,用于打印16进制数据
def dump_hex(buffer, sep=' ', indent=0, line_size=16):
    """
    辅助函数,将bytes数组以如下格式打印输出:
    0000: 40 71 37 d0 80 32 7f 04 d9 6d fb fc f7 6a 7d d4
    0010: 48 ad 75 79 7a 0d 6c 55 01 ed 45 d5 1e 75 33 a6
    :param buffer: 待打印数据
    :param sep: 各16进制数据之间的分隔符,默认用空格' '分隔
    :param indent: 打印输出前是否需要缩进,默认不缩进
    :param line_size: 每行输出16进制的数量,默认1行输出16个
    :return: 无返回值
    """
    # 计算缩进空格数
    leading = '%s' % ' '*indent
    # 循环打印每行16进制数据
    for x in range(0, len(buffer), line_size):
        # 打印缩进字符和当前行数据的起始地址
        print('%s%04X: ' % (leading, x), end='')
        # 将当前行数据制作成列表list,并打印
        line = ['%02x' % i for i in buffer[x:x+line_size]]
        print(*line, sep=sep, end='\n')


# 加密函数
def encrypt(src_file_name, dst_file_name, public_key_file_name):
    """
    对原始数据文件使用指定的公钥进行加密,并将加密输出到目标文件中
    :param src_file_name: 原始数据文件
    :param dst_file_name: 加密输出文件
    :param public_key_file_name: 用于加密的公钥
    :return: 加密结果的bytes数组
    """
    # 读取原始数据
    data_file = open(src_file_name, 'rb')
    data = data_file.read()
    data_file.close()

    # 读取公钥数据
    key_file = open(public_key_file_name, 'rb')
    key_data = key_file.read()
    key_file.close()

    # 从公钥数据中加载公钥 
    public_key = serialization.load_pem_public_key(
        key_data,
        backend=default_backend()
        )

    # 使用公钥对原始数据进行加密,使用PKCS#1 v1.5的填充方式
    out_data = public_key.encrypt(
        data,
        padding.PKCS1v15()
    )

    # 将加密结果输出到目标文件中
    # write encrypted data
    out_data_file = open(dst_file_name, 'wb')
    out_data_file.write(out_data)
    out_data_file.close()

    # 返回加密结果
    return out_data


# 解密函数
def decrypt(src_file_name, dst_file_name, private_key_file_name):
    """
    对原始数据文件使用指定的私钥进行解密,并将结果输出到目标文件中
    :param src_file_name: 原始数据文件
    :param dst_file_name: 解密输出文件
    :param private_key_file_name: 用于解密的私钥
    :return: 解密结果的bytes数组
    """
    # 读取原始数据
    data_file = open(src_file_name, 'rb')
    data = data_file.read()
    data_file.close()

    # 读取私钥数据
    key_file = open(private_key_file_name, 'rb')
    key_data = key_file.read()
    key_file.close()

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

    # 使用私钥对数据进行解密,使用PKCS#1 v1.5的填充方式
    out_data = private_key.decrypt(
        data,
        padding.PKCS1v15()
    )

    # 将解密结果输出到目标文件中
    out_data_file = open(dst_file_name, 'wb')
    out_data_file.write(out_data)
    out_data_file.close()

    # 返回解密结果
    return out_data

if __name__ == "__main__":
    data_file_name = r'msg.bin'
    encrypted_file_name = r'msg.bin.encrypted'
    decrypted_file_name = r'msg.bin.decrypted'

    private_key_file_name = r'Key.pem'
    public_key_file_name = r'Key_pub.pem'

    # 先对数据加密
    data = encrypt(data_file_name, encrypted_file_name, public_key_file_name)
    # 打印加密结果
    print("encrypted data:")
    dump_hex(data)

    # 对数据进行解密
    data = decrypt(encrypted_file_name, decrypted_file_name, private_key_file_name)
    # 打印解密结果
    print("decrypted data:")
    dump_hex(data)

运行rsa-enc-dec.py,控制台结果如下:

$ python3 rsa-enc-dec.py 
encrypted data:
0000: 1c a3 be d5 e6 b0 58 57 06 ee a3 49 f4 2f 54 da
0010: 6a 27 13 90 71 6e ca 26 ca 54 ef d2 d1 6d 69 99
0020: a4 31 fc 75 c7 bf 39 08 7e ee c8 59 d4 31 f0 62
0030: c7 6e 75 71 fd d2 b1 e1 67 ae 10 78 e4 a3 40 f7
0040: b2 6d 73 bd 49 f2 90 0d 75 d7 37 5e b2 00 35 94
0050: 3f 24 64 6e 48 5b f5 34 13 f5 80 c7 5f 0c 46 af
0060: fa 36 5d f0 79 13 5d 53 20 0f 97 79 d2 c2 31 ef
0070: c7 30 fe 1d 65 13 37 12 f0 3e 27 49 a5 2b 30 c0
0080: c3 8d be 4e 5e 8e 68 54 88 f9 1e d3 a6 5e b6 a9
0090: c9 29 cb f5 72 28 44 e8 81 be bc 36 e8 68 c5 dc
00A0: ef ad 3c cb 13 3a a5 07 ff b3 eb 3b 82 93 e9 b9
00B0: 56 7c 3b 0a e5 fb 87 49 f1 15 15 a5 a3 77 75 d0
00C0: 9f b6 66 ec 51 64 26 f0 5b c4 5f e6 16 31 17 b1
00D0: 18 82 56 32 d8 8d 49 ef 06 b1 84 a6 e0 d8 ce cf
00E0: d1 8b ea d3 06 0d 20 05 48 88 3c 9e 8c 9c 78 22
00F0: 2e 97 56 c4 6c 39 1e 71 19 1b 91 dc 70 0c a0 4d
decrypted data:
0000: 48 65 6c 6c 6f 20 52 6f 63 6b 79 21

对于解密后的数据msg.bin.decrypted和原始数据msg.bin,二者的md5校验值是一样的,说明加密后又成功进行了解密:

$ md5sum msg.bin msg.bin.decrypted 
53fdc7c239dbd79fe76cb9525fadcd85  msg.bin
53fdc7c239dbd79fe76cb9525fadcd85  msg.bin.decrypted

3. 非对称加解密的疑问

在对比Python和openssl命令的加密结果时,竟然发现二者居然不一样!!!纳尼?
但是Python和openssl又能够将这个加密的结果解密回原始数据!

然后,进一步实验,尝试用openssl命令对同一数据分别进行两次加密操作,结果也是不一样,如下:

$ openssl rsautl -in msg.bin -inkey Key_pub.pem -pubin -encrypt -hexdump -pkcs  
0000 - a0 6b 39 46 aa 27 9e 34-51 a2 62 0a fd fe a3 64   .k9F.'.4Q.b....d
0010 - b8 29 3b ca 1f 5e 08 1d-53 3f f4 66 3e e7 2f b6   .);..^..S?.f>./.
0020 - d5 e3 43 3b c7 c5 33 2b-3d b7 73 20 c0 01 97 39   ..C;..3+=.s ...9
0030 - 00 11 62 60 55 5f 19 cf-17 4d 7b 9d eb 9b a5 e0   ..b`U_...M{.....
0040 - 8c e6 08 a0 ed ee ea 2a-71 59 75 bf 5b 8f 67 c8   .......*qYu.[.g.
0050 - f6 a9 be ba d1 bf 18 77-ee 10 d7 01 5c 37 f2 03   .......w....\7..
0060 - 87 26 ae 66 de ea 51 c0-cf 1b 79 ad 85 cf dd b6   .&.f..Q...y.....
0070 - c0 25 37 74 26 c5 57 d1-a2 4a 42 cc 89 a3 ba 23   .%7t&.W..JB....#
0080 - f7 dc 75 e3 cb 95 9a 63-31 f7 9a 24 17 29 03 66   ..u....c1..$.).f
0090 - 15 05 0a f6 fa 93 ef 47-c9 2c 27 9b e1 0d c1 b9   .......G.,'.....
00a0 - 3c 50 b5 f5 56 fb bb 62-db 48 7a 02 31 7e 63 03   <P..V..b.Hz.1~c.
00b0 - 39 1d d3 bb d3 97 65 9f-f1 74 9f b4 6e 72 4b 85   9.....e..t..nrK.
00c0 - 35 33 7c 7c a2 f8 48 98-60 fb a8 84 cb c6 18 70   53||..H.`......p
00d0 - 4d 33 de 44 fd 6d b4 8a-ed fb 10 b6 fb 7f 32 6a   M3.D.m........2j
00e0 - af 0b ee 22 bf 43 fd 42-fc 18 3f 38 73 5b b7 6b   ...".C.B..?8s[.k
00f0 - f8 3d 0a d5 cf c6 97 69-27 24 a3 2f f6 a7 9d f1   .=.....i'$./....
$
$ openssl rsautl -in msg.bin -inkey Key_pub.pem -pubin -encrypt -hexdump -pkcs
0000 - 70 5f 69 06 e0 59 b1 56-33 1f 05 54 03 29 8a a2   p_i..Y.V3..T.)..
0010 - 31 5e c9 68 a2 68 ac f7-e0 5c 89 23 47 fb 86 f9   1^.h.h...\.#G...
0020 - d3 d5 6b 19 ec 14 83 35-60 12 7d bf a5 de 7c d5   ..k....5`.}...|.
0030 - 74 0c 50 77 34 50 63 1a-d9 ae c4 74 c9 bd ce 72   t.Pw4Pc....t...r
0040 - 09 60 6d fd 55 9f e3 5e-4a 0e 3d 20 ec d8 2f 5c   .`m.U..^J.= ../\
0050 - e2 fe 21 64 a3 aa 65 67-1e d5 a1 70 4c 59 9f 8d   ..!d..eg...pLY..
0060 - 79 6c cf 8d d6 f0 cd 66-bd e2 be 74 6a 7b 53 5c   yl.....f...tj{S\
0070 - da 2e 43 23 1c 0a 59 5e-81 f7 76 aa 17 cd 3b ca   ..C#..Y^..v...;.
0080 - d5 1d 45 3a 2c 35 cf 9a-cf 33 ff a8 1d 91 37 e1   ..E:,5...3....7.
0090 - 20 ad 71 f3 87 bc db e1-d2 52 86 30 eb 02 0c 1f    .q......R.0....
00a0 - 4e a2 73 81 f0 84 6c 31-2e 4a c1 04 c9 3f e9 6c   N.s...l1.J...?.l
00b0 - e5 30 63 d0 3b fc 74 0a-b7 53 29 0a d8 a3 b6 a6   .0c.;.t..S).....
00c0 - 1d 8f e6 ec f0 8f 20 c6-e4 f6 bf 29 34 0f 0b a1   ...... ....)4...
00d0 - a9 19 2a cf 0a dc aa 3d-e4 6a 44 06 99 be 37 35   ..*....=.jD...75
00e0 - 59 57 43 f1 fb df 8d 19-45 64 eb 06 b7 d5 23 c9   YWC.....Ed....#.
00f0 - e3 19 98 a1 c9 80 6b 54-aa c6 d4 73 04 d9 06 fd   ......kT...s....

个人猜测可能是加密中引入了随机变量,导致加密结果不一样,例如此处原始数据只有12字节,通过随机填充再进行加密,导致结果不一样了。

是的,这个问题跟对数据的padding即填充有关,详细说来,是跟PKCS #1 v1.5指定的padding方式有关,下面对这个问题进行详细的说明。

为什么RSA公钥每次加密得到的结果都不一样

1. 问题的来源

1.1 准备测试数据

将字符串"I Love China!"保存到msg.bin作为测试数据

$ echo -n "I Love China!" > msg.bin
$ hexdump -Cv msg.bin 
00000000  49 20 4c 6f 76 65 20 43  68 69 6e 61 21           |I Love China!|
0000000d

随机生成测试用的私钥key.pem,并导出公钥key_pub.pem:

$ openssl genrsa -out key.pem -f4 2048
Generating RSA private key, 2048 bit long modulus
........+++
.......+++
e is 65537 (0x10001)
$ openssl rsa -in key.pem -pubout -out key_pub.pem
writing RSA key

1.2 使用私钥对同一数据签名

使用私钥key.pem对msg.bin进行两次签名,签名结果分别为msg.bin.sig1和msg.bin.sig2:

$ openssl dgst -sha256 -out msg.bin.sig1 -sign key.pem msg.bin
$ openssl dgst -sha256 -out msg.bin.sig2 -sign key.pem msg.bin 
$ md5sum msg.bin.sig1 msg.bin.sig2
4d10a2163f92f90f114126de2371deb8  msg.bin.sig1
4d10a2163f92f90f114126de2371deb8  msg.bin.sig2

从签名数据的md5校验和看,msg.bin.sig1和msg.bin.sig2的md5值一样,其内容显然是一样的。

1.3 使用公钥对同一数据加密

使用公钥key_pub.pem对msg.bin进行两次加密,加密结果分别为msg.bin.enc1和msg.bin.enc2:

$ openssl rsautl -in msg.bin -out msg.bin.enc1 -inkey key_pub.pem -pubin -encrypt
$ openssl rsautl -in msg.bin -out msg.bin.enc2 -inkey key_pub.pem -pubin -encrypt
$ md5sum msg.bin.enc1 msg.bin.enc2
19c3bf692e94eaf87770001181c5eb10  msg.bin.enc1
f576f31a796332fcd3c4d3627ef4ad5d  msg.bin.enc2

显然msg.bin.enc1与msg.bin.enc2的md5不一样,二者的内容也不一样,也就是说,使用同一个RSA公钥对同一段数据加密,两次加密的结果不一样。

2. PKCS #1 v1.5指定的填充方式

除了PKCS #1 v1.5指定的填充方式外,后续版本对填充方式进行了更新:

  • PKCS #1 v2.0 指定了针对加密使用的OAEP填充方式
  • PKCS #1 v2.1 又进一步指定了针对签名使用的PSS填充方式

这里只讨论早期PKCS #1 v1.5指定的简单的填充方式,也是目前最常见的填充方式。

RSA建议,为提高安全性,在新的应用中应逐步采用OAEP和PSS方式的进行填充。

2.1 填充方式的描述

不管是使用RSA私钥进行签名还是公钥进行加密,操作中都需要对待处理的数据先进行填充,然后再对填充后的数据进行加密处理。针对如何对内容进行填充,"[RFC2313] PKCS #1: RSA Encryption Version 1.5“的”8.1 Encryption-block formatting"节提供了详细的说明,原文如下:

8.1 Encryption-block formatting

   A block type BT, a padding string PS, and the data D shall be
   formatted into an octet string EB, the encryption block.

              EB = 00 || BT || PS || 00 || D .           (1)

   The block type BT shall be a single octet indicating the structure of
   the encryption block. For this version of the document it shall have
   value 00, 01, or 02. For a private- key operation, the block type
   shall be 00 or 01. For a public-key operation, it shall be 02.

   The padding string PS shall consist of k-3-||D|| octets. For block
   type 00, the octets shall have value 00; for block type 01, they
   shall have value FF; and for block type 02, they shall be
   pseudorandomly generated and nonzero. This makes the length of the
   encryption block EB equal to k.

由于篇幅的原因,这里没有列出针对这段话的"Notes"部分,可以通过点击原文链接查看。

简单说来,PKCS #1 v1.5规定的填充格式为:

EB = 00 || BT || PS || 00 || D

其中:

D: data (指待处理数据,即填充前的原始数据)
PS: padding string (填充字符串)
BT: block type (数据块类型)
EB: encryption block (待加密的数据块,经过填充后结果)
||: 表示连接操作 (X||Y表示将X和Y的内容连接到一起)

所以:

"填充后数据" = "00" + "数据块类型" + "填充字符串" + "00" + "原始数据"

"填充块类型"BT决定了紧挨着的"填充字符串"PS的内容。
BT的可能取值包括00, 01和02:

  • 针对私钥处理的数据,BT取值为00或01;
    • BT取值为00时,PS为全00的字符串
    • BT取值为01时,PS为全FF的字符串,通过填充得到的整数会足够大,可以阻止某些攻击,因此也是推荐的填充方式
  • 针对公钥处理的数据,BT取值为02;
    • 使用伪随机的16进制字符串填充PS,而且每次操作进行填充的伪随机书都是独立的

重点来了,针对公钥处理的数据,其填充内容为伪随机的16进制字符串,每次操作的填充内容都不一样。这就是为什么每次使用公钥加密数据得到的结果都不一样了。

下面还是以本文开始的公钥加密数据进行说明。

2.2 检查公钥加密的填充数据

第1节中,使用公钥key_pub.pem对数据msg.bin进行了加密,我们使用私钥key.pem解密看看加密前填充的数据。

2.2.1 解密msg.bin.enc1到msg.bin.enc1.dec
$ openssl rsautl -in msg.bin.enc1 -out msg.bin.enc1.dec -inkey key.pem -decrypt -raw

为了显示填充结构,这里用UltraEdit打开解密后的数据:
image.png

2.2.2 解密msg.bin.enc2到msg.bin.enc2.dec
$ openssl rsautl -in msg.bin.enc2 -out msg.bin.enc2.dec -inkey key.pem -decrypt -raw

为了显示填充结构,这里用UltraEdit打开解密后的数据:
image.png

上面两张图都是对数据msg.bin进行填充后,并且在使用公钥key_pub.pem加密前的内容:

  • 两个绿色部分是指定的“00”填充;
  • 紫色部分指定BT为02, 说明后续使用公钥处理的数据;
  • 灰色部分为PS,其根据BT=02,填充了伪随机数;
  • 橙色部分为原始数据。

可见,两次填充的伪随机数是不一样,这样在使用公钥加密后其结果自然就不一样了。

我看网上有帖子说JAVA下可以通过设置,使每次填充生成同样的内容,但这样似乎不够安全。
我在openssl工具下没有找到相应的针对公钥操作时数据的填充设置选项。

2.3 检查私钥加密的填充数据

在第1节的签名例子中,使用私钥签名时,会先对数据计算SHA256编码,然后对SHA256哈希进行BER编码,再进行填充。
我们可以通过对签名数据使用公钥解密后,看看填充数据的内容:

$ openssl rsautl -in msg.bin.sig1 -out msg.bin.sig1.dec -inkey key_pub.pem -pubin -verify -raw

注意:这里针对openssl rsault 命令使用-raw选项显示原始的数据结构,否则只会显示数据部分,而不会显示填充的内容

按照惯例,为了显示填充结构,这里用UltraEdit打开解密后的签名数据:
image.png

上图中是对SHA256数据进行BER编码填充,使用私钥key.pem加密前的内容:

  • 两个绿色部分是指定的“00”填充;
  • 紫色部分指定BT为01, 说明后续是使用私钥处理的数据;
  • 灰色部分为PS,其根据BT=01,填充为全FF的数据;
  • 橙色部分为原始数据(即SHA256数据经过BER编码的内容)。

再来假设下BT为00的情况:
当BT为00时,此时填充的内容PS部分为全00,又由于填充格式中指定了两个“00”的分隔符,如果此时原始数据又是以“00”开始的话,如何划分填充数据和原始数据呢?
显然此时是无法进行区分的,除非你知道原始数据由多长。幸运的是,我们基本上不会采用BT=00的填充方式,所以这种情况几乎不会发生。

3. 一个"openssl rsautl"的bug

最后,以我发现的一个"openssl rsautl"命令的bug来结束本文。

在进行RSA公钥加密私钥解密时,如果加密的原始数据为全00,解密时通过-hexdump选项,无法正确在控制台显示原始数据,问题的复现步骤如下:

生成16字节全0数据:

$ dd if=/dev/zero of=data.bin bs=1 count=32
32+0 records in
32+0 records out
32 bytes (32 B) copied, 0.000227422 s, 141 kB/s
$ hexdump -Cv data.bin 
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020

使用公钥key.pem对全0数据data.bin进行加密:

$ openssl rsautl -in data.bin -out data.bin.enc -inkey key_pub.pem -pubin -encrypt

使用私钥解密数据,并直接在控制台显示:

$ openssl rsautl -in data.bin.enc -inkey key.pem -decrypt -hexdump
0020 - <SPACES/NULS>
$ openssl rsautl -in data.bin.enc -inkey key.pem -decrypt -hexdump -raw
0000 - 00 02 49 d0 3d 94 b6 90-8b 4e 40 e7 e4 d1 e2 90   ..I.=....N@.....
0010 - e8 18 29 7f cf 5c c7 4a-bd cc 11 97 81 b3 a7 2b   ..)..\.J.......+
0020 - c3 83 43 60 6d f9 d5 1d-e9 29 ab 51 d1 98 58 49   ..C`m....).Q..XI
0030 - 22 5b ae d2 27 d4 da bd-2b 0d ba 54 7e 04 10 b1   "[..'...+..T~...
0040 - 3b c7 a1 7d 57 02 d8 1c-53 41 91 52 2d ac 0b da   ;..}W...SA.R-...
0050 - 1e 0e 0b 94 b3 90 0d 20-49 2c 94 84 22 87 30 c1   ....... I,..".0.
0060 - 31 36 6e 18 e0 52 5e ec-59 f9 36 47 37 c6 45 1e   16n..R^.Y.6G7.E.
0070 - 24 db dc 9a e4 d8 4e dc-b6 0b b2 57 f4 27 a9 56   $.....N....W.'.V
0080 - 05 11 2d 03 75 75 64 58-87 b8 86 8d 4c 3d d5 f4   ..-.uudX....L=..
0090 - 10 34 9d 24 ab 48 b4 27-58 59 f7 27 3b 9b 39 5a   .4.$.H.'XY.';.9Z
00a0 - b4 ed 2e fb 1f 6e 1f 13-b3 cd 67 78 5f ec f5 63   .....n....gx_..c
00b0 - ae 46 a7 17 87 f7 10 09-32 6d 30 dc 0e 1b 48 2a   .F......2m0...H*
00c0 - 2c 27 09 bc 42 32 38 22-8c 76 c9 cb 8c e9 0d f9   ,'..B28".v......
00d0 - 5e d0 c3 a8 af 8f cd 68-b6 96 3c 94 5c 3c d1      ^......h..<.\<.
0100 - <SPACES/NULS>

这里使用私钥解密后竟然没有原始数据,你说神奇不神奇?我第1次使用不带"-raw"选项操作,发现命令输出没有数据显示时简直大吃了一惊。
还好,如果将解密的数据送到指定文件再显示则没有问题:

$ openssl rsautl -in data.bin.enc -out data.bin.enc.dec -inkey key.pem -decrypt -raw
$ hexdump -Cv data.bin.enc.dec 
00000000  00 02 49 d0 3d 94 b6 90  8b 4e 40 e7 e4 d1 e2 90  |..I.=....N@.....|
00000010  e8 18 29 7f cf 5c c7 4a  bd cc 11 97 81 b3 a7 2b  |..)..\.J.......+|
00000020  c3 83 43 60 6d f9 d5 1d  e9 29 ab 51 d1 98 58 49  |..C`m....).Q..XI|
00000030  22 5b ae d2 27 d4 da bd  2b 0d ba 54 7e 04 10 b1  |"[..'...+..T~...|
00000040  3b c7 a1 7d 57 02 d8 1c  53 41 91 52 2d ac 0b da  |;..}W...SA.R-...|
00000050  1e 0e 0b 94 b3 90 0d 20  49 2c 94 84 22 87 30 c1  |....... I,..".0.|
00000060  31 36 6e 18 e0 52 5e ec  59 f9 36 47 37 c6 45 1e  |16n..R^.Y.6G7.E.|
00000070  24 db dc 9a e4 d8 4e dc  b6 0b b2 57 f4 27 a9 56  |$.....N....W.'.V|
00000080  05 11 2d 03 75 75 64 58  87 b8 86 8d 4c 3d d5 f4  |..-.uudX....L=..|
00000090  10 34 9d 24 ab 48 b4 27  58 59 f7 27 3b 9b 39 5a  |.4.$.H.'XY.';.9Z|
000000a0  b4 ed 2e fb 1f 6e 1f 13  b3 cd 67 78 5f ec f5 63  |.....n....gx_..c|
000000b0  ae 46 a7 17 87 f7 10 09  32 6d 30 dc 0e 1b 48 2a  |.F......2m0...H*|
000000c0  2c 27 09 bc 42 32 38 22  8c 76 c9 cb 8c e9 0d f9  |,'..B28".v......|
000000d0  5e d0 c3 a8 af 8f cd 68  b6 96 3c 94 5c 3c d1 00  |^......h..<.\<..|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000100

看吧,通过后者得到的输出,其全0数据在结果的最后两行都好好的在呢,虚惊一场啊。

来源:https://blog.csdn.net/guyongqiangx/article/details/74732434
https://blog.csdn.net/guyongqiangx/article/details/74930951

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[1075]OpenSSL和Python实现RSA Key公钥加密私钥解密 的相关文章

随机推荐

  • 将字符串格式的时间格式化

    时间格式化 param Number date 时间戳 param DateString fmt 时间格式 dateFormat yyyy MM dd hh mm ss S gt 2016 03 12 20 13 32 232 return
  • Object.create 以及 Object.setPrototypeOf

    第一部分 Object crate 方法是es5中的关于原型的方法 这个方法会使用指定的原型对象以及属性去创建一个新的对象 语法 Object create proto propertiesObjecy 参数 proto 必须的 一个对象
  • 云计算简介

    什么是 云 迁移至云端 在云中运行 在云中存储 从云端访问 当今时代 似乎一切都在 云 中进行 但是 云 究竟是一个什么样的概念 简单来说 云就是互联网连接的另一端 你可以从云端访问各种应用程序和服务 也可以在云端安全存储你的数据 云 之所
  • 扫描二维码 跳转到小程序指定页面

    注意 必须发布代码之后才能使用扫描二维码跳转 规则 1 二维码规则 填写你要生成的二维码的链接 2 小程序功能页面 要跳转的小程序的页面 3 测试链接 也填同样的链接 4 将上面的链接生成一个二维码 测试链接 5 通过微信扫描这个二维码跳转
  • 安装Apex库

    在Linux系统下安装Apex库 1 安装流程 按顺序使用如下命令 git clone https github com NVIDIA apex cd apex pip3 install v no cache dir 注意 不能直接使用pi
  • iOS 多线程

    1 怎么用GCD实现多读单写 dispatch barrier async 2 ios系统为我们提供的几种多程序技术各自的特点是怎样的 GCD 实现一些简单的线程同步 子线程的分派 包括一些类似于多读单写 nsoperation 比如adn
  • 2022年最佳的9种逆向工程工具[持续更新]

    逆向是复杂的 然而 软件开发人员经常在面临一项具有挑战性的任务时转向反向工程 增强软件安全性 使软件与第三方组件兼容 维护遗留代码 等等 在本文中 我们将描述我们的软件逆向程序在工作中所依赖的主要工具 并展示如何使用这些工具的实际示例 本文
  • python输入多组测试数据_python ddt数据驱动实例代码分享

    python ddt数据驱动最简实例 在接口自动化测试中 往往一个接口的用例需要考虑 正确的 错误的 异常的 边界值等诸多情况 然后你需要写很多个同样代码 参数不同的用例 如果测试接口很多 不但需要写大量的代码 测试数据和代码柔合在一起 可
  • STM32基于HAL库带FreeRTOS系统的Freemodbus移植

    STM32基于HAL库移植带FreeRTOS系统的Freemodbus移植 移植前提 下载所需源码 可能的win10 IAR设置 从站注意定义寄存器数量大小 效果查询报文 效果回复报文 移植事件 定时器 串口 事件移植 串口移植 定时器移植
  • Electron+React+Antd将网页打包成应用程序完整步骤(electron-builder (搭建热更新) 或 electron-packager(不搭建热更新))

    一 创建React项目 ps 由于写的时候没注意 包安装有的用npm有的用yarn 大家统一用一个就行 尽量不要使用cnpm ps 源码地址 git clone https github com Aug Sunrise electron t
  • 【mysql】mysql表分区、索引的性能测试

    概述 mysql分区表概述 google搜索一下 RANGE COLUMNS partitioning 主要测试mysql分区表的性能 load 500w 条记录 大约在10min左右 batch insert 1 9w条记录 没建立索引
  • 小米升级后开机显示无服务器,小米手机升级后无法开机解决方法

    方法1 刷机 在关机的状态下 进rec中双清 音量上键和开机键按住出来第一个mi画面全部松手 再按住音量加 然后在双清 然后关机音量下键和开机键同时摁住进入fastboot模式 出现米兔修机器人界面 线刷开发版刷机包和教程可以到http w
  • vue3中keep-alive和vue-router的结合使用

    vue3中keep alive和vue router的结合使用 前言 代码 一 为何要使用keep alive 二 vue2中使用keep alive 将 router view 组件包含于 keep alive 即可 三 vue3中使用k
  • 打印二叉树

    摘要 https wylu github io posts 91f36751 本文将使用两种展示方式打印一颗二叉树 效果如下 8 12 14 20 15 13 10 11 9 4 6 7 5 2 3 1 20
  • 50 openEuler搭建PostgreSQL数据库服务器-配置环境

    文章目录 50 openEuler搭建PostgreSQL数据库服务器 配置环境 50 1 关闭防火墙并取消开机自启动 50 2 修改SELINUX为disabled 50 3 创建组和用户 50 4 创建数据盘 50 4 1 方法一 在r
  • 21、同步与异步(三种方法)

    1 同步 按顺序一条一条数据执行 1 同步 按顺序一条一条数据执行 console log 第1条数据 console log 第2条数据 console log 第3条数据 console log 第4条数据 console log 第5
  • flowable工作流简介

    官网地址 https www flowable org Flowable6 3中文教程 https tkjohn github io flowable userguide introduction Flowable Modeler 流程定义
  • 数据结构-树(树基本实现C++)

    树形结构是一种重要的非线性数据结构 其中树和二叉树最为常用 直观看来树是以分支关系定义的层次结构 树形结构是我们平时比较熟悉的 比如文件夹目录 公司组织关系等 在计算机领域也得到广泛的应用 编译程序就是以树来表示源程序的语法结构 二叉树是一
  • osgEarth的Rex引擎原理分析(一二六)rex瓦片组织方式

    目标 一二五 中问题212 通过如下确定瓦片的组织方式 核心是profile osgEarth Map cpp void Map calculateProfile collect the terrain layers we will nee
  • [1075]OpenSSL和Python实现RSA Key公钥加密私钥解密

    文章目录 1 OpenSSL实现非对称加解密 1 1 生成私钥 并导出公钥 1 2 准备测试数据 1 3 公钥加密 1 4 私钥解密 2 Python实现非对称加解密 3 非对称加解密的疑问 为什么RSA公钥每次加密得到的结果都不一样 1