-1

Is there a way to get a non-deterministic output from the CryptoAPI? In other words, a different string output when encrypting a string.

For example, using CALG_AES_256 when deriving a crypt key with password of 'password' and string to encrypt of 'a', it always returns "SnÆwÞ¢L\x1e?6FÏLþw"

I'm somewhat of a n00b in using CryptoAPI, so any assistance is appreciated.

Edited: Here is the cryptography code from Microsoft's example code decrypte and encrypt This is the same code, just shortened/compacted. This code was compiled in VS 2017 as a Win32 Console app. pszSource and pszDest are two files in the C:\temp folder. source.txt has the letter we're trying to encrypt in it.

The problem I'm having is that this crypt/decrypt code from the CryptoAPI does not allow certain strings to be encrypted and then decrypted (i.e. n, t, L, p, aa, ab, ac, ad, ae, etc). If someone can tell me why, that would be very helpful.

#include <windows.h>
#include <tchar.h>
#include <wincrypt.h>

#define KEYLENGTH  0x00800000
#define ENCRYPT_BLOCK_SIZE 8 

bool MyDecryptFile(LPTSTR szSource,LPTSTR szDestination,LPTSTR szPassword);
bool MyEncryptFile(LPTSTR szSource,LPTSTR szDestination,LPTSTR szPassword);

int _tmain(int argc, _TCHAR* argv[])
{

    LPTSTR pszSource = L"c:\\temp\\source.txt";
    LPTSTR pszDestination = L"c:\\temp\\dest.txt";
    LPTSTR pszPassword = L"t";

    if (MyEncryptFile(pszSource, pszDestination, pszPassword))
        {
        _tprintf(TEXT("Encryption of the file %s was successful. \n"),pszSource);
        _tprintf(TEXT("The encrypted data is in file %s.\n"),pszDestination);
        }

    if (MyDecryptFile(pszSource, pszDestination, pszPassword))
        {
        _tprintf(TEXT("Encryption of the file %s was successful. \n"),pszSource);
        _tprintf(TEXT("The encrypted data is in file %s.\n"),pszDestination);
        }

    return 0;
}

bool MyEncryptFile(LPTSTR pszSourceFile,LPTSTR pszDestinationFile,LPTSTR pszPassword)
{
    bool fReturn = false;
    HANDLE hSourceFile = INVALID_HANDLE_VALUE, hDestinationFile = INVALID_HANDLE_VALUE;
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKey = NULL, hXchgKey = NULL;
    HCRYPTHASH hHash = NULL;
    PBYTE pbBuffer = NULL;
    DWORD dwBlockLen, dwBufferLen, dwCount;

    hSourceFile = CreateFile(pszSourceFile,FILE_READ_DATA,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (INVALID_HANDLE_VALUE == hSourceFile)
        goto Exit_MyEncryptFile;

    hDestinationFile = CreateFile(pszDestinationFile,FILE_WRITE_DATA,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    if (INVALID_HANDLE_VALUE == hDestinationFile)
        goto Exit_MyEncryptFile;

    CryptAcquireContext(&hCryptProv,NULL,MS_ENH_RSA_AES_PROV,PROV_RSA_AES,0);
    CryptCreateHash(hCryptProv,CALG_SHA_256,0,0,&hHash);
    CryptHashData(hHash,(BYTE *)pszPassword,lstrlen(pszPassword),0);
    CryptDeriveKey(hCryptProv,CALG_AES_256,hHash,CRYPT_EXPORTABLE,&hKey);

    dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;
    if (ENCRYPT_BLOCK_SIZE > 1)
        dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE;
    else
        dwBufferLen = dwBlockLen;
    pbBuffer = (BYTE *)malloc(dwBufferLen);

    bool fEOF = FALSE;
    do
    {
        if (ReadFile(hSourceFile,pbBuffer,dwBlockLen,&dwCount,NULL))
            {
            if (dwCount < dwBlockLen)
                fEOF = TRUE;
            if (CryptEncrypt(hKey,NULL,fEOF,0,pbBuffer,&dwCount,dwBufferLen))
                WriteFile(hDestinationFile,pbBuffer,dwCount,&dwCount,NULL);
            }
    } 
    while (!fEOF);

    fReturn = true;

Exit_MyEncryptFile:
    if (hSourceFile) CloseHandle(hSourceFile);
    if (hDestinationFile) CloseHandle(hDestinationFile);
    if (pbBuffer) free(pbBuffer);
    if (hHash) {CryptDestroyHash(hHash);hHash = NULL;}
    if (hKey) CryptDestroyKey(hKey);
    if (hCryptProv) CryptReleaseContext(hCryptProv, 0);

    return fReturn;
}


bool MyDecryptFile(LPTSTR pszSourceFile,LPTSTR pszDestinationFile,LPTSTR pszPassword)
{
    bool fReturn = false;
    HANDLE hSourceFile = INVALID_HANDLE_VALUE, hDestinationFile = INVALID_HANDLE_VALUE;
    HCRYPTKEY hKey = NULL;
    HCRYPTHASH hHash = NULL;
    HCRYPTPROV hCryptProv = NULL;
    PBYTE pbBuffer = NULL;
    DWORD dwCount, dwBlockLen, dwBufferLen;

    hSourceFile = CreateFile(pszDestinationFile,FILE_READ_DATA,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    if (INVALID_HANDLE_VALUE == hSourceFile)
        goto Exit_MyDecryptFile;

    hDestinationFile = CreateFile(pszSourceFile,FILE_WRITE_DATA,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (INVALID_HANDLE_VALUE == hDestinationFile)
        goto Exit_MyDecryptFile;

    CryptAcquireContext(&hCryptProv,NULL,MS_ENH_RSA_AES_PROV,PROV_RSA_AES,0);
    CryptCreateHash(hCryptProv,CALG_SHA_256,0,0,&hHash);
    CryptHashData(hHash,(BYTE *)pszPassword,lstrlen(pszPassword),0);
    CryptDeriveKey(hCryptProv,CALG_AES_256,hHash,CRYPT_EXPORTABLE,&hKey);

    dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;
    dwBufferLen = dwBlockLen;

    pbBuffer = (PBYTE)malloc(dwBufferLen);
    bool fEOF = false;
    do
    {
        if (!ReadFile(hSourceFile,pbBuffer,dwBlockLen,&dwCount,NULL))
            goto Exit_MyDecryptFile;

        if (dwCount <= dwBlockLen)
            fEOF = TRUE;

        LONG rv = CryptDecrypt(hKey,0,fEOF,0,pbBuffer,&dwCount);
        if (rv==0)
            {
            DWORD dwErr = GetLastError();       // <--- fails if password and string are n, t, L, p, aa, ab, ac, ad , ae
            goto Exit_MyDecryptFile;
            }

        if (!WriteFile(hDestinationFile,pbBuffer,dwCount,&dwCount,NULL))
            goto Exit_MyDecryptFile;
    } 
    while (!fEOF);

    fReturn = true;

Exit_MyDecryptFile:

    if (pbBuffer) free(pbBuffer);
    if (hSourceFile) CloseHandle(hSourceFile);
    if (hDestinationFile) CloseHandle(hDestinationFile);
    if (hHash) {CryptDestroyHash(hHash);hHash = NULL;}
    if (hKey) CryptDestroyKey(hKey);
    if (hCryptProv) CryptReleaseContext(hCryptProv, 0);

    return fReturn;
}

What about using this to get the KP_IV option?

BOOL bRV;
bRV = CryptAcquireContextW(&hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0);
bRV = CryptGenKey(hCryptProv, CALG_AES_256,0,&hKey);
DWORD dwMode = CRYPT_MODE_CBC;
bRV = CryptSetKeyParam(hKey,KP_MODE,(BYTE*)&dwMode,0);
BYTE pbData[16];
memcpy(pbData,"n",sizeof("n"));  // <--- Hard coded password
bRV = CryptSetKeyParam(hKey,KP_IV,pbData,0);
enter code here
JeffR
  • 765
  • 2
  • 8
  • 23
  • 1
    When saving a password verifier just using a hash function is not sufficient and just adding a salt does little to improve the security. Instead iterate over an HMAC with a random salt for about a 100ms duration and save the salt with the hash. Better yet use a function such as `PBKDF2`, `Rfc2898DeriveBytes`, `password_hash`, `Bcrypt`, `passlib.hash` or similar functions. The point is to make the attacker spend a substantial of time finding passwords by brute force. – zaph Sep 22 '17 at 11:55
  • 1
    Hash functions are deterministic, that is a major property of them, In order to obtain a random output you need to supply a random input generally obtained with a Cryptographically Secure Pseudo Random Number Generator [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator). – zaph Sep 22 '17 at 11:59
  • Thanks. The old capicom.dll file did aes 256 and gave a different string each time, but it decrypted correctly each time. – JeffR Sep 22 '17 at 13:54
  • 1. It is common to use a random IV and prefix the encrypted data with it, that way the encryption is different for the same inout and can be decrypted using the prefixed IV. 2. It is hard to see what is happening with no code/inputs, etc. Please update the question with a [mcve] 3. It would also help you to take the time to study text encodings and understand that not all binary can be expressed in displayable text. – zaph Sep 22 '17 at 16:38
  • I put the source code example in the question. – JeffR Sep 22 '17 at 18:28
  • 1. `KEYLENGTH 0x00800000`does not make ant sense. AES supports only three key length: 128, 192 & 256 **bits**. 2. `ENCRYPT_BLOCK_SIZE 8` AES has a block size if 16 bytes. 3. But none of these are used so why are they there? 4. What a throwback to the 80's with hungarian notation and `goto`s! – zaph Sep 22 '17 at 18:43
  • I just compressed the example given from Microsoft. – JeffR Sep 24 '17 at 12:09
  • Question: if I pass a long 40-char password into the CryptHashData function, would that be a secure solution that would meet your point of making an attacker spending a substantial amount of time using brute force? – JeffR Sep 25 '17 at 14:07
  • It should but you never supplied the language/framework you are using. But wondering, would you go to a general doctor if you needed knee surgery of would you go to a doctor that had the specific training? Here is the deal, we have a major problem with computer security and you want to create something that requires security. Do you really feel you are trained to provide good security? If not get a security expert to at least advise and review, I have doe that when developing secure code. It is the users who will be at risk. – zaph Sep 25 '17 at 19:39
  • It's Visual Studio compiler with C++ code using Microsoft's CryptoAPI – JeffR Sep 25 '17 at 20:42
  • @zaph, 1. if I put a random IV into the hKey, I need to know what IV was used before I run CryptDecrypt, correct? 2. If I do a CryptSetKeyParam with KP_IV, that is done *after* the CryptDeriveKey, correct? – JeffR Oct 01 '17 at 19:26

1 Answers1

0

If you want to obtain different cypertext when encrypting the same plaintext with the same key, you have to use the CBC mode of operation: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

In order to correctly encrypt with CBC, you need to generate a different random Initialization Vector (IV) every time. In order to decrypt, you need to know the IV used during encryption. So, the IV must be associated (in clear) to the cyphertext.

In reference to your example, when calling the CryptDeriveKey function, the CBC is the default mode but it uses an IV set to zero and this invalidates the utility of the CBC operating mode: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379916(v=vs.85).aspx

In order to set the random IV you need to call the CryptSetKeyParam function, which accept the KP_IV param: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380272(v=vs.85).aspx

Bye Giovanni

Gio
  • 94
  • 5
  • I've put example code in the question for CryptSetKeyParam. Is that example code the correct way to use CryptSetKeyParam? It is encrypting correctly, but when I use the same code before a CryptDecrypt, CryptDecrypt sets GetLastEror to NTE_BAD_DATA. – JeffR Sep 24 '17 at 12:43
  • CBC made with a zero IV is not secure but is better than ECB mode at concealing patterns, see [ECB mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_.28ECB.29), scroll down to the Penguin. There are also other modes other than CBC that use a random IV or IV substitute. – zaph Sep 24 '17 at 17:12
  • I can get it to encrypt with a zero IV, but I can't get it to decrypt. Can you help me with that? – JeffR Sep 25 '17 at 12:58
  • I'm sorry but, at the moment, I haven't an environment ready to test your code. But you can find a good example about how to use CryptSetKeyParam to set the IV here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa382370(v=vs.85).aspx If you look carefully at the steps in the example, I think you can reach your goal. – Gio Sep 25 '17 at 13:11
  • Thank you. Quick question: if I use a 40-char string for the IV, and another 40-char string for the CryptHashData, would that accomplish the goal of thwarting brute-force attacks, which is the main way to crack the codes? Or is one 40-char string strong enough when used with CryptHashData? – JeffR Sep 25 '17 at 18:42
  • If the attacker knows that your key is obtained from a password, the brute force attack will be against possibile passwords. So the only way to obtain more strength is to use a longer password. In other words, if you use a long hash but the attacker knows that your are feeding the hash function with a password, he will try to guess the password and not the hash. – Gio Sep 25 '17 at 22:53