2014-10-30 161 views
1

我想做一個簡單的應用程序加密一個字符串,然後解密它。 到目前爲止我的代碼:CryptDecrypt在解碼字符串的末尾返回隨機字符?

int main(int argc, char* argv[]) 
{ 
char test[ 32 ] = { 0 }; 
strcpy(test, "This is a sample string."); 
BYTE buf = NULL; 
DWORD len = strlen(test); 

EncryptData(lpszPassword, test, &len); 

return 0; 
} 

void EncryptData(TCHAR *lpszPassword, char *pbBuffer, DWORD *dwCount) 
{ 
HCRYPTPROV hProv = 0; 
HCRYPTKEY hKey = 0; 
HCRYPTHASH hHash = 0; 
LPWSTR wszPassword = lpszPassword; 
DWORD cbPassword = (wcslen(wszPassword) + 1)*sizeof(WCHAR); 

if (!CryptAcquireContext(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) 
{ 
    printf("Error %x during CryptAcquireContext!\n", GetLastError()); 
    goto Cleanup; 
} 

if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) 
{ 
    printf("Error %x during CryptCreateHash!\n", GetLastError()); 
    goto Cleanup; 
} 

if (!CryptHashData(hHash, (PBYTE)wszPassword, cbPassword, 0)) 
{ 
    printf("Error %x during CryptHashData!\n", GetLastError()); 
    goto Cleanup; 
} 

if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey))//hKey 
{ 
    printf("Error %x during CryptDeriveKey!\n", GetLastError()); 
    goto Cleanup; 
} 
DWORD size = (DWORD)strlen(pbBuffer)/sizeof(char); 
printf("\nLength of string = %d", size); 
if (!CryptEncrypt(hKey, 0, TRUE, 0, (LPBYTE)pbBuffer, &size, BLOCK_SIZE)) 
{ 
    printf("Error %x during CryptEncrypt!\n", GetLastError()); 
    goto Cleanup; 
} 
printf("\nEncrypted bytes = %d", size); 
printf("\nEncrypted text = %s", pbBuffer); 

if (!CryptDecrypt(hKey, 0, TRUE, 0, (LPBYTE)pbBuffer, &size)) 
{ 
    printf("Error %x during CryptDecrypt!\n", GetLastError()); 
    goto Cleanup; 
} 
printf("\nDecrypted bytes = %d", size); 
printf("\nDecrypted text = %s", pbBuffer); 

Cleanup: 

if (hKey) 
{ 
    CryptDestroyKey(hKey); 
} 
if (hHash) 
{ 
    CryptDestroyHash(hHash); 
} 
if (hProv) 
{ 
    CryptReleaseContext(hProv, 0); 
} 

} 

這將產生輸出:

Length of string = 24 
Encrypted bytes = 32 
Encrypted text = ╨é╖·ç┤╠├ó br.≡·►;╜K/┤E(↓)╫%┤Cà¡╩╠╠╠╠╘)Ñ°♀·L 
Decrypted bytes = 24 
Decrypted text = This is a sample string.)╫%┤Cà¡╩╠╠╠╠╘)Ñ°♀·L 

所以basicly它幾乎工作,但是從加密的字符串留在加密的字符串的和有字符。

所以我的問題是,我做錯了什麼或者我只是想念什麼?

在此先感謝!

+0

'Decrypted bytes = 24'指示緩衝區中有效數據的長度,但是您打印整個事物... – 2014-10-30 17:59:05

+0

您將此標記爲'C++',但實際上它全部是'C'。 – PaulMcKenzie 2014-10-30 18:05:24

+0

@PaulMcKenzie:對不起,錯打了它 – kampi 2014-10-30 18:07:53

回答

2

當給出「%s」時,printf函數需要一個以NULL結尾的字符串。很明顯,該字符串不是NULL終止的(實際上,NULL位於誰知道的地方,但printf()在打印數據的有效部分之後很久才發現它)。

使用您檢索的size值作爲解密文本。這是有效的實際字節數。

這是一個解決方案,不僅糾正size和解密數據問題,而且還糾正使用goto的問題。

#include <string> 
#include <iostream> 

using namespace std; 

struct CryptStuff 
{ 
    HCRYPTPROV* hProv; 
    HCRYPTKEY* hKey; 
    HCRYPTHASH* hHash; 

    CryptStuff(HCRYPTPROV* hprov, HCRYPTKEY* hkey, HCRYPTHASH* hash) : 
        hProv(hprov), hKey(hkey), hHash(hash) {} 

     ~CryptStuff() 
     { 
     if (*hKey) CryptDestroyKey(*hKey); 
     if (*hHash) CryptDestroyHash(*hHash); 
     if (*hProv) CryptReleaseContext(*hProv, 0); 
     } 
}; 

void EncryptData(TCHAR *lpszPassword, char *pbBuffer, DWORD *dwCount) 
{ 
    HCRYPTPROV hProv = 0; 
    HCRYPTKEY hKey = 0; 
    HCRYPTHASH hHash = 0; 

    // create an instance of CryptStuff. This will cleanup the data on return 
    CryptStuff cs(&hProv, &hKey, &hHash); 

    LPWSTR wszPassword = lpszPassword; 
    DWORD cbPassword = (wcslen(wszPassword) + 1)*sizeof(WCHAR); 

    if (!CryptAcquireContext(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 
           CRYPT_VERIFYCONTEXT)) 
    { 
     return; 
    } 

    if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) 
    { 
     return; 
    } 

    if (!CryptHashData(hHash, (PBYTE)wszPassword, cbPassword, 0)) 
    { 
     return; 
    } 

    if (!CryptDeriveKey(hProv, CALG_AES_256, hHash, CRYPT_EXPORTABLE, &hKey)) 
    { 
     return; 
    } 

    DWORD size = (DWORD)strlen(pbBuffer)/sizeof(char); 
    cout << "\nLength of string = " << size; 
    if (!CryptEncrypt(hKey, 0, TRUE, 0, (LPBYTE)pbBuffer, &size, BLOCK_SIZE)) 
    { 
     return; 
    } 
    cout << "\nEncrypted bytes = " << size; 
    cout << "\nEncrypted text = "; 
    cout.write(pbBuffer, size); 

    if (!CryptDecrypt(hKey, 0, TRUE, 0, (LPBYTE)pbBuffer, &size)) 
    { 
     return; 
    } 
    cout << "\nDecrypted bytes = " << size; 
    cout << "\nDecrypted text = "; 
    cout.write(pbBuffer, size); 
} 

我寫了這個沒有編譯器方便,所以原諒任何錯別字。爲了簡潔,我也刪除了錯誤輸出。

上面的代碼首先通過使用cout.write來輸出正確數量的字符(由size值表示)來糾正解密數據的問題。這保證我們得到我們想要的字符輸出。我使用了cout.write,因爲對於未加密的數據來說,包含嵌入的NULL是完全可以接受的,而且我們不想停止顯示在字符串中的第一個NULL。一旦我們點擊輸出的字符數size,我們就會停止。


該做的下一件事是使用一種稱爲RAII技術(資源獲取就是初始化)刪除goto。請注意這是如何完成的:

我們首先創建了一個名爲CryptStuff的結構,它包含指向我們要清理的3個項目的指針。在這個結構中,我們有一個清理這些項目的析構函數。爲了利用這個結構,我們在EncryptData的內部創建了一個名爲cs的實例,並給出構造實例的3個項目的地址。

因此,基本上,當EncryptData返回時,該cs實例將自動調用它的析構函數,這意味着我們可以清理我們的句柄。這比使用諸如goto(實際上任何東西都比goto更好)或棘手的冗餘編碼來清理句柄更有利。爲什麼清理是自動的 - 無論返回EncryptData的原因是什麼,即return或某個函數導致異常被拋出,我們都會清理這些句柄。另外,如果在以後的時間,代碼變得更加複雜,那麼對於每個新的返回場景,都不需要記得一遍又一遍地「添加goto」或「編寫清理代碼」。請注意,錯誤條件執行簡單的return而不需要goto

RAII信息可以在這裏找到:

What is meant by Resource Acquisition is Initialization (RAII)?

它是在寫C++有管理創建的資源,必須始終如一地破壞代碼的重要組成部分。

+0

所以基本上我沒有做任何錯誤,它只是Cryptdecrypt如何返回字符串,對吧?所以我只需要添加終止NULL。 – kampi 2014-10-30 18:08:58

+0

你需要小心。對於未加密(或解密)的數據來說,包含嵌入的NULL是完全有效的。 – PaulMcKenzie 2014-10-30 18:34:33

+0

非常感謝!但爲什麼你刪除了你之前發佈的代碼?它工作正常,這是可以理解的你爲什麼做這些改變。 – kampi 2014-10-30 19:15:13