PGP (Pretty Good Privacy)
PGP是一个完整的电子邮件安全软件包,包括加密、鉴别、电子签名和压缩等技术。
虽然 PGP 已被广泛使用,但 PGP 并不是因特网的正式标准。
一、加密过程。
发件人
md5用于实现消息完整性验证。
H=EAsk(MD5(P))
RSA私钥用于签名。
P1=P∣∣H
KM是收件人不知道的。在使用IDEA加密后用收件人的公钥加密共享密钥KM,串在后面,当收件人收到后,用私钥解开Km,就可以解开IDEA。
发送:
[{(P∣∣EAsk(MD5(P))).Z}KM∣∣EBpk(KM)]base64
发送(带时间戳):
[{(P∣∣EAsk(MD5(P)∣∣T)).Z}KM∣∣EBpk(KM)]base64
收件人
base64还原。
获得{(P∣∣EAsk(MD5(P))).Z}KM∣∣EBpk(KM)
用私钥解密获取KM。
DBsk(EBpk(KM)))=KM
用KM解开前面的消息。
DKM((P∣∣EAsk(MD5(P))).Z)KM)=(P∣∣EAsk(MD5(P))).Z
解压缩。
P∣∣EAsk(MD5(P))
验证A的签名。
EApk(EAsk(MD5(P)))=MD5(P)
验证完整性。
MD5(P′)==MD5(P)
信任并接收P。
设计思路
MD5用于做完整性验证。
签名常常签在消息摘要上,因为摘要比较短,节约加密开销。
压缩。需要做无损压缩,使收件人可以还原消息。
应用场景
电子邮件传输、远程办公:家里网络不稳定–>offline的应用场景。
二、IDEA加密算法
传承于DES,分组加密,密钥128位,明文密文64位。
密钥更长,去掉了DES的S盒,构造了白盒构件。
参考:密码学系列之:IDEA - 知乎 (zhihu.com)
IDEA加密块长度是64bits,密钥长度是128bits,是由八轮变换和半轮输出转换组合而成的。加密和解密的过程是类似的。我们看下IDEA的基本流程图:
上面图中, 蓝色圆圈是XOR异或操作,绿色框是加法模数216。红色的点是乘模216 + 1,如果输入都是0(0x0000)那么将会被转换为216,如果输入是216,那么会被转换为0(0x0000)。
八轮操作之后,就是下面的半轮输出转换了,输出转换如下所示(中间两个值的交换抵消了最后一轮结束时的交换,因此没有净交换):
三、实验设计
在明文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[]) { AutoSeededRandomPool prng;
InvertibleRSAFunction params_A; params_A.GenerateRandomWithKeySize(prng, 2048); RSA::PrivateKey privateKey_A(params_A); RSA::PublicKey publicKey_A(params_A); FileSink privateKeyFile("private_A.key"); privateKey_A.Save(privateKeyFile); FileSink publicKeyFile("public_A.key"); publicKey_A.Save(publicKeyFile); AutoSeededRandomPool prng2; InvertibleRSAFunction params_B; params_B.GenerateRandomWithKeySize(prng2, 2048); RSA::PrivateKey privateKey_B(params_B); RSA::PublicKey publicKey_B(params_B); FileSink privateKeyFile_B("private_B.key"); privateKey_B.Save(privateKeyFile_B); 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[]) {
RSA::PrivateKey privateKey_A; FileSource privateKeyFile_A("./private_A.key", true); privateKey_A.Load(privateKeyFile_A); RSA::PublicKey publicKey_A; FileSource publicKeyFile_A("./public_A.key", true); publicKey_A.Load(publicKeyFile_A); RSA::PublicKey publicKey_B; FileSource publicKeyFile_B("./public_B.key", true); publicKey_B.Load(publicKeyFile_B); AutoSeededRandomPool prng; string timestampStr = to_string(time(nullptr)); time_t timestamp = std::stoi(timestampStr); struct tm *timeinfo = std::localtime(×tamp); 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; FileSource fs("msg_file_signatured.dat", false); Gzip zipper(new FileSink("msg_file_zipped.gz")); fs.Attach(new Redirector(zipper)); fs.PumpAll(); 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::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; RSAES_OAEP_SHA_Encryptor e(publicKey_B); string encryptedIDEAKey; StringSource ss1(string((char*)key, sizeof(key)), true, new PK_EncryptorFilter(prng, e, new StringSink(encryptedIDEAKey)) ); std::cout << "IDEA key is encrypted successfully." << endl; string ideaEncryptedData; FileSource fs3("msg_file_zipped.gz.idea", true, new StringSink(ideaEncryptedData)); string combinedData = "data=" + ideaEncryptedData + "key=" + encryptedIDEAKey; string base64Data; StringSource ss2(combinedData, true, new Base64Encoder( new StringSink(base64Data) ) ); 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[]) {
RSA::PrivateKey privateKey_B; FileSource privateKeyFile_B("./private_B.key", true); privateKey_B.Load(privateKeyFile_B); RSA::PublicKey publicKey_A; FileSource publicKeyFile_A("./public_A.key", true); publicKey_A.Load(publicKeyFile_A); RSA::PublicKey publicKey_B; FileSource publicKeyFile_B("./public_B.key", true); publicKey_B.Load(publicKeyFile_B); AutoSeededRandomPool prng; string base64EncodedData, decodedData; ifstream in("./encrypted_base64"); base64EncodedData = std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()); in.close(); StringSource(base64EncodedData, true, new Base64Decoder(new StringSink(decodedData))); cout << "Base64 decoded." << endl; size_t pos = decodedData.find("key="); string encryptedIDEAData = decodedData.substr(5, pos - 5); string encryptedIDEAKey = decodedData.substr(pos + 4); cout << "IDEA key and IDEA encrypted data seperated." << endl; RSAES_OAEP_SHA_Decryptor decryptor(privateKey_B); string decryptedIDEAKey; StringSource(encryptedIDEAKey, true, new PK_DecryptorFilter(prng, decryptor, new StringSink(decryptedIDEAKey))); 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 << "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); RSASS<PSS, MD5>::Verifier verifier(publicKey_A); StringSource ss2(unzipped_content, true, new SignatureVerificationFilter( verifier, NULL, SignatureVerificationFilter::THROW_EXCEPTION ) ); 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(×tamp_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;
}
|
五、运行结果
加密
解密