/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ #include "stdafx.h" #include "MyCrypto.h" #include using namespace std; ///////////////////////////////////////////////////////////////////////////// // Static constants. const DWORD MyCrypto::MYCRYPTO_BLOCK_SIZE(8UL); const DWORD MyCrypto::MYCRYPTO_CHUNK_SIZE(1016UL); const DWORD MyCrypto::MYCRYPTO_BUFFER_SIZE(MYCRYPTO_CHUNK_SIZE + MYCRYPTO_BLOCK_SIZE); const DWORD MyCrypto::DIGITS_PER_ENCRYPTED_BYTE(3L); ///////////////////////////////////////////////////////////////////////////// // MyCrypto // Constructor. MyCrypto::MyCrypto() : m_hCryptProv(NULL) , m_hKey(NULL) , m_hHash(NULL) , m_Mode(MyCrypto::Unknown) { TRACE(" MyCrypto constructed.\n"); } // Destructor. /* virtual */ MyCrypto::~MyCrypto() { // Destroy the session key. if (! m_hKey || CryptDestroyKey(m_hKey)) { TRACE(" CryptDestroyKey() ok for key <%d>.\n", m_hKey); m_hKey = NULL; } else { TRACE(" CryptDestroyKey() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptDestroyKey() failed!"); } // Destroy the hash object. if (! m_hHash || CryptDestroyHash(m_hHash)) { TRACE(" CryptDestroyHash() ok for handle <%d>.\n", m_hHash); m_hHash = NULL; } else { TRACE(" CryptDestroyHash() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptDestroyHash() failed!"); } // Release the provider handle. if (! m_hCryptProv || CryptReleaseContext(m_hCryptProv, /* dwFlags - reserved */ 0)) { TRACE(" CryptReleaseContext() ok for handle <%d>.\n", m_hCryptProv); m_hCryptProv = NULL; } else { TRACE(" CryptReleaseContext() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptReleaseContext() failed!"); } } // Must be called before any other method. bool MyCrypto::Initialize(IN const PBYTE pbyPassword, IN DWORD dwLenPassword, IN LPCTSTR lpctstrProviderName /* = NULL */ ) { _ASSERTE(! m_hCryptProv); _ASSERTE(! m_hKey); _ASSERTE(! m_hHash); _ASSERTE(0 == MYCRYPTO_CHUNK_SIZE % MYCRYPTO_BLOCK_SIZE && "Must be a multiple"); _ASSERTE(1024 == MYCRYPTO_BUFFER_SIZE && "Did this deliberately for word alignment speed"); bool bReturn(false); // Connect to the Microsoft Base Crypto Service Provider (CSP). if (CryptAcquireContext( &m_hCryptProv, // Returned CSP handle. NULL, // Not used for Base CSP. lpctstrProviderName, // Provider Name for this Provider Type. PROV_RSA_FULL, // Provider Type. CRYPT_VERIFYCONTEXT)) { // Don't need access to private keys. TRACE(" CryptAcquireContext() ok with handle <%d>.\n", m_hCryptProv); // Create an empty hash object. if (CryptCreateHash( m_hCryptProv, // CSP handle. CALG_MD5, // Specify hashing algorithm. 0, // Hash key is zero for MD5. 0, // Reserved - must be zero. &m_hHash)) { // Return hash handle. TRACE(" CryptCreateHash() ok with handle <%d>.\n", m_hHash); // Hash the password string. if (CryptHashData( m_hHash, // Hash handle. pbyPassword, // Data to hash. dwLenPassword, // Bytes of data. 0)) { // No user-supplied data. TRACE(" CryptHashData() ok.\n"); // Create a session key based on the hash of the password. if (CryptDeriveKey( m_hCryptProv, // CSP handle. CALG_RC2, // Symmetric encryption algorithm. m_hHash, // Hash handle. CRYPT_CREATE_SALT, // Options. &m_hKey)) { // Returned key. TRACE(" CryptDeriveKey() ok with key <%d>.\n", m_hKey); // Test that the block size is that we think it is. DWORD dwBlockLenInBits(0); DWORD dwBlockLenSize(sizeof(dwBlockLenInBits)); if (CryptGetKeyParam(m_hKey, KP_BLOCKLEN, reinterpret_cast (&dwBlockLenInBits), &dwBlockLenSize, 0)) { if (8UL * MYCRYPTO_BLOCK_SIZE == dwBlockLenInBits) { bReturn = true; } else { TRACE(" CryptGetKeyParam() returned bad blocklen: <%d>\n", dwBlockLenInBits); _ASSERTE(! "CryptGetKeyParam() returned bad blocklen!"); } } else { TRACE(" CryptGetKeyParam() failed with error <%d>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptGetKeyParam() failed!"); } } else { TRACE(" CryptDeriveKey() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptDeriveKey() failed!"); } } else { TRACE(" CryptHashData() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptHashData() failed!"); } } else { TRACE(" CryptCreateHash() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptCreateHash() failed!"); } } else { TRACE(" CryptAcquireContext() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptAcquireContext() failed!"); } return bReturn; } // Must be called before any other method. This will call the other Initialize() // overload. bool MyCrypto::Initialize(IN const CString& sPassword, IN LPCTSTR lpctstrProviderName /* = NULL */ ) { _ASSERTE(! sPassword.IsEmpty() && "Password cannot be empty!"); bool bReturn(false); // Convert CString password to byte array. CString sPasswordLocal(sPassword); DWORD dwPasswordBufferSize(sPasswordLocal.GetLength()); PBYTE pbyPassword = new BYTE[dwPasswordBufferSize]; memcpy(pbyPassword, sPasswordLocal.GetBuffer(dwPasswordBufferSize), dwPasswordBufferSize); sPasswordLocal.ReleaseBuffer(); // Call other overload and return the value it sets. bReturn = Initialize(pbyPassword, dwPasswordBufferSize, lpctstrProviderName); // Cleanup. delete [] pbyPassword; pbyPassword = NULL; return bReturn; } // The caller needs to know how long to make the buffer. "The ciphertext is up // to a 'block length' larger than the plaintext." Note that you can call this // before calling Initialize(), or before this class is even instantiated. /* static */ DWORD MyCrypto::GetRequiredCryptBufferSize(IN DWORD dwLenPlain) { _ASSERTE(0 < dwLenPlain); return dwLenPlain + MYCRYPTO_BLOCK_SIZE; //?? % } // Encrypt any length of text. This will call EncryptOneChunk() repeatedly. bool MyCrypto::Encrypt(IN const PBYTE pbyPlain, IN DWORD dwLenPlain, IN PBYTE pbyCrypt, OUT DWORD& dwLenCrypt) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(pbyPlain && "Must send an allocated array!"); _ASSERTE(0 < dwLenPlain && "Must send some bytes to encrypt!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(0 == dwLenCrypt && "This is an out parameter!"); _ASSERTE(Decrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(true); for (DWORD dwPlainOffset = 0; dwPlainOffset < dwLenPlain; dwPlainOffset += MyCrypto::MYCRYPTO_CHUNK_SIZE) { // Determine how big this chunk is, and if it's the last one. bool bFinalChunk(dwPlainOffset + MyCrypto::MYCRYPTO_CHUNK_SIZE > dwLenPlain); DWORD dwThisChunkSize(bFinalChunk ? dwLenPlain % MyCrypto::MYCRYPTO_CHUNK_SIZE : MyCrypto::MYCRYPTO_CHUNK_SIZE); dwThisChunkSize = (dwThisChunkSize ? dwThisChunkSize : MyCrypto::MYCRYPTO_CHUNK_SIZE); // Do one chunk at a time. DWORD dwCryptReturnedLen(0); if (EncryptOneChunk(&pbyPlain[dwPlainOffset], dwThisChunkSize, &pbyCrypt[dwLenCrypt], dwCryptReturnedLen, bFinalChunk)) { dwLenCrypt += dwCryptReturnedLen; } else { _ASSERTE(! "MyCrypto::EncryptOneChunk() failed!"); bReturn = false; break; } } TRACE(" Encrypted <%d> bytes of plaintext into <%d> bytes.\n", dwLenPlain, dwLenCrypt); return bReturn; } // Encrypt any length of text. This will call the other Encrypt() overload. bool MyCrypto::Encrypt(IN const CString& sPlain, IN PBYTE pbyCrypt, OUT DWORD& dwLenCrypt) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(! sPlain.IsEmpty() && "Must send a value for plaintext!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(0 == dwLenCrypt && "This is an out parameter!"); _ASSERTE(Decrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(false); // Convert CString plaintext to byte array. CString sPlainLocal(sPlain); DWORD dwPlainBufferSize(sPlainLocal.GetLength()); PBYTE pbyPlain = new BYTE[dwPlainBufferSize]; memcpy(pbyPlain, sPlainLocal.GetBuffer(dwPlainBufferSize), dwPlainBufferSize); sPlainLocal.ReleaseBuffer(); // Call other overload and return the value it sets. bReturn = Encrypt(pbyPlain, dwPlainBufferSize, pbyCrypt, dwLenCrypt); // Cleanup. delete [] pbyPlain; pbyPlain = NULL; return bReturn; } // Encrypt any length of text into decimal numerals (for example, // "138161118171153219". This will call the other Encrypt() overload. bool MyCrypto::Encrypt(IN const CString& sPlain, OUT CString& sCrypt) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(! sPlain.IsEmpty() && "Must send a value for encrypt!"); _ASSERTE(sCrypt.IsEmpty() && "This is an out parameter!"); _ASSERTE(Decrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(false); // Allocate buffer for crypt text. DWORD dwCryptBufferSize( MyCrypto::GetRequiredCryptBufferSize(sPlain.GetLength())); PBYTE pbyCrypt = new BYTE[dwCryptBufferSize]; memset(pbyCrypt, 0, dwCryptBufferSize); // Call the other overload. DWORD dwCryptReturnedLen(0); bReturn = Encrypt(sPlain, pbyCrypt, dwCryptReturnedLen); if (bReturn) { // Convert the encrypted byte array to decimal numerals. Avoiding // CString::operator+=() this way is 5 times faster. DWORD dwLenCrypt((dwCryptReturnedLen * DIGITS_PER_ENCRYPTED_BYTE) + 1); LPTSTR pltstrCrypt = sCrypt.GetBufferSetLength(dwLenCrypt); for (DWORD dwByte = 0; dwByte < dwCryptReturnedLen; ++dwByte) { DWORD dwOffset(dwByte * DIGITS_PER_ENCRYPTED_BYTE); sprintf(&pltstrCrypt[dwOffset], "%0*d", DIGITS_PER_ENCRYPTED_BYTE, pbyCrypt[dwByte]); } sCrypt.ReleaseBuffer(); } else { sCrypt.Empty(); } // Clean up. delete [] pbyCrypt; pbyCrypt = NULL; return bReturn; } // Encrypt one chunk of data. bool MyCrypto::EncryptOneChunk(IN const PBYTE pbyPlain, IN DWORD dwLenPlain, IN PBYTE pbyCrypt, OUT DWORD& dwLenCrypt, IN bool bFinalChunk) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(pbyPlain && "Must send an allocated array!"); _ASSERTE(MYCRYPTO_CHUNK_SIZE >= dwLenPlain && "Sent plaintext is too long - split it up!"); _ASSERTE(0 < dwLenPlain && "Must send some bytes to encrypt!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(0 == dwLenCrypt && "This is an out parameter!"); _ASSERTE(Decrypting != m_Mode && "Mustn't do both on same context handle!"); // When a block cipher is used, this data length must be a multiple of the // block size unless this is the final section of data to be decrypted and // the Final parameter is TRUE. _ASSERTE(bFinalChunk || 0 == dwLenPlain % MYCRYPTO_BLOCK_SIZE); bool bReturn(false); // The MS CryptEncrypt() call will overwrite memory if you tell it to! if (MYCRYPTO_CHUNK_SIZE >= dwLenPlain) { if (pbyPlain && pbyCrypt) { BYTE byData[MYCRYPTO_BUFFER_SIZE]; memcpy(byData, pbyPlain, MYCRYPTO_CHUNK_SIZE); m_Mode = Encrypting; dwLenCrypt = dwLenPlain; if (CryptEncrypt( m_hKey, // Key handle. 0, // No hashing *while* encrypting. bFinalChunk, // Is this the last call? 0, // Reserved - must be zero. byData, // In = Plaintext data. // Out = Encrypted data. &dwLenCrypt, // In = bytes to encrypt. // Out = encrypted bytes. MYCRYPTO_BUFFER_SIZE)) { // Size of the 'byData' buffer. TRACE(" CryptEncrypt() ok with <%d> encrypted bytes.\n", dwLenCrypt); // For debugging use - very tedious (encrypting). #if 0 for (int nX = 0; nX < dwLenCrypt; ++nX) { TRACE(" [%d]: %x (%c).\n", nX, byData[nX], byData[nX]); Sleep(10); } #endif // "If the key is a block cipher key, the data is padded to a // multiple of the block size of the cipher (when bFinal is // TRUE)." _ASSERTE(! bFinalChunk || 0 == dwLenCrypt % MYCRYPTO_BLOCK_SIZE); memcpy(pbyCrypt, byData, dwLenCrypt); bReturn = true; } else { TRACE(" CryptEncrypt() failed with error <%x>: <%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptEncrypt() failed!"); } } else { TRACE(" Null pointer for variable(s): <%s>\n", "pbyPlain && pbyCrypt"); _ASSERTE(! "Null pointer!"); } } else { TRACE(" The plaintext is too long at <%d> bytes; must be <= " "MYCRYPTO_CHUNK_SIZE (<%d>) bytes. Overwrite avoided.\n", dwLenPlain, MYCRYPTO_CHUNK_SIZE); _ASSERTE(! "Plaintext too long! Overwrite avoided!"); } return bReturn; } // Decrypt any length of text. This will call DecryptOneChunk() repeatedly. bool MyCrypto::Decrypt(IN const PBYTE pbyCrypt, IN DWORD dwLenCrypt, IN PBYTE pbyPlain, OUT DWORD& dwLenPlain) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(pbyPlain && "Must send an allocated array!"); _ASSERTE(0 < dwLenCrypt && "Must send some bytes to decrypt!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(0 == dwLenPlain && "This is an out parameter!"); _ASSERTE(Encrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(true); dwLenPlain = 0; for (DWORD dwCryptOffset = 0; dwCryptOffset < dwLenCrypt; dwCryptOffset += MyCrypto::MYCRYPTO_CHUNK_SIZE) { DWORD dwPlainReturnedLen(0); bool bFinalChunk(dwCryptOffset + MyCrypto::MYCRYPTO_CHUNK_SIZE > dwLenCrypt); DWORD dwThisChunkSize(bFinalChunk ? dwLenCrypt % MyCrypto::MYCRYPTO_CHUNK_SIZE : MyCrypto::MYCRYPTO_CHUNK_SIZE); dwThisChunkSize = (dwThisChunkSize ? dwThisChunkSize : MyCrypto::MYCRYPTO_CHUNK_SIZE); if (DecryptOneChunk(&pbyCrypt[dwCryptOffset], dwThisChunkSize, &pbyPlain[dwLenPlain], dwPlainReturnedLen, bFinalChunk)) { dwLenPlain += dwPlainReturnedLen; } else { _ASSERTE(! "MyCrypto::DecryptOneChunk() failed!"); bReturn = false; break; } } TRACE(" Decrypted <%d> bytes of encrypted text into <%d> bytes.\n", dwLenCrypt, dwLenPlain); return bReturn; } // Decrypt any length of text. This will call the other Decrypt() overload. bool MyCrypto::Decrypt(IN const PBYTE pbyCrypt, IN DWORD dwLenCrypt, OUT CString& sPlain) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(0 < dwLenCrypt && "Must send some bytes to decrypt!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(sPlain.IsEmpty() && "This is an out parameter!"); _ASSERTE(Encrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(false); // Allocate buffer for plain text. We can safely say that the plaintext // can be no larger than the encrypted text. DWORD dwLenPlain(0); PBYTE pbyPlain = new BYTE[dwLenCrypt]; memset(pbyPlain, 0, dwLenCrypt); // Call other overload and return the value it sets. bReturn = Decrypt(pbyCrypt, dwLenCrypt, pbyPlain, dwLenPlain); if (bReturn) { lstrcpyn(sPlain.GetBufferSetLength(dwLenPlain + 1), reinterpret_cast (pbyPlain), dwLenPlain + 1); sPlain.ReleaseBuffer(); } else { sPlain.Empty(); } // Cleanup. delete [] pbyPlain; pbyPlain = NULL; return bReturn; } // Decrypt any length of text from decimal numerals (for example, // "138161118171153219". This will call the other Decrypt() overload. bool MyCrypto::Decrypt(IN const CString& sCrypt, OUT CString& sPlain) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(! sCrypt.IsEmpty() && "Must send a value to decrypt!"); _ASSERTE(sPlain.IsEmpty() && "This is an out parameter!"); _ASSERTE(Encrypting != m_Mode && "Mustn't do both on same context handle!"); bool bReturn(false); // Allocate buffer for crypt text. DWORD dwCryptBufferSize(sCrypt.GetLength() / DIGITS_PER_ENCRYPTED_BYTE); PBYTE pbyCrypt = new BYTE[dwCryptBufferSize]; memset(pbyCrypt, 0, dwCryptBufferSize); // Convert numeric crypt text to byte array. CString sByte; DWORD dwByteIndex(0); for (DWORD dwPos = 0; dwPos < static_cast (sCrypt.GetLength()); dwPos += DIGITS_PER_ENCRYPTED_BYTE) { sByte = sCrypt.Mid(dwPos, DIGITS_PER_ENCRYPTED_BYTE); pbyCrypt[dwByteIndex] = atoi(sByte); ++dwByteIndex; } _ASSERTE(dwByteIndex == dwCryptBufferSize && "Whole 'pbyCrypt' array was not populated!"); // Call the other overload. bReturn = Decrypt(pbyCrypt, dwCryptBufferSize, sPlain); // Clean up. delete [] pbyCrypt; pbyCrypt = NULL; return bReturn; } // Decrypt one chunk of data. bool MyCrypto::DecryptOneChunk(IN const PBYTE pbyCrypt, IN DWORD dwLenCrypt, IN PBYTE pbyPlain, OUT DWORD& dwLenPlain, IN bool bFinalChunk) { _ASSERTE(m_hCryptProv && "Must call Initialize() first!"); _ASSERTE(m_hKey && "Must call Initialize() first!"); _ASSERTE(m_hHash && "Must call Initialize() first!"); _ASSERTE(pbyCrypt && "Must send an allocated array!"); _ASSERTE(MYCRYPTO_BUFFER_SIZE >= dwLenCrypt); _ASSERTE(0 < dwLenCrypt && "Must send some bytes to decrypt!"); _ASSERTE(pbyPlain && "Must send an allocated array!"); _ASSERTE(0 == dwLenPlain && "This is an out parameter"); _ASSERTE(Encrypting != m_Mode && "Mustn't do both on same context handle!"); // When a block cipher is used, this data length must be a multiple of the // block size unless this is the final section of data to be decrypted and // the Final parameter is TRUE. _ASSERTE(bFinalChunk || 0 == dwLenCrypt % MYCRYPTO_BLOCK_SIZE); bool bReturn(false); if (pbyCrypt && pbyPlain) { BYTE byData[MYCRYPTO_BUFFER_SIZE]; memcpy(byData, pbyCrypt, dwLenCrypt); m_Mode = Decrypting; dwLenPlain = dwLenCrypt; if (CryptDecrypt( m_hKey, // Key handle. 0, // No hashing *while* encrypting. bFinalChunk, // Is this the last call? 0, // Reserved - must be zero. byData, // In = Encrypted data. // Out = Plaintext data. &dwLenPlain)) { // In = bytes to decrypt. // Out = Plaintext bytes. TRACE(" CryptDecrypt() ok with %d decrypted bytes.\n", dwLenPlain); // For debugging use - very tedious (decrypting). #if 0 for (int nX = 0; nX < dwLenPlain; ++nX) { TRACE(" [%d]: %x (%c).\n", nX, byData[nX], byData[nX]); Sleep(10); } #endif memcpy(pbyPlain, byData, dwLenPlain); bReturn = true; } else { TRACE(" CryptDecrypt() failed with error <%x> (suspect bad key): " "<%s>\n", GetLastError(), GetSysErrorString()); _ASSERTE(! "CryptDecrypt() failed!"); } } else { TRACE(" Null pointer for variable(s): <%s>\n", "pbyPlain && pbyCrypt"); _ASSERTE(! "Null pointer!"); } return bReturn; } // Format the system error number returned by ::GetLastError() to a string. CString MyCrypto::GetSysErrorString(IN int nErrSent /* = 0 */ ) { static string str; // Use sent value if one was sent. if (0 == nErrSent) { nErrSent = GetLastError(); } // There is an error number still. if (nErrSent) { LPVOID lpMsgBuf = NULL; if (::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, // Flags. NULL, // Message source. nErrSent, // Message id. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language. (LPTSTR) &lpMsgBuf, // Buffer to receive. 0, // Max size of buffer. NULL // Argument list. )) { // Remove goofy final characters. str = reinterpret_cast (lpMsgBuf); str = str.substr(0, str.length() - 2); } else { TRACE(" \n::FormatMessage() failed with error <%d>\n.", nErrSent); } VERIFY(NULL == ::LocalFree(lpMsgBuf)); } else { // No error number. We must not return the default string, "The // operation completed successfully.", because that confuses the user. // Calling CString::LoadString() from here always fails with errno 1814: // "The specified resource name cannot be found in the image file." } return str.data(); }