PGP (Pretty Good Privacy)

PGP是一个完整的电子邮件安全软件包,包括加密、鉴别、电子签名和压缩等技术。
虽然 PGP 已被广泛使用,但 PGP 并不是因特网的正式标准。

一、加密过程。

image-20240407090304770

发件人

md5用于实现消息完整性验证。
H=EAsk(MD5(P))H=E_{A_{sk}}({MD5(P)})
RSA私钥用于签名。
P1=PHP_{1}=P||H
KMK_{M}是收件人不知道的。在使用IDEA加密后用收件人的公钥加密共享密钥KMK_M,串在后面,当收件人收到后,用私钥解开Km,就可以解开IDEA。
发送:
[{(PEAsk(MD5(P))).Z}KMEBpk(KM)]base64[\{(P||E_{A_{sk}}(MD5(P))).Z\}_{K_{M}}||E_{B_{pk}}(K_{M})]_{base64}
发送(带时间戳):
[{(PEAsk(MD5(P)T)).Z}KMEBpk(KM)]base64[\{(P||E_{A_{sk}}(MD5(P)||T)).Z\}_{K_{M}}||E_{B_{pk}}(K_{M})]_{base64}

收件人

base64还原。
获得{(PEAsk(MD5(P))).Z}KMEBpk(KM)\{(P||E_{A_{sk}}(MD5(P))).Z\}_{K_{M}}||E_{B_{pk}}(K_{M})
用私钥解密获取KMK_{M}
DBsk(EBpk(KM)))=KMD_{B_{sk}}(E_{B_{pk}}(K_{M})))=K_{M}
KMK_M解开前面的消息。
DKM((PEAsk(MD5(P))).Z)KM)=(PEAsk(MD5(P))).ZD_{K_{M}}((P||E_{A_{sk}}(MD5(P))).Z)_{K_{M}})=(P||E_{A_{sk}}(MD5(P))).Z
解压缩。
PEAsk(MD5(P))P||E_{A_{sk}}(MD5(P))
验证A的签名。
EApk(EAsk(MD5(P)))=MD5(P)E_{A_{pk}}(E_{A_{sk}}(MD5(P)))=MD5(P)
验证完整性。
MD5(P)==MD5(P)MD5(P')==MD5(P)
信任并接收P。

设计思路

MD5用于做完整性验证。
签名常常签在消息摘要上,因为摘要比较短,节约加密开销。
压缩。需要做无损压缩,使收件人可以还原消息。

应用场景

电子邮件传输、远程办公:家里网络不稳定–>offline的应用场景。

二、IDEA加密算法

传承于DES,分组加密,密钥128位,明文密文64位。
密钥更长,去掉了DES的S盒,构造了白盒构件。

参考:密码学系列之:IDEA - 知乎 (zhihu.com)

IDEA加密块长度是64bits,密钥长度是128bits,是由八轮变换和半轮输出转换组合而成的。加密和解密的过程是类似的。我们看下IDEA的基本流程图:

img

上面图中, 蓝色圆圈是XOR异或操作,绿色框是加法模数216。红色的点是乘模216 + 1,如果输入都是0(0x0000)那么将会被转换为216,如果输入是216,那么会被转换为0(0x0000)。

八轮操作之后,就是下面的半轮输出转换了,输出转换如下所示(中间两个值的交换抵消了最后一轮结束时的交换,因此没有净交换):

img

三、实验设计

在明文P后拼接了时间戳,然后进行签名。其余流程不变。

1.字段划分

在时间戳前添加字段名,便于分离出时间戳。
进行签名的数据:fileContent + “timestamp=” + timestampStr
进行IDEA加密的数据:fileContent + “timestamp=” + timestampStr + signature;
拼接后的数据:string combinedData = “data=” + ideaEncryptedData + “key=” + encryptedIDEAKey;
通过str.find()函数来划分字段。此处没有考虑邮件数据中出现与字段名相同字符串的情况。

2.原始文件

msg_file是使用命令行生成的一个.dat文件,大小为1G,内容不详,仅用于测试。

3.结果验证

Bob成功验证签名时输出Verified signature on message,并保存解密后的文件为received_file。
Bob可以成功还原Alice发送的文件。在Linux上测试时可以使用diff命令查看msg_file和received_file,输入diff命令后无输出。在Windows上测试时可以在命令提示符窗口中使用fc命令查看msg_file和received_file,输出无差异。
Alice在开始加密前输出时间戳,Bob解密后应该提取出相同的时间戳。

4.密钥管理

假设Alice和Bob在本地存有自己的私钥和双方的公钥。本实验中没有涉及RSA密钥分发。
在使用IDEA加密时,初始化向量iv采用硬编码的形式写在双方的代码里,我们认为Alice和Bob已经协商过或通过某种公开渠道知晓iv。

四、代码实现

使用C++和Crypto++库实现。

生成密钥:

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
#include "idea.h"
#include "gzip.h"
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
using namespace CryptoPP;
using namespace std;

int main(int argc, char* argv[])
{
// Pseudo Random Number Generator
AutoSeededRandomPool prng;

///////////////////////////////////////
// Generate Parameters
InvertibleRSAFunction params_A;
params_A.GenerateRandomWithKeySize(prng, 2048);

///////////////////////////////////////
// Create Keys
RSA::PrivateKey privateKey_A(params_A);
RSA::PublicKey publicKey_A(params_A);

// Save private key to file
FileSink privateKeyFile("private_A.key");
privateKey_A.Save(privateKeyFile);

// Save public key to file
FileSink publicKeyFile("public_A.key");
publicKey_A.Save(publicKeyFile);

// Pseudo Random Number Generator
AutoSeededRandomPool prng2;

///////////////////////////////////////
// Generate Parameters
InvertibleRSAFunction params_B;
params_B.GenerateRandomWithKeySize(prng2, 2048);

///////////////////////////////////////
// Create Keys
RSA::PrivateKey privateKey_B(params_B);
RSA::PublicKey publicKey_B(params_B);

// Save private key to file
FileSink privateKeyFile_B("private_B.key");
privateKey_B.Save(privateKeyFile_B);

// Save public key to file
FileSink publicKeyFile_B("public_B.key");
publicKey_B.Save(publicKeyFile_B);

return 0;

}

加密

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
140
141
142
143
144
145
146
147
148
149
150
151
#include "cryptlib.h"
#include "rijndael.h"
#include "modes.h"
#include "files.h"
#include "osrng.h"
#include "hex.h"
#include "rsa.h"
#include "pssr.h"
#include "md5.h"
#include "base64.h"
#include "zlib.h"
#include "idea.h"
#include "gzip.h"
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
using namespace CryptoPP;
using namespace std;

int main(int argc, char* argv[])
{

///////////////////////////////////////
// Load keys
RSA::PrivateKey privateKey_A;
FileSource privateKeyFile_A("./private_A.key", true);
privateKey_A.Load(privateKeyFile_A);

// Load public key from file
RSA::PublicKey publicKey_A;
FileSource publicKeyFile_A("./public_A.key", true);
publicKey_A.Load(publicKeyFile_A);

// Load public key from file
RSA::PublicKey publicKey_B;
FileSource publicKeyFile_B("./public_B.key", true);
publicKey_B.Load(publicKeyFile_B);

// Pseudo Random Number Generator
AutoSeededRandomPool prng;

//时间戳
string timestampStr = to_string(time(nullptr));
time_t timestamp = std::stoi(timestampStr); // 将字符串转换为时间戳
struct tm *timeinfo = std::localtime(&timestamp); // 将时间戳转换为时间信息结构体
std::cout << "Time stamp: " << 1900 + timeinfo->tm_year << "/";
std::cout << 1 + timeinfo->tm_mon << "/";
std::cout << timeinfo->tm_mday << " ";
std::cout << timeinfo->tm_hour << ":";
std::cout << timeinfo->tm_min << ":";
std::cout << timeinfo->tm_sec << std::endl;

// 写入时间戳到文件末尾
string filename = "msg_file.dat";
string fileContent;
FileSource file(filename.c_str(), true, new StringSink(fileContent));
ofstream fout;
fout.open("msg_file_timestamp.dat");
fout << fileContent << "timestamp=" << timestampStr;
fout.close();

//生成签名
string signature;
RSASS<PSS, MD5>::Signer signer(privateKey_A);
FileSource("msg_file_timestamp.dat", true,
new SignerFilter(prng, signer, new StringSink(signature)));
ofstream out1;
out1.open("msg_file_signatured.dat");
out1 << fileContent << "timestamp=" << timestampStr;
cout<<"The size of timestampStr is "<<timestampStr.size()<<endl;
out1 << signature;
out1.close();

std::cout << "Message file id signatured and added timestamp successfully." << endl;

// compress dat to gz
// 输入文件
FileSource fs("msg_file_signatured.dat", false);

// 创建一个Gzip压缩过滤器
Gzip zipper(new FileSink("msg_file_zipped.gz"));

// 将输入文件通过Gzip过滤器进行压缩
fs.Attach(new Redirector(zipper));
fs.PumpAll();

//////////////////////////////////////////////////////
//IDEA,K
//initial iv
byte iv[IDEA::BLOCKSIZE] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
// 加密密钥
byte key[IDEA::DEFAULT_KEYLENGTH];
prng.GenerateBlock(key, IDEA::DEFAULT_KEYLENGTH);

// 输入文件
FileSource fs2("msg_file_zipped.gz", false);

// 创建一个IDEA加密过滤器
IDEA::Encryption ideaEncryption(key, IDEA::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Encryption cbcEncryption(ideaEncryption, iv);

StreamTransformationFilter stfEncryptor(cbcEncryption, new FileSink("msg_file_zipped.gz.idea"));
fs2.Attach(new Redirector(stfEncryptor));
fs2.PumpAll();
std::cout << "IDEA encryption succeeded." << endl;

////////////////////////////////////////////////
//RSA ke encrypt
// The RSA encryption object
RSAES_OAEP_SHA_Encryptor e(publicKey_B);

// Encrypted IDEA key
string encryptedIDEAKey;
StringSource ss1(string((char*)key, sizeof(key)), true, new PK_EncryptorFilter(prng, e, new StringSink(encryptedIDEAKey)) // PK_EncryptorFilter
); // StringSource

//cout << encryptedIDEAKey.size() << endl;
std::cout << "IDEA key is encrypted successfully." << endl;

//////////////////////////////////////////////////////
//Combine
// 加载IDEA加密的数据

string ideaEncryptedData;
FileSource fs3("msg_file_zipped.gz.idea", true, new StringSink(ideaEncryptedData));

// 拼接数据和密钥
string combinedData = "data=" + ideaEncryptedData + "key=" + encryptedIDEAKey;
//cout << "size before base64 encoding is "<<combinedData.size()<<endl;
//cout << "size of ideaEncryptedData is "<<ideaEncryptedData.size()<<endl;

///////////////////////////////////////////////////////////
//BASE 64
string base64Data;
StringSource ss2(combinedData, true,
new Base64Encoder(
new StringSink(base64Data)
) // Base64Encoder
); // StringSource

//cout<<"size of base64Data is "<<base64Data.size()<<endl;
// 将Base64编码的数据写入到文件中
ofstream out2("encrypted_base64");
out2 << base64Data;
out2.close();

std::cout << "Data and key are combined and encoded to Base64 successfully." << endl;
return 0;

}

解密

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
#include "cryptlib.h"
#include "rijndael.h"
#include "modes.h"
#include "files.h"
#include "osrng.h"
#include "hex.h"
#include "rsa.h"
#include "pssr.h"
#include "md5.h"
#include "base64.h"
#include "zlib.h"
#include "idea.h"
#include "gzip.h"
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
using namespace CryptoPP;
using namespace std;
int main(int argc, char* argv[])
{

///////////////////////////////////////
// Load keys
RSA::PrivateKey privateKey_B;
FileSource privateKeyFile_B("./private_B.key", true);
privateKey_B.Load(privateKeyFile_B);

// Load public key from file
RSA::PublicKey publicKey_A;
FileSource publicKeyFile_A("./public_A.key", true);
publicKey_A.Load(publicKeyFile_A);

// Load public key from file
RSA::PublicKey publicKey_B;
FileSource publicKeyFile_B("./public_B.key", true);
publicKey_B.Load(publicKeyFile_B);

// Pseudo Random Number Generator
AutoSeededRandomPool prng;

// 读取 Base64 编码的数据
string base64EncodedData, decodedData;
ifstream in("./encrypted_base64");
base64EncodedData = std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
in.close();
//cout<<"size of base64Data is "<<base64EncodedData.size()<<endl;
// Base64 解码
StringSource(base64EncodedData, true, new Base64Decoder(new StringSink(decodedData)));
cout << "Base64 decoded." << endl;
//cout<<"size of decodedData is "<<decodedData.size()<<endl;
// 从数据中分离出加密的 IDEA 密钥和加密数据
size_t pos = decodedData.find("key=");
string encryptedIDEAData = decodedData.substr(5, pos - 5); // 从 "data=" 开始到 "key=" 之前
string encryptedIDEAKey = decodedData.substr(pos + 4); // 从 "key=" 后开始到结束
cout << "IDEA key and IDEA encrypted data seperated." << endl;
//cout << "size of ideaEncryptedData is "<<encryptedIDEAData.size()<<endl;

RSAES_OAEP_SHA_Decryptor decryptor(privateKey_B);

string decryptedIDEAKey;
StringSource(encryptedIDEAKey, true,
new PK_DecryptorFilter(prng, decryptor, new StringSink(decryptedIDEAKey)));

// 解密 IDEA 数据
SecByteBlock key((const byte*)decryptedIDEAKey.data(), decryptedIDEAKey.size());
byte iv[IDEA::BLOCKSIZE] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; // 初始化向量应与加密时相同

string decryptedData;
IDEA::Decryption ideaDecryption(key, IDEA::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Decryption cbcDecryption(ideaDecryption, iv);

StringSource(encryptedIDEAData, true,
new StreamTransformationFilter(cbcDecryption, new StringSink(decryptedData)));

//cout << "Decrypted Data: " << decryptedData << endl;
cout << "IDEA decrypted." << endl;

string unzipped_content;
Gunzip unzipper(new StringSink(unzipped_content));

unzipper.Put((unsigned char*)decryptedData.data(), decryptedData.size());
unzipper.MessageEnd();
cout << "Unzipped." << endl;

int pos_tm = unzipped_content.find("timestamp=");

string received_time_stamp = unzipped_content.substr(pos_tm + 10, 10);

// Verifier signature
RSASS<PSS, MD5>::Verifier verifier(publicKey_A);

StringSource ss2(unzipped_content, true,
new SignatureVerificationFilter(
verifier, NULL,
SignatureVerificationFilter::THROW_EXCEPTION
) // SignatureVerificationFilter
); // StringSource

cout << "Verified signature on message" << endl;

ofstream out("received_file");
if (out.is_open()) {
out << unzipped_content.substr(0, pos_tm); // 将得到的原字符串写入文件
out.close(); // 关闭文件
cout << "Received file is saved." << endl;
}
else {
std::cerr << "error when opening the file." << endl;
}

time_t timestamp_recover = std::stoi(received_time_stamp); // 将字符串转换为时间戳
struct tm *timeinfo = std::localtime(&timestamp_recover); // 将时间戳转换为时间信息结构体
// 打印时间信息
cout << "Time stamp: " << 1900 + timeinfo->tm_year << "/";
cout << 1 + timeinfo->tm_mon << "/";
cout << timeinfo->tm_mday << " ";
cout << timeinfo->tm_hour << ":";
cout << timeinfo->tm_min << ":";
cout << timeinfo->tm_sec << std::endl;
return 0;

}

发送

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
#include <iostream>
#include <fstream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8888

int main() {
int sockfd;
struct sockaddr_in serverAddr;

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}

serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

if(connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Error connecting to server" << std::endl;
return 1;
}

std::string filename = "./encrypted_base64";
std::ifstream file(filename, std::ios::binary);
if(!file) {
std::cerr << "Error opening file" << std::endl;
return 1;
}

char buffer[1024];
while(file.good()) {
file.read(buffer, sizeof(buffer));
send(sockfd, buffer, file.gcount(), 0);
}

file.close();
close(sockfd);

std::cout << "File sent successfully" << std::endl;

return 0;

}

接收

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
#include <iostream>
#include <fstream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define PORT 8888

int main() {
int sockfd, newsockfd;
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientLen;


sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}

serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = INADDR_ANY;

if(bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Error binding socket" << std::endl;
return 1;
}

listen(sockfd, 5);

clientLen = sizeof(clientAddr);
newsockfd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientLen);
if(newsockfd < 0) {
std::cerr << "Error accepting connection" << std::endl;
return 1;
}

std::string filename = "encrypted_base64";
std::ofstream file(filename, std::ios::binary);
if(!file) {
std::cerr << "Error creating file" << std::endl;
return 1;
}

char buffer[1024];
int bytesRead;
while((bytesRead = recv(newsockfd, buffer, sizeof(buffer), 0)) > 0) {
file.write(buffer, bytesRead);
}

file.close();
close(newsockfd);
close(sockfd);

std::cout << "File received successfully" << std::endl;

return 0;

}

五、运行结果

加密
image-20240511152911527

解密
image-20240511152918350
image-20240511152925902