公钥密码算法的研究与使用

1.实验目的

  • 理解非对称加密算法的基本原理。

  • 了解现有RSA算法的原理以及椭圆曲线公钥密码算法的原理与优缺点。

  • 基于C++、Java或.net中成熟的RSA或SM2等加密库实现一个非对称加密解密算法程序。

2.相关理论和实验环境

2.1. 相关基本理论

2.1.1 公钥密码算法加密与解密的基本原理

1)公钥密码算法的发展历史、研究现状、应用现状

  • 发展历史:

    1976年:Diffie和Hellman提出了Diffie-Hellman密钥交换算法[1],为非对称加密奠定了基础。
    1977年:Rivest、Shamir和Adleman三位科学家提出了RSA算法,这是第一个实用的公钥密码算法。
    1985年:Victor Miller和Neal Koblitz独立提出了椭圆曲线密码学(Elliptic Curve Cryptography, ECC),这是另一种强大的公钥加密算法。

  • 研究现状及应用现状

    如今,公钥密码算法已广泛应用于各种领域,如电子商务、数字签名、数据加密等,更复杂的包括:身份加密、属性加密、功能加密、全同态加密等等[2]。常用的公钥密码算法包括RSA、ECC、ElGamal等。公钥密码算法在信息安全中扮演着关键角色,特别是在确保数据传输的机密性、完整性和真实性方面。

  • 基本原理:公钥密码算法(Public Key Cryptography),也称为非对称加密算法,是现代密码学的一个重要分支,其概念首次由Whitfield Diffie和Martin Hellman在1976年提出。在此之前,密码学主要依赖对称加密算法,即使用相同的密钥进行加密和解密。非对称加密的创新在于使用一对相关的密钥:一个公开的公钥和一个保密的私钥。公钥密码的两个主要组成部分是公钥密码和公钥分发算法,即密钥是由公钥和私钥组成不再是机密内容[2]

2)解释公钥、私钥、电子签名、数字证书等基本概念。

  • 公钥(Public Key):公开的密钥,任何人都可以获得,用于加密数据或验证签名。
  • 私钥(Private Key):保密的密钥,仅拥有者持有,用于解密数据或生成签名。
    在非对称加密中,公钥和私钥成对出现,公钥用于加密,私钥用于解密。一个用户的公钥可以公开给所有人,但私钥必须严格保密。
  • 电子签名:通过数字手段实现的签名,通常用于验证数据的来源和完整性。
  • 数字证书:又称为数字标识,是一串在通信中用于标识身份信息的数字,通常由权威证书颁发机构CA签发,从而建立了实体身份和证书公钥之间权威可信的关联关系,这个关联关系采用数字签名或等效的密码学技术保证[3]

3) 比较软件数字证书与硬件数字证书在安全性、易用性、可靠性等方面的异同。

  • 软件数字证书:

    便于备份和复制:可以轻松复制到多个设备或位置,便于备份。

    便捷:安装和使用简单,适用于大多数操作系统和应用程序。

    配置复杂:在不同设备间迁移或重装系统时,可能需要重新配置。

  • 硬件数字证书:

    即插即用:许多硬件证书设备支持即插即用,无需复杂的配置过程。

    依赖硬件设备:用户需要随身携带硬件设备,可能不方便。

    高可靠性:硬件设备通常具有较高的可靠性,不易受操作系统问题影响。

特性 软件数字证书 硬件数字证书
安全性 易受攻击和盗取,密钥管理难度大 高安全性,防篡改,防盗
易用性 便捷,无需额外设备,配置复杂 即插即用,需要携带设备,可能依赖驱动程序
可靠性 灵活易备份,受系统崩溃影响,安全性较弱 高可靠性,不易受系统影响,设备故障或丢失可能导致问题

2.1.2 公钥密码算法的基本原理
1)列举不少于两种公钥密码算法。

  • RSA算法

  • 椭圆曲线加密算法(Elliptic Curve Cryptography, ECC)

2)说明以上列举的公钥密码算法的基本原理与优缺点。

  • RSA算法

    原理:

    m表示明文,用c表示密文(mc均小于n),则加密和解密运算为[4]:

    (1)加密: c=E(m)≡me(modn);

    (2)解密: m=D(c)≡dd(modn)。

    优点:
    安全性高:RSA算法的安全性基础是大整数的质因子分解是困难的[1]
    广泛应用:广泛用于数字签名、密钥交换等。
    缺点:
    计算复杂:大整数运算需要大量计算资源。
    密钥长度较长:为了保证安全性,需要较长的密钥。

  • 椭圆曲线加密算法(ECC)

    基本原理:

    算法协议层实现数字签名验证和数字明文加解密,而群运算层实现点加、倍点和点乘运算,群运算最终可转化为有限域上的模运算[5]

    优点:
    安全性高:基于椭圆曲线离散对数问题的难度。
    密钥长度短:在提供相同安全级别下,ECC的密钥长度比RSA短得多,计算更高效。
    缺点:
    实现复杂:椭圆曲线的数学理论较复杂。
    标准化不足:不同系统间的互操作性可能存在问题。

3)说明拟在实现中采用的公钥密码算法。
在本次实验中,选择使用 RSA算法。选择RSA算法的原因如下:

实现相对简单: 相对于ECC,RSA的实现较为简单,适合在学习和实验中进行实现和理解。
广泛应用: RSA算法是最早也是应用最广泛的公钥加密算法之一,有丰富的文档和支持库,便于实验实现。
性能和安全性: 虽然ECC在性能上有优势,但对于学习和基本应用,RSA提供了足够的性能和安全性。

2.2 实验环境构建

C++: 有许多成熟的加密库,如Crypto++、OpenSSL等,支持广泛的加密算法,包括RSA、AES等。C++库通常具有较高的性能和灵活性。

Java: Java也有强大的加密库,如Java Cryptography Architecture (JCA)、Bouncy Castle等,这些库集成方便,易于使用,且提供了丰富的文档和社区支持。

1) 选择语言及原因

选择Java作为开发语言,原因如下 :

  • Java广泛用于企业级应用,具有良好的跨平台性。
  • 丰富的加密库和社区支持,如javax.crypto包和第三方库BouncyCastle。
  • 已经安装配置过Java环境。

3. 实验内容和结果分析

3.1程序的设计和实现框架

1.使用Java和Bouncy Castle实现RSA加密的代码

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package password_test2;

import java.io.*;
import java.nio.file.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import java.util.Base64;

public class EncryptionDecryption {

public static void main(String[] args) {
try {
// 生成密钥对并存储到文件
generateKeyPair("publickey1.txt", "privatekey1.txt");
generateKeyPair("publickey2.txt", "privatekey2.txt");

// 读取原始明文
String plaintext = new String(Files.readAllBytes(Paths.get("plaintext.txt")));

// 加密明文
String ciphertext = encrypt(plaintext, "privatekey1.txt");
Files.write(Paths.get("ciphertext.txt"), ciphertext.getBytes());

// 解密密文
String decryptedText = decrypt(ciphertext, "publickey1.txt");
Files.write(Paths.get("result.txt"), decryptedText.getBytes());
System.out.println("Decrypted text: " + decryptedText);

} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 生成RSA密钥对并将公钥和私钥分别存储到指定文件中
*
* @param publicKeyFile 公钥文件名
* @param privateKeyFile 私钥文件名
* @throws Exception 如果发生任何异常
*/
public static void generateKeyPair(String publicKeyFile, String privateKeyFile) throws Exception {
// 创建KeyPairGenerator对象,指定算法为RSA
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
// 初始化KeyPairGenerator对象,密钥长度为2048位
keyGen.initialize(2048);
// 生成密钥对
KeyPair keyPair = keyGen.generateKeyPair();

// 获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

// 将公钥和私钥分别编码为Base64并写入文件
Files.write(Paths.get(publicKeyFile), Base64.getEncoder().encode(publicKey.getEncoded()));
Files.write(Paths.get(privateKeyFile), Base64.getEncoder().encode(privateKey.getEncoded()));
}

/**
* 使用指定的公钥文件加密明文
*
* @param plaintext 要加密的明文
* @param keyFile 公钥文件名
* @return 加密后的密文
* @throws Exception 如果发生任何异常
*/
public static String encrypt(String plaintext, String keyFile) throws Exception {
// 从文件中读取公钥
PublicKey publicKey = getPublicKey(keyFile);
// 创建Cipher对象,指定算法为RSA
Cipher cipher = Cipher.getInstance("RSA");
// 初始化Cipher对象为加密模式
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 执行加密操作
byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes());
// 将加密后的字节数组编码为Base64字符串并返回
return Base64.getEncoder().encodeToString(encryptedBytes);
}

/**
* 使用指定的私钥文件解密密文
*
* @param ciphertext 要解密的密文
* @param keyFile 私钥文件名
* @return 解密后的明文
* @throws Exception 如果发生任何异常
*/
public static String decrypt(String ciphertext, String keyFile) throws Exception {
// 从文件中读取私钥
PrivateKey privateKey = getPrivateKey(keyFile);
// 创建Cipher对象,指定算法为RSA
Cipher cipher = Cipher.getInstance("RSA");
// 初始化Cipher对象为解密模式
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 执行解密操作
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
// 将解密后的字节数组转换为字符串并返回
return new String(decryptedBytes);
}

/**
* 从文件中读取公钥
*
* @param filename 公钥文件名
* @return 公钥对象
* @throws Exception 如果发生任何异常
*/
private static PublicKey getPublicKey(String filename) throws Exception {
// 从文件中读取字节数组
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
// 创建X509EncodedKeySpec对象
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(keyBytes));
// 获取KeyFactory对象,指定算法为RSA
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 生成并返回公钥
return keyFactory.generatePublic(spec);
}

/**
* 从文件中读取私钥
*
* @param filename 私钥文件名
* @return 私钥对象
* @throws Exception 如果发生任何异常
*/
private static PrivateKey getPrivateKey(String filename) throws Exception {
// 从文件中读取字节数组
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
// 创建PKCS8EncodedKeySpec对象
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyBytes));
// 获取KeyFactory对象,指定算法为RSA
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 生成并返回私钥
return keyFactory.generatePrivate(spec);
}
}



3.1 密钥生成过程实验

程序从当前目录(程序所在目录)的plaintext.txt文件中读取原始明文。

1
This is the second test of the class.

程序从当前目录的publickey1.txt、publickey2.txt文件(公钥)中读取公钥,从当前目录的privatekey1.txt、privatekey2.txt文件(私钥)中读取私钥。

publickey1.txt

image-20240607221015978

publickey2.txt

image-20240607220921284

privatekey1.txt

image-20240607220849336

privatekey2.txt

image-20240607221003598

3.2 基本加解密实验

从plaintext.txt文件中读取明文文本,并将加密后的密文存储到ciphertext.txt文件中。

image-20240607221135517

执行解密算法,从ciphertext.txt文件中读取密文文本,进行解密,并在控制台或文本框中输出得到的明文。得到的明文同时存储至result.txt文件中。

image-20240607221221254

采用第1对密钥对中的私钥进行加密,观察得到的密文是否与原始明文相同;采用第1对密钥对中的公钥进行解密。观察得到的明文是否与原始明文相同。

1
2
3
4
5
6
7
8
9
10
11
// 读取原始明文
String plaintext = new String(Files.readAllBytes(Paths.get("plaintext.txt")));

// 加密明文
String ciphertext = encrypt(plaintext, "privatekey1.txt");
Files.write(Paths.get("ciphertext.txt"), ciphertext.getBytes());

// 解密密文
String decryptedText = decrypt(ciphertext, "publickey1.txt");
Files.write(Paths.get("result.txt"), decryptedText.getBytes());
System.out.println("Decrypted text: " + decryptedText);

image-20240607222524590

image-20240607222606356

第1对密钥对中的私钥进行加密,观察得到的密文与原始明文不同;

第1对密钥对中的公钥进行解密。观察得到的明文与原始明文相同

多次执行的结果相同,密文每次都发生了改变

image-20240607222739331

3.3 多对公钥私钥混合实验

采用私钥1进行加密,而后采用公钥2进行解密

1
2
3
4
5
6
7
8
// 加密明文
String ciphertext = encrypt(plaintext, "privatekey1.txt");
Files.write(Paths.get("ciphertext.txt"), ciphertext.getBytes());

// 解密密文
String decryptedText = decrypt(ciphertext, "publickey2.txt");
Files.write(Paths.get("result.txt"), decryptedText.getBytes());
System.out.println("Decrypted text: " + decryptedText);

实验结果:

​ 程序不能正常进行。

原因分析:RSA加密体系中,公钥和私钥是成对出现的。私钥1加密的数据只能由公钥1解密,公钥2无法解密私钥1加密的数据。

采用公钥1进行加密,而后采用私钥2进行解密

1
2
3
4
5
6
7
8
// 加密明文
String ciphertext = encrypt(plaintext, "publickey1.txt");
Files.write(Paths.get("ciphertext.txt"), ciphertext.getBytes());

// 解密密文
String decryptedText = decrypt(ciphertext, "privatekey2.txt");
Files.write(Paths.get("result.txt"), decryptedText.getBytes());
System.out.println("Decrypted text: " + decryptedText);

实验结果:

​ 程序不能正常进行。

原因分析:标准RSA加密流程是用公钥加密,私钥解密。但是公钥1加密的数据必须用对应的私钥1解密,而私钥2是另一对密钥对中的私钥,不能解密由公钥1加密的数据。

采用私钥1进行加密,而后采用私钥2进行解密

1
2
3
4
5
6
7
String ciphertext = encrypt(plaintext, "privatekey1.txt");
Files.write(Paths.get("ciphertext.txt"), ciphertext.getBytes());

// 解密密文
String decryptedText = decrypt(ciphertext, "privatekey2.txt");
Files.write(Paths.get("result.txt"), decryptedText.getBytes());
System.out.println("Decrypted text: " + decryptedText);

实验结果:

​ 程序不能正常进行。

原因分析:私钥用于加密主要用于生成数字签名,表明数据来自私钥所有者。只有对应的公钥可以解密这个签名,以验证数据的完整性和来源。使用私钥1加密的数据必须使用对应的公钥1解密,私钥2无法解密因为它们不是一对。

4.总结与展望

通过本次实验,我们深入理解了非对称加密算法的基本原理,熟悉了RSA和椭圆曲线公钥密码算法的工作机制及其优缺点,进一步增强了对公钥、私钥、电子签名和数字证书等核心概念的理解。实验中,我们不仅掌握了公钥密码算法的理论知识,还提升了实际编程与库调用的能力。未来,我们将探索更加安全高效的加密算法,如量子密码算法,同时优化现有算法的易用性与性能,以应对不断演进的安全挑战。

参考文献

[1] WDiffie,M Hell man.New Directions inCryptography[J].IEEE Trans on Information Theory,1976

[2]郭东栋,杜文博,周浩.公钥密码体制基本原理介绍及研究方向探索.信息科技,2020

[3] 卢开澄.计算机密码学[M].北京:清华大学出版社,2003.

[4] 陈传波,祝中涛.RSA算法应用及实现细节[J].计算机工程与科学,2006

[5]徐志旭.信息科技.数字证书技术在车联网安全通信中的应用.信息科技,2024

[6]方应李.基于ECC和AES图像加密算法的研究.南京邮电大学,2024