Easy String Encryption Using CryptoAPI in C++

DanRollins
CERTIFIED EXPERT
Published:
Updated:
When your program needs to access a service, web site, or API that requires a username and password login, how can you store the password so that it is never written to disk in cleartext?  

You need to encrypt it... and I'm not talking about some sort of XOR/scrambling logic that a cypher tech could break in five minutes, but real RSA encryption.  The Windows CryptoAPI provides the tools to do this, but the documentation is rather complicated and the steps are not particularly obvious.

A typical scenario might be that you need to make a database connection that requires a password:

1) Get input of the password used in a SQL Server connection string in a settings dialog.
2) Encrypt: Make the password data unusable except to your program.
3) Store the encrypted data into the System Registry.
   ... later ...
4) Decrypt:  Recover the original cleartext data.
5) Build the database connection string using the cleartext password.

I wrote a smallish (140-line) C++ class to do steps 2 and 4, and I'll provide the entire source code in this article.  Here is the rough outline of the steps:

Setup:
    CryptAcquireContext
    CryptCreateHash
    CryptHashData
    CryptDeriveKey  (create the key needed below)
Encrypt:
    CryptEncrypt
    Convert binary to hexadecimal (for easy transport/storage)
Decrypt:
   Convert hexadecimal to binary (for using the CryptoAPI)
   CryptDecrypt

It boils down to:  Make a CryptoAPI key and use it in calls to CryptEncrypt and CryptDecrypt.   Creating that key is the only tricky (non-obvious) part.  You start with a base raw key (any string of characters), hash it, and then convert that hash into a key that can be used by the CryptoAPI.  

Base Raw Key and Salt
So what do you use as the original base raw key string of characters?  I want my code to be extremely simple and not require that the caller know the base key.  I wanted to be able to use this tool from various program modules transparently, so I provided a built-in "default" base key as a member variable.  You may see the security hole there:  Anyone who has a copy of my program can decrypt anything that has been (default-ly) encrypted by my program.

To give a warm feeling to my clients, I added a feature called a salt -- an optional, user-provided string that can be appended to the default base key string.  Now, if my users add a salt, then anybody who does not know that salt value can never decrypt the passwords (or Social Security Numbers or other encrypted data).  Note:  I put a big warning in my documentation:  "If you forget your salt value, you will lose all of the encrypted data and there is no backdoor.  Don't call tech support because we can't help you."

Problems with Binary Data
One significant problem you will encounter with encrypted data is that it is no longer simple text.   It ends up as a very random-looking array of binary data -- including unprintable characters, apostrophes and percentage signs (SQL users beware!), and worst of all, it may contain embedded binary NULL values (0x00).  As a C/C++ programmer you know that it is possible to work around such situations in various ways, but you also know that you will eventually run into headaches.  

I chose the simple expedient of always storing encrypted data as a C-style string of hexadecimal digits.   The hex-encoded secret data is always exactly twice as long as the encrypted data, which makes it somewhat easier to work with than base-64 encoding or other techniques.

Enough with the jawing... let's get to the code.

Crypt.h -- Header for the Encryption Object
#pragma once
                      #include "stdafx.h"
                      #include <Wincrypt.h>
                      
                      class CCrypt
                      {
                      public:
                          CCrypt(void);
                          virtual ~CCrypt(void) {
                              if ( m_hKey )  CryptDestroyKey( m_hKey ); 
                              if ( m_hHash ) CryptDestroyHash( m_hHash ); 
                              if ( m_hProv ) CryptReleaseContext( m_hProv, 0); 
                          }
                          BOOL SetKey( LPCSTR szKey= 0, LPCSTR pszSalt= 0 );
                      
                          BOOL EncryptDecrypt( BYTE* pData, DWORD* dwDataLen, LPCSTR pKey, BOOL fEncrypt );
                      
                          CString EncryptStrToHex(   LPCSTR szText, LPCSTR pKey= 0, LPCSTR pszSalt= 0 );
                          CString DecryptStrFromHex( LPCSTR szHex,  LPCSTR pKey= 0, LPCSTR pszSalt= 0 );
                      
                          CString EncodeToHex(   BYTE* p, int nLen );
                          int     DecodeFromHex( LPCSTR pSrc, BYTE* pDest, int nBufLen );
                      
                      private:
                          HCRYPTPROV  m_hProv;
                          HCRYPTHASH  m_hHash;
                          HCRYPTKEY   m_hKey;
                      
                          BOOL        m_fOK;
                          DWORD       m_nLastErr;
                          CString     m_sErrMsg;
                          char*       m_pszDefaultKeyRaw;
                      };

Open in new window

Crypt.cpp -- Code of the Encryption Object
#include "utCrypt.h"
                      CCrypt::CCrypt(void)
                      {
                          m_hProv= m_hHash= m_hKey= 0; 
                          m_pszDefaultKeyRaw= "fdC)Y%yum3ww09";
                      }
                      BOOL CCrypt::SetKey( LPCSTR szKey, LPCSTR szSalt/*=0*/ )
                      {
                          m_fOK= TRUE;
                          if ( 0 == m_hProv ) {
                              m_fOK= CryptAcquireContext( &m_hProv, NULL, 
                                  MS_DEF_PROV, 
                                  PROV_RSA_FULL, 
                                  CRYPT_VERIFYCONTEXT 
                              );
                          }
                          if ( m_fOK && (0 != m_hHash) ) {
                              m_fOK= CryptDestroyHash( m_hHash ); 
                              m_hHash= 0;
                          }
                          if ( m_fOK && (0 == m_hHash) ) {
                              m_fOK= CryptCreateHash( m_hProv, CALG_MD5, 0, 0, &m_hHash );
                          }
                          if ( m_fOK ) {
                              if ( 0 == szKey ) {  // request to use default rawKey
                                  char szTmp[100];
                                  strcpy_s( szTmp, sizeof(szTmp), m_pszDefaultKeyRaw );
                                  if ( szSalt ) {
                                      strncat_s( szTmp, sizeof(szTmp), szSalt, 5 ); // use part of salt
                                  }
                                  // minor security tweak -- scramble the key+salt
                                  int nLen= strlen(szTmp)-1;  
                                  for ( int j=0; j< nLen; j++ ) {
                                      char c= szTmp[nLen-j];
                                      szTmp[nLen-j]= (char)(szTmp[j]+5);
                                      szTmp[j]= c;
                                  }
                                  szKey= &szTmp[4]; // discard the first part, for fun
                              }
                              m_fOK= CryptHashData( m_hHash, (BYTE*)szKey, strlen(szKey), 0);
                          }
                          if ( m_fOK ) {
                              m_fOK= CryptDeriveKey( m_hProv, CALG_RC4, m_hHash, CRYPT_EXPORTABLE, &m_hKey);
                          }
                          if ( !m_fOK ) { 
                              m_nLastErr= GetLastError(); 
                              m_sErrMsg= "Error creating encryption key";
                          }
                          return( m_fOK );
                      }
                      //--- workhorse function:  Encrypt or decrypt "in place"
                      BOOL CCrypt::EncryptDecrypt( BYTE* pData, DWORD* dwDataLen, LPCSTR pKey, BOOL fEncrypt )
                      {
                          m_fOK= TRUE;
                          SetKey( (LPCSTR)pKey );	
                          if ( fEncrypt ) {
                                 m_fOK= CryptEncrypt( m_hKey, 0, TRUE, 0, pData, dwDataLen, *dwDataLen );
                          }
                          else  {
                              m_fOK= CryptDecrypt( m_hKey, 0, TRUE, 0, pData, dwDataLen );
                          }
                          return( m_fOK );
                      }
                      
                      CString CCrypt::EncryptStrToHex( LPCSTR szText, LPCSTR pszKey/*= 0*/, LPCSTR pszSalt/*= 0*/ )
                      {
                          m_fOK= TRUE;
                          CString sRet= "";
                          DWORD nDataLen= strlen( szText );
                          if ( pszSalt || pszKey || (0 == m_hKey) ) {
                              m_fOK= SetKey( (LPCSTR)pszKey, pszSalt );	
                          }
                          if ( m_fOK ) {
                              char* pTmp= new char[nDataLen+1] ;
                              strncpy_s( pTmp, nDataLen+1, szText, nDataLen+1 );
                              m_fOK= CryptEncrypt( m_hKey, 0, TRUE, 0, (BYTE*)pTmp, &nDataLen, nDataLen );
                              if (m_fOK ) {
                                  sRet= EncodeToHex( (BYTE*)pTmp, nDataLen );
                              }
                              delete pTmp;
                          }
                          return( sRet );
                      }
                      
                      CString CCrypt::DecryptStrFromHex( LPCSTR szHex, LPCSTR pszKey/*=0*/, LPCSTR pszSalt/*= 0*/ )
                      {
                          m_fOK= TRUE;
                          CString sRet= "";
                          DWORD nDataLen= strlen( szHex );
                      
                          if ( pszSalt || pszKey || (0 == m_hKey) ) {
                              m_fOK= SetKey( (LPCSTR)pszKey, pszSalt );	
                          }
                          if ( m_fOK ) {
                              DWORD nDecryptLen= nDataLen/2;
                              char* pTmp= new char[ nDecryptLen+1 ];
                              DecodeFromHex( szHex, (BYTE*)pTmp, nDecryptLen );
                              m_fOK= CryptDecrypt( m_hKey, 0, TRUE, 0, (BYTE*)pTmp, &nDecryptLen );
                              if ( m_fOK ) {
                                  sRet= pTmp;
                              }
                              delete pTmp;
                          }
                          return( sRet );
                      }
                      
                      //--------------------------------------------------------
                      // inefficient but requires no explanation :-)
                      CString CCrypt::EncodeToHex( BYTE* p, int nLen )
                      {
                          CString sRet, sTmp;
                          for( int j=0; j< nLen; j++ ) {
                              sTmp.Format( "%02x", p[j] );
                              sRet+= sTmp;
                          }
                          return (sRet );
                      }
                      
                      //---------------------------------------------------------
                      // returns length of decoded hex buffer
                      int CCrypt::DecodeFromHex( LPCSTR pSrc, BYTE* pDest, int nBufLen )
                      {
                          int nRet= 0;
                          int nLen= strlen(pSrc);
                          *pDest = 0;
                          BYTE cIn1, cIn2, nFinal;
                          for( int j=0; j< nLen; j += 2 ) {
                              cIn1= (BYTE)toupper(*pSrc++);  cIn1 -= '0'; if ( cIn1>9 ) cIn1 -= 7;
                              cIn2= (BYTE)toupper(*pSrc++);  cIn2 -= '0'; if ( cIn2>9 ) cIn2 -= 7;
                              nFinal= (BYTE)((cIn1 << 4) | cIn2); 
                              if (nFinal>255) nFinal=0; // in case trying to decode non-hex data
                              *pDest++ = nFinal; 
                              *pDest = 0;
                              if ( nRet >= nBufLen ) {
                                  break;
                              }
                              nRet++;
                          }
                          return( nRet );
                      }

Open in new window


Here is an example of usage:
#include "Crypt.h"
                          ...
                      void CEncryptTestDlg::OnBnClickedButton1()
                      {
                         CCrypt crypt;
                         char szSrc[]="Secret Password";
                      
                         //------------ use default rawKey
                         CString sEncrypted= crypt.EncryptStrToHex( szSrc );  
                         CString sDecrypted= crypt.DecryptStrFromHex( sEncrypted );
                         MessageBox( sEncrypted,sDecrypted );
                      
                         //------------ test use of wrong salt value
                         sEncrypted= crypt.EncryptStrToHex(   szSrc,     0,"mySalt"   );  
                         sDecrypted= crypt.DecryptStrFromHex( sEncrypted,0,"yourSalt" ); // oops!
                      }

Open in new window


Notes:
The EncryptStrToHex function assumes that the input data is simple text -- it calculates the length using strlen().  Don't use it to encrypt binary data (any data that might contain an embedded NULL).
The CryptEncrypt and CryptDecrypt CryptoAPIs do their work in place; that is, they overwrite the original with the modified data.  I wanted to be able to pass LPCSTR data (constant string) to the functions, so I chose to make a copy of the incoming data.  This would be inefficient if working with larger buffers.
My actual (production) code provides a few variations of the main functions; for instance, it can encrypt binary data (not just text strings) and one variation does not encode to hex.  I removed most of this extraneous code from this article because those functions are not needed nearly as often -- and I wanted to leave something for you to do on your own :-)
You may want to "disguise" the length of the secret data.  For instance, a codebreaker could save a lot of time knowing that the cleartext string is only 3 characters (six hexadecimal digits).   The simplest technique is to pad the input string with spaces, and then strip them off when the cleartext is needed.  

But you can also use the lower-level Crypt::EncryptDecrypt function -- it does not assume that the input is clean text, so it will accept embedded NULLs; thus, you could append a NULL and pad with random characters.  For instance, if the input text is
     "Secret Password\0asXXefg"
then after decrypting, the string will end at the right place.
Just an afterthought:  If all you need is a way to authenticate the person who uses your program, there is no need to encrypt or store or transport his password at all.  Instead just create and store a hash of his password.  Each time he logs on, hash the input password and compare it to the stored value.  That way the cleartext password is irrelevant -- never needed and never stored.  

References:
Cryptography Reference
http://msdn.microsoft.com/en-us/library/aa380256(VS.85).aspx

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1
23,766 Views
DanRollins
CERTIFIED EXPERT

Comments (5)

could you please upload demo project?

This code is not working for me...

1. I create new class "Crypt.h"
2.then "Crypt.cpp"

then during compilation VC++ asked me to add '#include "StdAfx.h"' in "Crypt.cpp" after that following list of errors appeared...

1>------ Build started: Project: EditPath, Configuration: Debug Win32 ------
1>Build started 3/1/2012 8:22:24 PM.
1>InitializeBuildStatus:
1>  Touching "Debug\EditPath.unsuccessfulbuild".
1>ClCompile:
1>  All outputs are up-to-date.
1>  Crypt.cpp
1>d:\ibs\editpath\editpath\crypt.cpp(1): warning C4627: '#include "Crypt.h"': skipped when looking for precompiled header use
1>          Add directive to 'StdAfx.h' or rebuild precompiled header
1>d:\ibs\editpath\editpath\crypt.cpp(4): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(5): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
1>d:\ibs\editpath\editpath\crypt.cpp(6): error C2065: 'm_hProv' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(6): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(6): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(7): error C2065: 'm_pszDefaultKeyRaw' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(8): warning C4508: 'CCrypt' : function should return a value; 'void' return type assumed
1>d:\ibs\editpath\editpath\crypt.cpp(9): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(11): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(12): error C2065: 'm_hProv' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(13): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(13): error C2065: 'm_hProv' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(19): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(19): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(20): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(20): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(21): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(23): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(23): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(24): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(24): error C2065: 'm_hProv' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(24): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(26): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(29): error C2065: 'm_pszDefaultKeyRaw' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(42): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(42): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(44): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(45): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(45): error C2065: 'm_hProv' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(45): error C2065: 'm_hHash' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(45): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(47): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(48): error C2065: 'm_nLastErr' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(49): error C2065: 'm_sErrMsg' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(51): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(54): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(56): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(57): error C2660: 'SetKey' : function does not take 1 arguments
1>d:\ibs\editpath\editpath\crypt.cpp(59): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(59): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(62): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(62): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(64): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(67): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(69): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(70): error C2440: 'initializing' : cannot convert from 'const char [1]' to 'ATL::CStringT<BaseType,StringTraits>'
1>          with
1>          [
1>              BaseType=wchar_t,
1>              StringTraits=StrTraitMFC_DLL<wchar_t>
1>          ]
1>          Constructor for class 'ATL::CStringT<BaseType,StringTraits>' is declared 'explicit'
1>          with
1>          [
1>              BaseType=wchar_t,
1>              StringTraits=StrTraitMFC_DLL<wchar_t>
1>          ]
1>d:\ibs\editpath\editpath\crypt.cpp(72): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(73): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(75): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(78): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(78): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(79): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(80): error C3861: 'EncodeToHex': identifier not found
1>d:\ibs\editpath\editpath\crypt.cpp(87): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(89): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(90): error C2440: 'initializing' : cannot convert from 'const char [1]' to 'ATL::CStringT<BaseType,StringTraits>'
1>          with
1>          [
1>              BaseType=wchar_t,
1>              StringTraits=StrTraitMFC_DLL<wchar_t>
1>          ]
1>          Constructor for class 'ATL::CStringT<BaseType,StringTraits>' is declared 'explicit'
1>          with
1>          [
1>              BaseType=wchar_t,
1>              StringTraits=StrTraitMFC_DLL<wchar_t>
1>          ]
1>d:\ibs\editpath\editpath\crypt.cpp(93): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(94): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(96): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(99): error C3861: 'DecodeFromHex': identifier not found
1>d:\ibs\editpath\editpath\crypt.cpp(100): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(100): error C2065: 'm_hKey' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(101): error C2065: 'm_fOK' : undeclared identifier
1>d:\ibs\editpath\editpath\crypt.cpp(111): error C2653: 'CCrypt' : is not a class or namespace name
1>d:\ibs\editpath\editpath\crypt.cpp(115): error C2664: 'void ATL::CStringT<BaseType,StringTraits>::Format(const wchar_t *,...)' : cannot convert parameter 1 from 'const char [5]' to 'const wchar_t *'
1>          with
1>          [
1>              BaseType=wchar_t,
1>              StringTraits=StrTraitMFC_DLL<wchar_t>
1>          ]
1>          Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>d:\ibs\editpath\editpath\crypt.cpp(123): error C2653: 'CCrypt' : is not a class or namespace name
1>
1>Build FAILED.
CERTIFIED EXPERT
Author of the Year 2009

Author

Commented:
Create a dialog-based MFC App.  Add the two files.
Yes Dan I did that, My application is in VC++ MFC Unicode. My goal is to protect(encrypt) password string using my personal key. This string will be saved in Windows registry. I'm going to use this password by fetching registry key and decryption using the same key but in different application. Nothing is working for me, I tried with DPAPI but no luck. Where did I go wrong?
I think your code would not compile becuase the name of the .h file in the code should be Crypt.h and not "utCrypt.h" which is in the .cpp file as #include "utCrypt.h" which is no where to be found. So I just deleted the "ut" part and all of the errors disapear expect for this one :

Error      2      error LNK2019: unresolved external symbol "public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall CCrypt::EncodeToHex(unsigned char *,int)" (?EncodeToHex@CCrypt@@QAE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PAEH@Z) referenced in function "public: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __thiscall CCrypt::EncryptStrToHex(char const *,char const *,char const *)" (?EncryptStrToHex@CCrypt@@QAE?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PBD00@Z)      C:\Users\user\Documents\Visual Studio 2010\Projects\KeyDll\SprintDll\Crypt.obj

any clue to why this is being thrown? I am trying to add this to a DLL project I have. Is it possible to include in a TLL console app? or do I need to Create a dialog-based MFC App? I would prefer to just create the .cpp and .h files.
Oval PistonProgrammer, Consultant

Commented:
Your "Salt" is not working. It has the exact same encrypted code when run multiple times. SALT should give a different password with the same parameters, no?

btw, for those not using MFC and not using ISO C++ 20, here is the code for ISO C++ 14 in a Console:

Here is Main.cpp:
#include "Crypt.h"
#include <iostream>

using namespace std;
using std::string;

int main()
{
    CCrypt crypt;
    char szSrc[] = "Secret Password";
    string sEncrypted, sDecrypted;

    //------------ use default rawKey
    sEncrypted = crypt.EncryptStrToHex(szSrc);
    sDecrypted = crypt.DecryptStrFromHex(sEncrypted.c_str());
    //MessageBoxA(0, sEncrypted.c_str(), sDecrypted.c_str(), 0);

    //------------ test use of salt value
    sEncrypted = crypt.EncryptStrToHex(szSrc, "asdf", "mySalt");
    sDecrypted = crypt.DecryptStrFromHex(sEncrypted.c_str(), "asdf", "mySalt");
    MessageBoxA(0, sEncrypted.c_str(), sDecrypted.c_str(), 0);
}

Here is Crypt.h:
#pragma once
#include <Windows.h>
#include <Wincrypt.h>
#include <iostream>


using std::string;

class CCrypt
{
public:
    CCrypt(void);
    virtual ~CCrypt(void) {
        if ( m_hKey )  CryptDestroyKey( m_hKey ); 
        if ( m_hHash ) CryptDestroyHash( m_hHash ); 
        if ( m_hProv ) CryptReleaseContext( m_hProv, 0); 
    }
    BOOL SetKey( LPCSTR szKey= 0, LPCSTR pszSalt= 0 );

    BOOL EncryptDecrypt( BYTE* pData, DWORD* dwDataLen, LPCSTR pKey, BOOL fEncrypt );

    string EncryptStrToHex(LPCSTR szText);
    string EncryptStrToHex(LPCSTR szText, LPCSTR pKey, LPCSTR pszSalt);
    string DecryptStrFromHex( LPCSTR szHex,  LPCSTR pKey= 0, LPCSTR pszSalt= 0 );

    string EncodeToHex(   BYTE* p, int nLen );
    int     DecodeFromHex( LPCSTR pSrc, BYTE* pDest, int nBufLen );

private:
    HCRYPTPROV  m_hProv;
    HCRYPTHASH  m_hHash;
    HCRYPTKEY   m_hKey;

    BOOL        m_fOK;
    DWORD       m_nLastErr;
    string     m_sErrMsg;
    char*       m_pszDefaultKeyRaw;
};

And here is Crypt.cpp:
#include "Crypt.h"
#include <iostream>
#include <iomanip>
#include <sstream>

using std::string;
using std::ostringstream;

#define tnewc(nCharacters) new CHAR[nCharacters]()

CCrypt::CCrypt(void)
{
    m_hProv= m_hHash= m_hKey= 0; 
    m_pszDefaultKeyRaw = tnewc(64);
    strcpy_s(m_pszDefaultKeyRaw,64, "fdC)Y%yum3ww09");
}
BOOL CCrypt::SetKey( LPCSTR szKey, LPCSTR szSalt/*=0*/ )
{
    m_fOK= TRUE;
    if ( 0 == m_hProv ) {
        m_fOK= CryptAcquireContext( &m_hProv, NULL, 
            MS_DEF_PROV, 
            PROV_RSA_FULL, 
            CRYPT_VERIFYCONTEXT 
        );
    }
    if ( m_fOK && (0 != m_hHash) ) {
        m_fOK= CryptDestroyHash( m_hHash ); 
        m_hHash= 0;
    }
    if ( m_fOK && (0 == m_hHash) ) {
        m_fOK= CryptCreateHash( m_hProv, CALG_MD5, 0, 0, &m_hHash );
    }
    if ( m_fOK ) {
        if ( 0 == szKey ) {  // request to use default rawKey
            char szTmp[100];
            strcpy_s( szTmp, sizeof(szTmp), m_pszDefaultKeyRaw );
            if ( szSalt ) {
                strncat_s( szTmp, sizeof(szTmp), szSalt, 5 ); // use part of salt
            }
            // minor security tweak -- scramble the key+salt
            int nLen= strlen(szTmp)-1;  
            for ( int j=0; j< nLen; j++ ) {
                char c= szTmp[nLen-j];
                szTmp[nLen-j]= (char)(szTmp[j]+5);
                szTmp[j]= c;
            }
            szKey= &szTmp[4]; // discard the first part, for fun
        }
        m_fOK= CryptHashData( m_hHash, (BYTE*)szKey, strlen(szKey), 0);
    }
    if ( m_fOK ) {
        m_fOK= CryptDeriveKey( m_hProv, CALG_RC4, m_hHash, CRYPT_EXPORTABLE, &m_hKey);
    }
    if ( !m_fOK ) { 
        m_nLastErr= GetLastError(); 
        m_sErrMsg= "Error creating encryption key";
    }
    return( m_fOK );
}
//--- workhorse function:  Encrypt or decrypt "in place"
BOOL CCrypt::EncryptDecrypt( BYTE* pData, DWORD* dwDataLen, LPCSTR pKey, BOOL fEncrypt )
{
    m_fOK= TRUE;
    SetKey( (LPCSTR)pKey );   
    if ( fEncrypt ) {
           m_fOK= CryptEncrypt( m_hKey, 0, TRUE, 0, pData, dwDataLen, *dwDataLen );
    }
    else  {
        m_fOK= CryptDecrypt( m_hKey, 0, TRUE, 0, pData, dwDataLen );
    }
    return( m_fOK );
}

string CCrypt::EncryptStrToHex( LPCSTR szText )
{
    m_fOK= TRUE;
    string sRet= "";
    DWORD nDataLen= strlen( szText );
    LPCSTR pszKey{}, pszSalt{};
    m_fOK= SetKey( (LPCSTR)pszKey, pszSalt );   
    
    if ( m_fOK ) {
        char* pTmp= new char[nDataLen+1]() ;
        strncpy_s( pTmp, nDataLen+1, szText, nDataLen+1 );
        m_fOK= CryptEncrypt( m_hKey, 0, TRUE, 0, (BYTE*)pTmp, &nDataLen, nDataLen );
        if (m_fOK ) {
            sRet= EncodeToHex( (BYTE*)pTmp, nDataLen );
        }
        delete[] pTmp;
    }
    return( sRet );
}

string CCrypt::EncryptStrToHex( LPCSTR szText, LPCSTR pszKey, LPCSTR pszSalt )
{
    m_fOK= TRUE;
    string sRet= "";
    DWORD nDataLen= strlen( szText );
    if ( pszSalt || pszKey || (0 == m_hKey) ) {
        m_fOK= SetKey( (LPCSTR)pszKey, pszSalt );   
    }
    if ( m_fOK ) {
        char* pTmp= new char[nDataLen+1] ;
        strncpy_s( pTmp, nDataLen+1, szText, nDataLen+1 );
        m_fOK= CryptEncrypt( m_hKey, 0, TRUE, 0, (BYTE*)pTmp, &nDataLen, nDataLen );
        if (m_fOK ) {
            sRet= EncodeToHex( (BYTE*)pTmp, nDataLen );
        }
        delete pTmp;
    }
    return( sRet );
}

string CCrypt::DecryptStrFromHex( LPCSTR szHex, LPCSTR pszKey/*=0*/, LPCSTR pszSalt/*= 0*/ )
{
    m_fOK= TRUE;
    string sRet= "";
    DWORD nDataLen= strlen( szHex );

    if ( pszSalt || pszKey || (0 == m_hKey) ) {
        m_fOK= SetKey( (LPCSTR)pszKey, pszSalt );   
    }
    if ( m_fOK ) {
        DWORD nDecryptLen= nDataLen/2;
        char* pTmp= new char[ nDecryptLen+1 ];
        DecodeFromHex( szHex, (BYTE*)pTmp, nDecryptLen );
        m_fOK= CryptDecrypt( m_hKey, 0, TRUE, 0, (BYTE*)pTmp, &nDecryptLen );
        if ( m_fOK ) {
            sRet= pTmp;
        }
        delete pTmp;
    }
    return( sRet );
}

//--------------------------------------------------------
// inefficient but requires no explanation :-)
string CCrypt::EncodeToHex( BYTE* p, int nLen )
{
    //string sRet, sTmp;
    string sRet;
    ostringstream sTmp;
    for( int j=0; j< nLen; j++ ) 
        {
        //sTmp.format( "%02x", p[j] );
        sTmp<< std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(p[j]);
        
        }
    sRet.assign(sTmp.str());
    return (sRet );
}

//---------------------------------------------------------
// returns length of decoded hex buffer
int CCrypt::DecodeFromHex( LPCSTR pSrc, BYTE* pDest, int nBufLen )
{
    int nRet= 0;
    int nLen= strlen(pSrc);
    *pDest = 0;
    BYTE cIn1, cIn2, nFinal;
    for( int j=0; j< nLen; j += 2 ) {
        cIn1= (BYTE)toupper(*pSrc++);  cIn1 -= '0'; if ( cIn1>9 ) cIn1 -= 7;
        cIn2= (BYTE)toupper(*pSrc++);  cIn2 -= '0'; if ( cIn2>9 ) cIn2 -= 7;
        nFinal= (BYTE)((cIn1 << 4) | cIn2); 
        if (nFinal>255) nFinal=0; // in case trying to decode non-hex data
        *pDest++ = nFinal; 
        *pDest = 0;
        if ( nRet >= nBufLen ) {
            break;
        }
        nRet++;
    }
    return( nRet );
}






Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.