密碼管理器的實現

密碼管理器的實現

軟件依賴

  • gcc編譯環境
  • Cryptopp運行環境

軟件描述

軟件的功能描述:安全

  1. 登錄密碼管理器
  2. 添加域名密碼對到密碼管理器
  3. 移除域名密碼對到密碼管理器
  4. 查看指定域名的密碼
  5. 修改指定域名的密碼
  6. 驗證存儲文件是否被修改
  7. 註冊新用戶

軟件的架構圖:架構

登陸功能:
檢查用戶是否第一次使用該系統。函數

若是是,則根據用戶的密碼做爲master password,經過AES加密後存儲起來(使用的是代碼中固定的initKey和initIv),保存在相應的文件中,如Wsine.dat文件,一個用戶一個數據文件。網站

若是否,加載用戶的數據,對於master password進行AES解密操做(使用的是代碼中固定的initKey和initIv),與登錄密碼相比較,判斷是否登錄成功。編碼

完整性檢查:
當用戶登陸成功的時候,加載用戶保存的域名密鑰對,對於每一個域名計算它的哈希值(使用的是master password擴展後的key),與記錄的哈希值進行比較,若是有任一不匹配,則說明文件被人爲修改過,完整性被破壞。不然爲完整文件。加密

插入鍵值對:
登錄成功的狀況下,查找該域名是否已經存儲。
若是否,對master password經過pbkdf2放縮至16位(使用的pwd是initKey),計算域名的哈希值(使用的是master password擴展後的key),將網站密碼進行AES加密(使用的是master password擴展後的key和代碼中固定的initIv),加密後添加到用戶數據中。
若是是,提示域名密碼對已存在。命令行

刪除鍵值對:
登錄成功的狀況下,查找該域名是否已經存儲。code

若是是,將該域名對移除。orm

若是否,提示域名密碼對不存在。blog

查詢鍵值對:
登錄成功的前提下,查找該域名是否已經存儲。
若是是,對master password經過pbkdf2放縮至16位(使用的pwd是initKey),將網站密碼進行AES解密(使用的是master password擴展後的key和代碼中固定的initIv),解密後返回給用戶。

若是否,提示域名密碼對不存在。

修改鍵值對:
登錄成功的前提下,查找該域名是否已經存儲。

若是是,對master password經過pbkdf2放縮至16位(使用的pwd是initKey),將新的網站密碼進行AES加密(使用的是master password擴展後的key和代碼中固定的initIv),替換掉原來的網站密碼。

若是否,提示域名密碼對不存在。

IO操做:

因爲使用到了文件存儲,所以須要進行IO操做,AES加密後的結果有不可輸出的字符存在,所以所有的保存到文件中的hash值和password值都使用16進制數來保存,加載的時候須要對其進行從新編碼。

代碼實現

該軟件的實現方式遵循UNIX的命令行準則,使用get_opt()接口來進行命令行參數解析,使用assert進行參數檢查,使用try catch進行運行時檢查。

對於實驗中使用到的密碼管理的接口,都封裝到了PasswordManagerHelper類中。

類圖一覽:

重要參數解析:

AES加密函數:

string PasswordManagerHelper::AESCBCEncrypt(const string& plaintext, const byte *key, const byte *iv) {
    string ciphertext;
    try {
        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 byte*>(plaintext.c_str()), plaintext.length() + 1);
        stfEncryptor.MessageEnd();
    } catch (const CryptoPP::Exception& e) {
        cerr << e.what() << endl;
        exit(1);
    }
    return ciphertext;
}

該函數對原文進行加密操做,須要傳入一個key和一個initialization vector,使用的塊加密模式是CBC模式。

AES解密函數:

string PasswordManagerHelper::AESCBCDecrypt(const string& ciphertext, const byte *key, const byte *iv) {
    string plaintext;
    try {
        AES::Decryption aesDecryption(key, AES::DEFAULT_KEYLENGTH);
        CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv);

        StreamTransformationFilter stfDecryptor(cbcDecryption, new StringSink(plaintext));
        stfDecryptor.Put(reinterpret_cast<const byte*>(ciphertext.c_str()), ciphertext.size());
        stfDecryptor.MessageEnd();
    } catch (const CryptoPP::Exception& e) {
        cerr << e.what() << endl;
        exit(1);
    }
    return plaintext;
}

該函數是對密文進行解密操做,須要傳入一個key和一個initialization vector,使用的塊解密模式是CBC。

擴展key函數:

string PasswordManagerHelper::expandKey(const string& key, const string& pwd) {
    string decoderPwd, decoderIv, result;
    
    try {
        Base64Decoder decoder1(new StringSink(decoderPwd));
        decoder1.Put((const byte*)pwd.data(), pwd.size());
        decoder1.MessageEnd();

        Base64Decoder decoder2(new StringSink(decoderIv));
        decoder2.Put((const byte*)key.data(), key.size());
        decoder2.MessageEnd();

        int c = 100;
        byte derived[8];
        PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf2;
        pbkdf2.DeriveKey(derived, sizeof(derived), 0, (byte*)decoderPwd.data(), decoderPwd.size(), 
                            (byte*)decoderIv.data(), decoderIv.size(), c);

        HexEncoder encoder(new StringSink(result));
        encoder.Put(derived, sizeof(derived));
        encoder.MessageEnd();

    } catch (const CryptoPP::Exception& e) {
        cerr << e.what() << endl;
        exit(1);
    }

    return result;
}

該函數使用pbkdf2進行擴展,這裏須要一個pwd進行擴展,使用的方式是SHA1,雖然比較短可是截止到我作實驗爲止世界上尚未人破解,那麼久認爲它是安全的。在本次實驗選擇使用initKey來擴展,返回值是擴展後的結果,對輸入的值進行了解碼和從新編碼,選擇使用16進制。

Hash函數:

string PasswordManagerHelper::hash(const string& message, const string& key) {
    string mac, encoded;
    try {
        HMAC<SHA1> hmac((byte*)key.c_str(), key.length());
        StringSource(message, true, new HashFilter(hmac, new StringSink(mac)));
        encoded.clear();
        StringSource(mac, true, new Base64Encoder(new StringSink(encoded)));
    } catch (const CryptoPP::Exception& e) {
        cerr << e.what() << endl;
        exit(1);
    }
    return encoded;
}

該函數經過hmac一個使用了key的hash函數進行哈希計算,輸出的值通過從新編碼後再返回。

運行結果

查看一下軟件的版本和使用方法

這裏列出的該軟件所有的使用方法和版辦號,目前的版本號比較低,推出以後版本號會變成1.0正式版

新用戶登陸

新用戶登陸,新建一個數據文件用戶存儲,新用戶與否取決因而否有用戶數據。用戶數據的首行存儲的是用戶名和通過AES加密後的16進制用戶密碼

嘗試錯誤密碼:

說明了100分纔是該用戶的正確登錄密碼,0分是不行的。

添加域名和密碼:

添加鍵值對,存儲的格式是域名,哈希值的16進制,網站密碼通過AES加密的16進制形式

嘗試重複添加相同的域名:

由於已經存儲過了,因此會提示已存在

刪除域名密碼對:

查看本來就有的用戶數據,而後刪除其中一條,而後再從新查看,發現刪除成功。

嘗試刪除不存在的域名:

對於不存在的域名,沒法刪除,提示不存在

查詢指定域名的密碼:

成功查詢到記錄的密碼

查詢不存在的域名:

查詢失敗。好想買一個.com域名

修改指定域名的密碼:

對比先後兩次能夠發現,指定域名的存儲密碼已經改變

修改不存在的域名:

會提示修改失敗,域名不存在

人爲修改用戶數據:

先手動修改一下數據,而後登錄系統會提示數據已經被修改過,可能已經不安全了,再也不讀取這份文件,保證了完整性。

後記

密性指的是密碼的安全不能暴露在文本中,須要通過加密後存儲。完整性指的是加密後的數據不能被別人修改過,須要驗證這一點藉助了哈希函數的幫忙

使用了Crytopp來進行加密和解密的操做,基本上都是使用庫函數操做,可是私密性和完整性在上述的說明已經很好地體現了。

使用了UNIX的CLI標準這點是比較高興的。

傳送門:下載

相關文章
相關標籤/搜索