基於cryptopp -- A Password Manager密碼管理系統
阿新 • • 發佈:2019-01-10
系統設計安全目標
- 私密性(confidentiality):訊息內容不讓其他人看到訊息:只有該看的人才能看
- 完整性(integrity):訊息不被其他人篡改或者篡改之後可以被發現:看到的訊息是正確的
- 可用性(availability):加密後,解密仍然能夠讀到資訊:想看隨時看
基本功能
登入介面
提示使用者輸入使用者名稱和master password
檢查文件是否存在
- 不存在建立一個新的文件,並以輸入的master password作為這個使用者的master password
存在則檢查文件中的master password(需要先進行解密)和使用者輸入的是否一致,
- 如果不一致則報錯,保留在登入介面
- 如果一致則進行完整性檢查:將每一行的網站域名抽出來通過HMAC做雜湊,將雜湊以後的值和儲存的對應雜湊值最對比,如果有某一項不一致則報錯,保留在登陸介面;如果一致就進入使用者功能介面
使用者功能介面
功能1:實現對於網站域名 + 雜湊值 + 加密以後的密碼的增加
- 提示使用者輸入網站域名和密碼
- 檢查文件中是否有相同的域名,如果有則報錯;沒有則將網站域名 + 通過HMAC算出的雜湊值 + 加密以後的密碼增加到檔案尾部,並輸出成功資訊
功能2:實現對於網站域名 + 雜湊值 + 加密以後的密碼的刪除
- 提示使用者輸入網站域名
- 檢查文件中是否有相同的域名,如果沒有則報錯;有則將對應的記錄從該文件中刪除
功能3:實現對於輸入網站域名後對於密碼的查詢
- 提示使用者輸入網站域名
- 檢查文件中是否有相同的域名,如果沒有則報錯;有則將對應加密後的密碼解密後輸出出來
功能4:實現對於輸入網站域名後對於密碼的修改
- 提示使用者輸入網站域名和密碼
- 檢查文件中是否有相同的域名,如果沒有則報錯;有則將輸入的密碼進行加密後替換掉文件中的對應項
功能5:返回登入介面
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <sstream>
//AES
#include "cryptopp563/filters.h" // StringSink, StringSource, StreamTransformationFilter
#include "cryptopp563/aes.h" // AES
#include "cryptopp563/modes.h" // CBC_Mode_*
//PBKDF2
#include "cryptopp563/base64.h" // Base64Decoder
#include "cryptopp563/sha.h" // SHA1
#include "cryptopp563/pwdbased.h" // PKCS5_PBKDF2_HMAC
#include "cryptopp563/hex.h" // HexEncoder
//HMAC
#include "cryptopp563/hmac.h" // HMAC<T>
using namespace std;
using namespace CryptoPP;
#define DEFAULT_STRING_SIZE (32)
//十進位制與十六進位制之間相互轉換
string decToHex(byte num);//十進位制轉十六進位制
byte hexToDec(string num);//十六進位制轉十進位制
//CBC加密
string CBC_encrypt(string str);
string CBC_encrypt(byte* key, string source);
//CBC解密
string CBC_decrypt(string source);
string CBC_decrypt(byte* key, string source);
bool masterKey(string username, string password); //比對輸入密碼與txt密碼是否一致
void integrity(string username); //檔案內容是否具備完整性
//與key相關的雜湊函式對domain進行加密
string digest(string key, string domain);
string deriveKey(string domain, string masterPassword);
void addNewKVS(string username, string password); //增加KVS
void deleteKVS(string username, string password); //刪除KVS
void searchPassword(string username, string password); //查詢對應域名下的密碼
void changePassword(string username, string password); //修改對應域名下的密碼
int main (int argc, char const *argv[]){
string username; //使用者名稱
string masterPassword; //密碼
string filename; //使用者名稱補充檔名
string masterPasswordInTxt;
/* 登入介面 */
while(1){
//輸入使用者名稱和密碼
cout << "\nPlease input the username and password." << endl;
cin >> username >> masterPassword;
//依據使用者名稱讀取檔案
filename = username + ".txt";
char *masterPasswordChar = (char*)masterPassword.data();
char *cname = (char*)filename.data();
ifstream in(cname);
if(in == NULL){
//使用者不存在 新建以username命名的txt檔案,儲存masterPassword
cout << "Hello, new friend." <<endl;
in.close();
string passwordAfterEncrypt = CBC_encrypt(masterPasswordChar);
ofstream out(cname);
out << passwordAfterEncrypt <<endl;
out.close();
break;//新建檔案完畢 跳出迴圈
}
else{
//使用者已存在,對密碼進行驗證
getline(in, masterPasswordInTxt);
//cout << masterPasswordInTxt + " !"<< endl;
in.close();
if(masterKey(username, masterPassword)) {
//密碼正確
cout << "Hello, " << username << "."<< endl;
integrity(username);
break;
}
else {
//密碼錯誤
cout << "The password is wrong!" << endl;
}
}
}
/* 功能選擇介面 */
int kind; // 功能型別
cout << "--------------------------------------------------------------" <<endl;
cout << "\nWelcome to Password Manager!\n" <<endl;
while(1){
cout << "--------------------------------------------------------------" <<endl;
cout << "1.Add a domain / password;" <<endl;
cout << "--------------------------------------------------------------" <<endl;
cout << "2.Remove a domain / password;" <<endl;
cout << "--------------------------------------------------------------" <<endl;
cout << "3.Check the corresponding password according to the domain;" <<endl;
cout << "--------------------------------------------------------------" <<endl;
cout << "4.Modify the corresponding password according to the domain;" <<endl;
cout << "--------------------------------------------------------------" <<endl;
cout << "5.Exit." <<endl;
cout << "--------------------------------------------------------------" <<endl;
cout << "Please input the number between 1 and 5." << endl;
cin >> kind;
while(kind < 1 || kind > 5){
cout << "Please input the right number.";
cin >> kind;
}
if(kind == 1){
addNewKVS(username, masterPassword);
}
else if(kind == 2){
deleteKVS(username, masterPassword);
}
else if(kind == 3){
searchPassword(username, masterPassword);
}
else if(kind == 4){
changePassword(username, masterPassword);
}
else{
break;
}
}
system("pause");
return 0;
}
string CBC_encrypt(string str) {
// 初始化金鑰和初始向量
byte key[CryptoPP::AES::DEFAULT_KEYLENGTH];
byte iv[CryptoPP::AES::BLOCKSIZE];
memset(key, 0x00, CryptoPP::AES::DEFAULT_KEYLENGTH);
memset(iv, 0x00, CryptoPP::AES::BLOCKSIZE);
//進行CBC加密
string ciphertext;
CryptoPP::AES::Encryption aesEncryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv);
StreamTransformationFilter stfEncryptor(cbcEncryption, new StringSink(ciphertext));
stfEncryptor.Put(reinterpret_cast<const unsigned char*>(str.c_str()), str.length() + 1);
stfEncryptor.MessageEnd();
// 獲得加密結果
string result;
for(int i = 0; i < ciphertext.size(); i++)
result.append(decToHex(0xFF & static_cast<byte>(ciphertext[i])));
return result;
}
void integrity(string username) {
string masterPassword, domain, hash, password;
//開啟檔案
string filename;
filename = username + ".txt";
char *cname = (char*)filename.data();
ifstream in(cname);
in >> masterPassword;
//若密碼長度不為default_string_size的倍數則密碼不完整
if(masterPassword.size() % DEFAULT_STRING_SIZE != 0) {
cout << "Fatal error: File is modified improperly" << endl;
exit(0);
}
//迴圈讀取所有記錄
while(in >> domain >> hash >> password) {
if(password.size() % DEFAULT_STRING_SIZE != 0) {
//若密碼長度不為default_string_size的倍數則密碼不完整
cout << "Fatal error: File is modified improperly" << endl;
exit(0);
}
else if(strcmp(hash.c_str(), digest(deriveKey(domain, CBC_decrypt(masterPassword)), domain).c_str()) != 0) {
//若域名雜湊後與儲存的對應值不一樣則檔案不完整
cout << "Fatal error: File is modified improperly" << endl;
exit(0);
}
}
in.close();
}
string decToHex(byte num) {
char result[3] = {'0', '0'};
int i = 2;
while(num > 0 && i-- > 0) {
result[i] += num % 16;
if(result[i] > '9')
result[i] += ('A' - 10 - '0');
num /= 16;
}
return result;
}
byte hexToDec(string num) {
byte result = 0;
result += 16 * (num[0] < 'A' ? num[0] - '0' : num[0] - 'A' + 10);
result += num[1] < 'A' ? num[1] - '0' : num[1] - 'A' + 10;
return result;
}
bool masterKey(string username, string password) {
//開啟檔案
string filename;
filename = username + ".txt";
char *cname = (char*)filename.data();
ifstream in(cname);
//讀取密碼
string passwordInTxt;
in >> passwordInTxt;
in.close();
//對讀取到的密碼進行解密
string decryptMasterKey = CBC_decrypt(passwordInTxt);
//比對兩個密碼是否一致
if(strcmp(decryptMasterKey.c_str(), password.c_str()) == 0)
return true;
else
return false;
}
string CBC_encrypt(byte* key, string source) {
// IV setup
byte iv[AES::BLOCKSIZE];
memset(iv, 0x00, AES::BLOCKSIZE);
// Create Cipher Text
string ciphertext;
AES::Encryption aesEncryption(key, AES::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv);
StreamTransformationFilter stfEncryptor(cbcEncryption, new StringSink(ciphertext));
stfEncryptor.Put(reinterpret_cast<const unsigned char*>(source.c_str()), source.length() + 1);
stfEncryptor.MessageEnd();
// get result
string result;
for(int i = 0; i < ciphertext.size(); i++)
result.append(decToHex(0xFF & static_cast<byte>(ciphertext[i])));
return result;
}
string CBC_decrypt(string source) {
// check modified
if(source.size() % DEFAULT_STRING_SIZE != 0) {
cout << "Fatal error: File is modified improperly" << endl;
exit(0);
}
// get cipher text
string cipher;
for(int i = 0; i < source.length(); i += 2)
cipher.append(1, hexToDec(source.substr(i, 2)));
// Key and IV setup
byte key[AES::DEFAULT_KEYLENGTH], iv[AES::BLOCKSIZE];
memset(key, 0x00, AES::DEFAULT_KEYLENGTH);
memset(iv, 0x00, AES::BLOCKSIZE);
// decrypt
string decryptedtext;
AES::Decryption aesDecryption(key, AES::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv);
StreamTransformationFilter stfDecryptor(cbcDecryption, new StringSink(decryptedtext));
stfDecryptor.Put(reinterpret_cast<const unsigned char*>(cipher.c_str()), cipher.size());
stfDecryptor.MessageEnd();
return decryptedtext;
}
string CBC_decrypt(byte* key, string source) {
// check modified
if(source.size() % DEFAULT_STRING_SIZE != 0) {
cout << "Fatal error: File is modified improperly" << endl;
exit(0);
}
// get cipher text
string cipher;
for(int i = 0; i < source.length(); i += 2)
cipher.append(1, hexToDec(source.substr(i, 2)));
// Key and IV setup
byte iv[AES::BLOCKSIZE];
memset(iv, 0x00, AES::BLOCKSIZE);
// decrypt
string decryptedtext;
AES::Decryption aesDecryption(key, AES::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv);
StreamTransformationFilter stfDecryptor(cbcDecryption, new StringSink(decryptedtext));
stfDecryptor.Put(reinterpret_cast<const unsigned char*>(cipher.c_str()), cipher.size());
stfDecryptor.MessageEnd();
return decryptedtext;
}
string digest(string key, string domain) {
byte result[AES::DEFAULT_KEYLENGTH];
HMAC<SHA >((byte *)key.c_str(), key.size()).CalculateDigest(result, (byte *)domain.c_str(), domain.size());
string value;
for(int i = 0; i < AES::DEFAULT_KEYLENGTH; i++)
value.append(decToHex(result[i]));
return value;
}
string deriveKey(string domain, string masterPassword) {
byte derived[AES::DEFAULT_KEYLENGTH];
PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf2;
pbkdf2.DeriveKey(derived, sizeof(derived), 0, (byte *)masterPassword.c_str(), masterPassword.size(), (byte *)domain.c_str(), domain.size(), 1);
string result;
HexEncoder encoder(new StringSink(result));
encoder.Put(derived, sizeof(derived));
encoder.MessageEnd();
return result;
}
void addNewKVS(string username, string password) {
//輸出提示資訊,提示輸入需要新增的域名和密碼
string domainInput, passwordInput;
cout << "Please input the domain you want to add: ";
cin >> domainInput;
cout << "Please input the new password of the domain: ";
cin >> passwordInput;
string key = deriveKey(domainInput, password);
ofstream file;
file.open((username + ".txt").c_str(), ios::out|ios::app);
//對應輸出域名,雜湊後的域名,加密後的密碼到檔案中
file << domainInput << " " << digest(key, domainInput) << " " << CBC_encrypt((byte *)key.c_str(), passwordInput) << endl;
file.close();
cout << "Success!" << endl;
}
void deleteKVS(string username, string password) {
//提示輸入需要刪除的域名
string domainInput;
cout << "Please input the domain you want to delete: ";
cin >> domainInput;
bool find = false;
string masterPassword;
vector<string> domainVector, hashVector, passwordVector; //臨時儲存記錄
string domain, hash, pw;
//開啟檔案,逐條記錄比對域名是否與輸入的域名一致
fstream file;
file.open((username + ".txt").c_str(), ios::in);
file >> masterPassword;
while(file >> domain >> hash >> pw) {
if(strcmp(domain.c_str(), domainInput.c_str()) == 0)
//域名是否與輸入的域名一致,不放入儲存向量中
find = true;
else {
domainVector.push_back(domain);
hashVector.push_back(hash);
passwordVector.push_back(pw);
}
}
file.close();
file.open((username + ".txt").c_str(), ios::out|ios::trunc);
file << masterPassword << endl;
//將記錄從臨時儲存的向量中移回檔案中
for(int i = 0; i < domainVector.size(); i++)
file << domainVector[i] << " " << hashVector[i] << " " << passwordVector[i] << endl;
file.close();
if(find)
cout << "Success!" << endl;
else
cout << "Can not find the record of the domain" << endl;
}
void searchPassword(string username, string password) {
//提示輸入需要查詢的域名
string domainInput;
cout << "Please input the domain you want to search: ";
cin >> domainInput;
bool find = false;
string masterPassword;
string domain, hash, pw;
ifstream file;
file.open((username + ".txt").c_str());
file >> masterPassword;
//逐條記錄比對,若域名一致則找到,將密碼解密出來並輸出
while(file >> domain >> hash >> pw) {
if(strcmp(domain.c_str(), domainInput.c_str()) == 0) {
find = true;
string key = deriveKey(domainInput, password);
cout << "The password of domain is: " << CBC_decrypt((byte *)key.c_str(), pw) << endl;
break;
}
}
if(!find)
cout << "Can not find the record of the domain" << endl;
}
void changePassword(string username, string password) {
//提示輸入需要修改的域名和密碼
string domainInput, passwordInput;
cout << "Please input the domain to be changed: ";
cin >> domainInput;
cout << "Please input your new password: ";
cin >> passwordInput;
bool find = false;
string masterPassword;
//轉存向量
vector<string> domainVector, hashVector, passwordVector;
string domain, hash, pw;
//開啟對應檔案
fstream file;
file.open((username + ".txt").c_str(), ios::in);
file >> masterPassword;
//逐條進行比對,若發現域名一致的記錄則將新的密碼加密後存入向量
while(file >> domain >> hash >> pw) {
if(strcmp(domain.c_str(), domainInput.c_str()) == 0) {
domainVector.push_back(domain);
hashVector.push_back(hash);
string key = deriveKey(domainInput, password);
passwordVector.push_back(CBC_encrypt((byte *)key.c_str(), passwordInput));
find = true;
}
else {
domainVector.push_back(domain);
hashVector.push_back(hash);
passwordVector.push_back(pw);
}
}
file.close();
file.open((username + ".txt").c_str(), ios::out|ios::trunc);
file << masterPassword << endl;
//將在向量內臨時儲存的內容移回檔案
for(int i = 0; i < domainVector.size(); i++)
file << domainVector[i] << " " << hashVector[i] << " " << passwordVector[i] << endl;
file.close();
if(find)
cout << "Success!" << endl;
else
cout << "Can not find the record of the domain" << endl;
}