//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //============================================================================= // Note: not using precompiled headers. The crypto++ headers create gunk that anyone including them // has to link to, so they can't be included in the global project file. They also contain // string functions which are deprecated by the global project file so they can't be included after, either. // So we can't use the global precompiled header, need to manually include the things we need. #include "winlite.h" #ifdef POSIX #include #include #endif /* this stuff needs to be before the crypto headers, as it relies on memdbg, which doesn't do the right thing in the face of xdebug getting included below */ // tier0 //#include "tier0/tier0.h" #include "tier0/basetypes.h" #include "tier0/vprof.h" //#include "constants.h" #include "vstdlib/vstdlib.h" #include "strtools.h" //#include "version.h" //#include "globals.h" //#include "ivalidate.h" #include "tier1/utlvector.h" #include "simplebitstring.h" #include "tier1/checksum_sha1.h" #include "tier0/memdbgon.h" #include "tier0/tslist.h" #include "tier0/memdbgoff.h" #ifdef ENABLE_OPENSSLCONNECTION #define USE_OPENSSL_AES_DECRYPT 1 #endif #ifdef USE_OPENSSL_AES_DECRYPT // openssl optimized AES routines #include "openssl/aes.h" #if defined(_M_IX86) || defined (_M_X64) || defined(__i386__) || defined(__x86_64__) #include #endif #endif // crypto ++ #include "tier0/valve_off.h" #include "../external/crypto++-5.6.3/cryptopushdisablewarnings.h" #if _MSC_VER < 1400 // doesn't work with vc8, things below need xdebug #define _XDEBUG_ // keep crypto++-5.2 from including xdebug // these are defined in xdebug and used in some subsequent headers, define them to be our version #define _NEW_CRT new #define _DELETE_CRT(_P) delete (_P) #define _DELETE_CRT_VEC(_P) delete[] (_P) #define _STRING_CRT string #endif #define CRYPTOPP_DLL #undef min #undef max #undef Verify #define VPROF_BUDGETGROUP_ENCRYPTION _T("Encryption") #define SPEW_CRYPTO "crypto" const int k_cMedBuff = 1024; // medium buffer #if defined(GNUC) #pragma GCC diagnostic ignored "-Wshadow" #endif #include "../external/crypto++-5.6.3/cryptlib.h" #include "../external/crypto++-5.6.3/osrng.h" #include "../external/crypto++-5.6.3/crc.h" #include "../external/crypto++-5.6.3/modes.h" #include "../external/crypto++-5.6.3/files.h" #include "../external/crypto++-5.6.3/hex.h" #include "../external/crypto++-5.6.3/base64.h" #include "../external/crypto++-5.6.3/base32.h" #include "../external/crypto++-5.6.3/words.h" #include "../external/crypto++-5.6.3/rsa.h" #include "../external/crypto++-5.6.3/aes.h" #include "../external/crypto++-5.6.3/hmac.h" #include "../external/crypto++-5.6.3/zlib.h" #include "../external/crypto++-5.6.3/gzip.h" #include "../external/crypto++-5.6.3/pwdbased.h" using namespace CryptoPP; typedef AutoSeededX917RNG CAutoSeededRNG; #include "../external/crypto++-5.6.3/cryptopopdisablewarnings.h" #if defined(GNUC) #pragma GCC diagnostic warning "-Wshadow" #endif #include "tier0/memdbgon.h" #include "tier0/valve_on.h" #include "crypto.h" #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) // list of auto-seeded RNG pointers // these are very expensive to construct, so it makes sense to cache them CTSList g_tslistPAutoSeededRNG; // to avoid deconstructor order issuses we allow to manually free the list void FreeListRNG() { g_tslistPAutoSeededRNG.Purge(); } //----------------------------------------------------------------------------- // Purpose: thread-safe access to a pool of cryptoPP random number generators //----------------------------------------------------------------------------- class CPoolAllocatedRNG { public: CPoolAllocatedRNG() { m_pRNGNode = g_tslistPAutoSeededRNG.Pop(); if ( !m_pRNGNode ) { m_pRNGNode = new CTSList::Node_t; } } ~CPoolAllocatedRNG() { g_tslistPAutoSeededRNG.Push( m_pRNGNode ); } CAutoSeededRNG &GetRNG() { return m_pRNGNode->elem; } private: CTSList::Node_t *m_pRNGNode; }; // force run this static construction code class CGlobalInitConstructor { public: CGlobalInitConstructor() { // we have to use this function once since the underlying static constructor // is not thread safe. See use of MicrosoftCryptoProvider in Crypto++ CAutoSeededRNG rng; rng.GenerateByte(); } }; volatile static CGlobalInitConstructor s_StaticCryptoConstructor; //----------------------------------------------------------------------------- // Purpose: Encrypts the specified data with the specified key. Uses AES (Rijndael) symmetric // encryption. The encrypted data may then be decrypted by calling SymmetricDecrypt // with the same key. // Input: pubPlaintextData - Data to be encrypted // cubPlaintextData - Size of data to be encrypted // pIV - Pointer to initialization vector // cubIV - Size of initialization vector // pubEncryptedData - Pointer to buffer to receive encrypted data // pcubEncryptedData - Pointer to a variable that at time of call contains the size of // the receive buffer for encrypted data. When the method returns, this will contain // the actual size of the encrypted data. // pubKey - the key to encrypt the data with // cubKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if encryption failed //----------------------------------------------------------------------------- bool CCrypto::SymmetricEncryptWithIV( const uint8 *pubPlaintextData, const uint32 cubPlaintextData, const uint8 *pIV, const uint32 cubIV, uint8 *pubEncryptedData, uint32 *pcubEncryptedData, const uint8 *pubKey, const uint32 cubKey ) { VPROF_BUDGET( "CCrypto::SymmetricEncrypt", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubPlaintextData ); Assert( cubPlaintextData ); Assert( pubEncryptedData ); Assert( pcubEncryptedData ); Assert( *pcubEncryptedData ); Assert( pubKey ); Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen bool bRet = false; uint32 cubEncryptedData = *pcubEncryptedData; // remember how big the caller's buffer is bool bUseTempBuffer = false; uint8 *pTemp = pubEncryptedData; // // Crypto++ does not play well with overlapping buffers. If the buffers are // overlapping, then allocate some temp space to use for the encryption. // // It does work fine with _identical_ buffers. // if ( ( pubEncryptedData + cubEncryptedData >= pubPlaintextData ) && ( pubPlaintextData + cubPlaintextData >= pubEncryptedData ) ) { pTemp = new uint8[cubEncryptedData]; bUseTempBuffer = true; } try // handle any exceptions crypto++ may throw { if ( pTemp != NULL ) { AESEncryption aesEncrypt( pubKey, cubKey ); byte rgubIVEncrypted[k_cMedBuff]; Assert( Q_ARRAYSIZE( rgubIVEncrypted ) >= aesEncrypt.BlockSize() ); Assert( pIV != NULL && cubIV >= aesEncrypt.BlockSize() ); ArraySink * pOutputSink = new ArraySink( pTemp, *pcubEncryptedData ); // encrypt the initial vector with the key aesEncrypt.ProcessBlock( pIV, rgubIVEncrypted ); // store the encrypted IV in the output - the recipient will need it pOutputSink->Put( rgubIVEncrypted, aesEncrypt.BlockSize() ); // encrypt the message, given the key & IV CBC_Mode_ExternalCipher::Encryption cipher( aesEncrypt, pIV ); // Note: StreamTransformationFilter now owns the pointer to pOutputSink and will // free it when the filter goes out of scope and destructs StreamTransformationFilter filter( cipher, pOutputSink ); filter.Put( (byte *) pubPlaintextData, cubPlaintextData ); filter.MessageEnd(); // return length of encrypted data to caller *pcubEncryptedData = pOutputSink->TotalPutLength(); // CryptoPP may leave garbage hanging around in the caller's buffer past the stated output length. // Just to be safe, zero out caller's buffer from end out output to end of max buffer if ( bUseTempBuffer ) { Q_memcpy( pubEncryptedData, pTemp, *pcubEncryptedData ); } Q_memset( pubEncryptedData + *pcubEncryptedData, 0, (cubEncryptedData - *pcubEncryptedData ) ); bRet = true; } } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::SymmetricEncrypt: crypto++ threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } if ( bUseTempBuffer ) { delete[] pTemp; } return bRet; } //----------------------------------------------------------------------------- // Purpose: Encrypts the specified data with the specified key. Uses AES (Rijndael) symmetric // encryption. The encrypted data may then be decrypted by calling SymmetricDecrypt // with the same key. Generates a random initialization vector of the // appropriate size. // Input: pubPlaintextData - Data to be encrypted // cubPlaintextData - Size of data to be encrypted // pubEncryptedData - Pointer to buffer to receive encrypted data // pcubEncryptedData - Pointer to a variable that at time of call contains the size of // the receive buffer for encrypted data. When the method returns, this will contain // the actual size of the encrypted data. // pubKey - the key to encrypt the data with // cubKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if encryption failed //----------------------------------------------------------------------------- bool CCrypto::SymmetricEncrypt( const uint8 *pubPlaintextData, const uint32 cubPlaintextData, uint8 *pubEncryptedData, uint32 *pcubEncryptedData, const uint8 *pubKey, const uint32 cubKey ) { bool bRet = false; // // Generate a random IV // AESEncryption aesEncrypt( pubKey, cubKey ); byte rgubIV[k_cMedBuff]; CPoolAllocatedRNG rng; rng.GetRNG().GenerateBlock( rgubIV, aesEncrypt.BlockSize() ); bRet = SymmetricEncryptWithIV( pubPlaintextData, cubPlaintextData, rgubIV, aesEncrypt.BlockSize(), pubEncryptedData, pcubEncryptedData, pubKey, cubKey ); return bRet; } #ifdef USE_OPENSSL_AES_DECRYPT // Local helper to perform AES+CBC decryption using optimized OpenSSL AES routines static bool BDecryptAESUsingOpenSSL( const uint8 *pubEncryptedData, uint32 cubEncryptedData, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, AES_KEY *key, const uint8 *pIV ) { COMPILE_TIME_ASSERT( k_nSymmetricBlockSize == 16 ); // Block cipher encrypted text must be a multiple of the block size if ( cubEncryptedData % k_nSymmetricBlockSize != 0 ) return false; // Enough input? Requirement is one padded final block if ( cubEncryptedData < k_nSymmetricBlockSize ) return false; // Enough output space for all the full non-final blocks? if ( *pcubPlaintextData < cubEncryptedData - k_nSymmetricBlockSize ) return false; uint8 rgubWorking[k_nSymmetricBlockSize]; uint32 nDecrypted = 0; // Process non-final blocks #if defined(_M_IX86) || defined (_M_X64) || defined(__i386__) || defined(__x86_64__) // ... believe it or not, Steam client on Windows supports Athlon XP without SSE2 if ( !IsWindows() || GetCPUInformation().m_bSSE2 ) { while ( nDecrypted < cubEncryptedData - k_nSymmetricBlockSize ) { AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key ); __m128i m128Temp = _mm_xor_si128( _mm_loadu_si128( (__m128i*)pIV ), _mm_loadu_si128( (__m128i*)rgubWorking ) ); pIV = pubEncryptedData + nDecrypted; nDecrypted += k_nSymmetricBlockSize; _mm_storeu_si128( (__m128i* RESTRICT)( pubPlaintextData + nDecrypted - k_nSymmetricBlockSize ), m128Temp ); } } else #endif { while ( nDecrypted < cubEncryptedData - k_nSymmetricBlockSize ) { AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key ); for ( int i = 0; i < k_nSymmetricBlockSize; ++i ) pubPlaintextData[nDecrypted + i] = rgubWorking[i] ^ pIV[i]; pIV = pubEncryptedData + nDecrypted; nDecrypted += k_nSymmetricBlockSize; } } // Process final block into rgubWorking for padding inspection Assert( nDecrypted == cubEncryptedData - k_nSymmetricBlockSize ); AES_decrypt( pubEncryptedData + nDecrypted, rgubWorking, key ); for ( int i = 0; i < k_nSymmetricBlockSize; ++i ) rgubWorking[i] ^= pIV[i]; // Get final block padding length and make sure it is backfilled properly (PKCS#5) uint8 pad = rgubWorking[ k_nSymmetricBlockSize - 1 ]; if ( pad < 1 || pad > k_nSymmetricBlockSize ) return false; for ( int i = k_nSymmetricBlockSize - pad; i < k_nSymmetricBlockSize; ++i ) if ( rgubWorking[i] != pad ) return false; // Check that we have enough space for final bytes if ( *pcubPlaintextData < nDecrypted + k_nSymmetricBlockSize - pad ) return false; // Write any non-pad bytes from rgubWorking to pubPlaintextData for ( int i = 0; i < k_nSymmetricBlockSize - pad; ++i ) pubPlaintextData[nDecrypted++] = rgubWorking[i]; // The old CryptoPP path zeros out the entire destination buffer, but that // behavior isn't documented or even expected. We'll just zero out one byte // in case anyone relies on string termination, but that zero isn't counted. if ( *pcubPlaintextData > nDecrypted ) pubPlaintextData[nDecrypted] = 0; *pcubPlaintextData = nDecrypted; return true; } #else /* function, not method */ static bool SymmetricDecryptWorker( const uint8 *pubEncryptedData, uint32 cubEncryptedData, const uint8 * pIV, uint32 cubIV, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, AESDecryption &aesDecrypt ) { VPROF_BUDGET( "CCrypto::SymmetricDecrypt", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubEncryptedData ); Assert( cubEncryptedData); Assert( pIV ); Assert( cubIV ); Assert( pubPlaintextData ); Assert( pcubPlaintextData ); Assert( *pcubPlaintextData ); bool bRet = false; uint32 cubPlaintextData = *pcubPlaintextData; // remember how big the caller's buffer is bool bUseTempBuffer = false; uint8* pTemp = pubPlaintextData; // // Crypto++ does not play nice with decrypting in place. If the buffers are // overlapping, then allocate some temp space to use for the decryption. // // It does work fine with _identical_ buffers, but due to the way we store // the IV in the returned encrypted data we never actually hit that case. // if ( ( pubEncryptedData + cubEncryptedData >= pubPlaintextData ) && ( pubPlaintextData + cubPlaintextData >= pubEncryptedData ) ) { pTemp = new uint8[cubPlaintextData]; bUseTempBuffer = true; } try // handle any exceptions crypto++ may throw { if ( pTemp != NULL ) { CryptoPP::ArraySink* pOutputSink = new CryptoPP::ArraySink( pTemp, *pcubPlaintextData ); CryptoPP::CBC_Mode_ExternalCipher::Decryption cbc( aesDecrypt, pIV ); // Note: StreamTransformationFilter now owns the pointer to pOutputSink and will // free it when the filter goes out of scope and destructs CryptoPP::StreamTransformationFilter padding( cbc, pOutputSink ); padding.Put( pubEncryptedData, cubEncryptedData ); padding.MessageEnd(); // return length of decrypted data to caller *pcubPlaintextData = pOutputSink->TotalPutLength(); // CryptoPP may leave garbage hanging around in the caller's buffer past the stated output length. // Just to be safe, zero out caller's buffer from end out output to end of max buffer if ( bUseTempBuffer ) { Q_memcpy( pubPlaintextData, pTemp, *pcubPlaintextData ); } Q_memset( pubPlaintextData + *pcubPlaintextData, 0, (cubPlaintextData - *pcubPlaintextData ) ); bRet = true; } } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 4, "CCrypto::SymmetricDecrypt: crypto++ threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } if ( bUseTempBuffer ) { delete[] pTemp; } return bRet; } #endif //----------------------------------------------------------------------------- // Purpose: Decrypts the specified data with the specified key. Uses AES (Rijndael) symmetric // decryption. // Input: pubEncryptedData - Data to be decrypted // cubEncryptedData - Size of data to be decrypted // pubPlaintextData - Pointer to buffer to receive decrypted data // pcubPlaintextData - Pointer to a variable that at time of call contains the size of // the receive buffer for decrypted data. When the method returns, this will contain // the actual size of the decrypted data. // pubKey - the key to decrypt the data with // cubKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if decryption failed //----------------------------------------------------------------------------- bool CCrypto::SymmetricDecrypt( const uint8 *pubEncryptedData, uint32 cubEncryptedData, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, const uint8 *pubKey, const uint32 cubKey ) { Assert( pubEncryptedData ); Assert( cubEncryptedData); Assert( pubPlaintextData ); Assert( pcubPlaintextData ); Assert( *pcubPlaintextData ); Assert( pubKey ); Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen // the initialization vector (IV) must be stored in the first block of bytes. // If the size of encrypted data is not at least the block size, it is not valid if ( cubEncryptedData < k_nSymmetricBlockSize ) return false; #ifdef USE_OPENSSL_AES_DECRYPT AES_KEY key; if ( AES_set_decrypt_key( pubKey, cubKey * 8, &key ) < 0 ) return false; // Our first block is straight AES block encryption of IV with user key, no XOR. uint8 rgubIV[ k_nSymmetricBlockSize ]; AES_decrypt( pubEncryptedData, rgubIV, &key ); pubEncryptedData += k_nSymmetricBlockSize; cubEncryptedData -= k_nSymmetricBlockSize; return BDecryptAESUsingOpenSSL( pubEncryptedData, cubEncryptedData, pubPlaintextData, pcubPlaintextData, &key, rgubIV ); #else AESDecryption aesDecrypt( pubKey, cubKey ); Assert( k_nSymmetricBlockSize == aesDecrypt.BlockSize() ); // Decrypt the IV byte rgubIV[k_cMedBuff]; Assert( Q_ARRAYSIZE( rgubIV ) >= aesDecrypt.BlockSize() ); aesDecrypt.ProcessBlock( pubEncryptedData, rgubIV ); // We have now consumed the IV, so remove it from the front of the message pubEncryptedData += k_nSymmetricBlockSize; cubEncryptedData -= k_nSymmetricBlockSize; // given the IV stored in the message, and the key, decrypt the message return SymmetricDecryptWorker( pubEncryptedData, cubEncryptedData, rgubIV, aesDecrypt.BlockSize(), pubPlaintextData, pcubPlaintextData, aesDecrypt ); #endif } //----------------------------------------------------------------------------- // Purpose: Decrypts the specified data with the specified key. Uses AES (Rijndael) symmetric // decryption. // Input: pubEncryptedData - Data to be decrypted // cubEncryptedData - Size of data to be decrypted // pIV - Initialization vector. Byte array one block in size. // cubIV - size of IV. This should be 16 (one block, 128 bits) // pubPlaintextData - Pointer to buffer to receive decrypted data // pcubPlaintextData - Pointer to a variable that at time of call contains the size of // the receive buffer for decrypted data. When the method returns, this will contain // the actual size of the decrypted data. // pubKey - the key to decrypt the data with // cubKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if decryption failed //----------------------------------------------------------------------------- bool CCrypto::SymmetricDecryptWithIV( const uint8 *pubEncryptedData, uint32 cubEncryptedData, const uint8 * pIV, uint32 cubIV, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, const uint8 *pubKey, const uint32 cubKey ) { Assert( pubEncryptedData ); Assert( cubEncryptedData); Assert( pIV ); Assert( cubIV ); Assert( pubPlaintextData ); Assert( pcubPlaintextData ); Assert( *pcubPlaintextData ); Assert( pubKey ); Assert( k_nSymmetricKeyLen == cubKey ); // the only key length supported is k_nSymmetricKeyLen // IV input into CBC must be exactly one block size if ( cubIV != k_nSymmetricBlockSize ) return false; #ifdef USE_OPENSSL_AES_DECRYPT AES_KEY key; if ( AES_set_decrypt_key( pubKey, cubKey * 8, &key ) < 0 ) return false; return BDecryptAESUsingOpenSSL( pubEncryptedData, cubEncryptedData, pubPlaintextData, pcubPlaintextData, &key, pIV ); #else AESDecryption aesDecrypt( pubKey, cubKey ); Assert( k_nSymmetricBlockSize == aesDecrypt.BlockSize() ); return SymmetricDecryptWorker( pubEncryptedData, cubEncryptedData, pIV, cubIV, pubPlaintextData, pcubPlaintextData, aesDecrypt ); #endif } //----------------------------------------------------------------------------- // Purpose: For specified plaintext data size, returns what size of symmetric // encrypted data will be //----------------------------------------------------------------------------- uint32 CCrypto::GetSymmetricEncryptedSize( uint32 cubPlaintextData ) { // empirically determined encrypted size as function of plaintext size for AES encryption uint k_cubBlock = 16; uint k_cubHeader = 16; return k_cubHeader + ( ( cubPlaintextData / k_cubBlock ) * k_cubBlock ) + k_cubBlock; } //----------------------------------------------------------------------------- // Purpose: Encrypts the specified data with the specified text password. // Uses the SHA256 hash of the password as the key for AES (Rijndael) symmetric // encryption. A SHA1 HMAC of the result is appended, for authentication on // the receiving end. // The encrypted data may then be decrypted by calling DecryptWithPasswordAndAuthenticate // with the same password. // Input: pubPlaintextData - Data to be encrypted // cubPlaintextData - Size of data to be encrypted // pubEncryptedData - Pointer to buffer to receive encrypted data // pcubEncryptedData - Pointer to a variable that at time of call contains the size of // the receive buffer for encrypted data. When the method returns, this will contain // the actual size of the encrypted data. // pchPassword - text password // Output: true if successful, false if encryption failed //----------------------------------------------------------------------------- bool CCrypto::EncryptWithPasswordAndHMAC( const uint8 *pubPlaintextData, uint32 cubPlaintextData, uint8 * pubEncryptedData, uint32 * pcubEncryptedData, const char *pchPassword ) { // // Generate a random IV // byte rgubIV[k_nSymmetricBlockSize]; CPoolAllocatedRNG rng; rng.GetRNG().GenerateBlock( rgubIV, k_nSymmetricBlockSize ); return EncryptWithPasswordAndHMACWithIV( pubPlaintextData, cubPlaintextData, rgubIV, k_nSymmetricBlockSize, pubEncryptedData, pcubEncryptedData, pchPassword ); } //----------------------------------------------------------------------------- // Purpose: Encrypts the specified data with the specified text password. // Uses the SHA256 hash of the password as the key for AES (Rijndael) symmetric // encryption. A SHA1 HMAC of the result is appended, for authentication on // the receiving end. // The encrypted data may then be decrypted by calling DecryptWithPasswordAndAuthenticate // with the same password. // Input: pubPlaintextData - Data to be encrypted // cubPlaintextData - Size of data to be encrypted // pIV - IV to use for AES encryption. Should be random and never used before unless you know // exactly what you're doing. // cubIV - size of the IV - should be same ase the AES blocksize. // pubEncryptedData - Pointer to buffer to receive encrypted data // pcubEncryptedData - Pointer to a variable that at time of call contains the size of // the receive buffer for encrypted data. When the method returns, this will contain // the actual size of the encrypted data. // pchPassword - text password // Output: true if successful, false if encryption failed //----------------------------------------------------------------------------- bool CCrypto::EncryptWithPasswordAndHMACWithIV( const uint8 *pubPlaintextData, uint32 cubPlaintextData, const uint8 * pIV, uint32 cubIV, uint8 * pubEncryptedData, uint32 * pcubEncryptedData, const char *pchPassword ) { uint8 rgubKey[k_nSymmetricKeyLen]; if ( !pchPassword || !pchPassword[0] ) return false; if ( !cubPlaintextData ) return false; uint32 cubBuffer = *pcubEncryptedData; uint32 cubExpectedResult = GetSymmetricEncryptedSize( cubPlaintextData ) + sizeof( SHADigest_t ); if ( cubBuffer < cubExpectedResult ) return false; try { CryptoPP::SHA256().CalculateDigest( rgubKey, (const uint8 *)pchPassword, Q_strlen( pchPassword ) ); } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 4, "CCrypto::EncryptWithPassword: crypto++ threw exception %s (%d)\n", e.what(), e.GetErrorType() ); return false; } bool bRet = SymmetricEncryptWithIV( pubPlaintextData, cubPlaintextData, pIV, cubIV, pubEncryptedData, pcubEncryptedData, rgubKey, k_nSymmetricKeyLen ); if ( bRet ) { // calc HMAC uint32 cubEncrypted = *pcubEncryptedData; *pcubEncryptedData += sizeof( SHADigest_t ); if ( cubBuffer < *pcubEncryptedData ) return false; SHADigest_t *pHMAC = (SHADigest_t*)( pubEncryptedData + cubEncrypted ); bRet = CCrypto::GenerateHMAC( pubEncryptedData, cubEncrypted, rgubKey, k_nSymmetricKeyLen, pHMAC ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Decrypts the specified data with the specified password. Uses AES (Rijndael) symmetric // decryption. First, the HMAC is verified - if it is not correct, then we know that // the key is incorrect or the data is corrupted, and the decryption fails. // Input: pubEncryptedData - Data to be decrypted // cubEncryptedData - Size of data to be decrypted // pubPlaintextData - Pointer to buffer to receive decrypted data // pcubPlaintextData - Pointer to a variable that at time of call contains the size of // the receive buffer for decrypted data. When the method returns, this will contain // the actual size of the decrypted data. // pchPassword - the text password to decrypt the data with // Output: true if successful, false if decryption failed //----------------------------------------------------------------------------- bool CCrypto::DecryptWithPasswordAndAuthenticate( const uint8 * pubEncryptedData, uint32 cubEncryptedData, uint8 * pubPlaintextData, uint32 * pcubPlaintextData, const char *pchPassword ) { uint8 rgubKey[k_nSymmetricKeyLen]; if ( !pchPassword || !pchPassword[0] ) return false; if ( cubEncryptedData <= sizeof( SHADigest_t ) ) return false; try { CryptoPP::SHA256().CalculateDigest( rgubKey, (const uint8 *)pchPassword, Q_strlen( pchPassword ) ); } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 4, "CCrypto::EncryptWithPassword: crypto++ threw exception %s (%d)\n", e.what(), e.GetErrorType() ); return false; } uint32 cubCiphertext = cubEncryptedData - sizeof( SHADigest_t ); SHADigest_t *pHMAC = (SHADigest_t*)( pubEncryptedData + cubCiphertext ); SHADigest_t hmacActual; bool bRet = CCrypto::GenerateHMAC( pubEncryptedData, cubCiphertext, rgubKey, k_nSymmetricKeyLen, &hmacActual ); if ( bRet ) { // invalid ciphertext or key if ( Q_memcmp( &hmacActual, pHMAC, sizeof( SHADigest_t ) ) ) return false; bRet = SymmetricDecrypt( pubEncryptedData, cubCiphertext, pubPlaintextData, pcubPlaintextData, rgubKey, k_nSymmetricKeyLen ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Generates a new pair of private/public RSA keys // Input: pubPublicKey - Pointer to buffer to receive public key (should be of size k_nRSAKeyLenMax) // pcubPublicKey - Pointer to variable that contains size of pubPublicKey buffer. At exit, // this is filled in with the actual size of the public key // pubPrivateKey - Pointer to buffer to receive private key (should be of size k_nRSAKeyLenMax) // pcubPrivateKey - Pointer to variable that contains size of pubPrivateKey buffer. At exit, // this is filled in with the actual size of the private key // Output: true if successful, false if key generation failed //----------------------------------------------------------------------------- bool CCrypto::RSAGenerateKeys( uint8 *pubPublicKey, uint32 *pcubPublicKey, uint8 *pubPrivateKey, uint32 *pcubPrivateKey ) { VPROF_BUDGET( "CCrypto::RSAGenerateKeys", VPROF_BUDGETGROUP_ENCRYPTION ); bool bRet = false; Assert( pubPublicKey ); Assert( pcubPublicKey ); Assert( pubPrivateKey ); Assert( pcubPrivateKey ); try // handle any exceptions crypto++ may throw { // generate private key ArraySink arraySinkPrivateKey( pubPrivateKey, *pcubPrivateKey ); CPoolAllocatedRNG rng; RSAES_OAEP_SHA_Decryptor priv( rng.GetRNG(), k_nRSAKeyBits ); priv.DEREncode( arraySinkPrivateKey ); *pcubPrivateKey = arraySinkPrivateKey.TotalPutLength(); // generate public key ArraySink arraySinkPublicKey( pubPublicKey, *pcubPublicKey ); RSAES_OAEP_SHA_Encryptor pub(priv); pub.DEREncode( arraySinkPublicKey ); *pcubPublicKey = arraySinkPublicKey.TotalPutLength(); bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAGenerateKeys: crypto++ threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Encrypts the specified data with the specified RSA public key. // The encrypted data may then be decrypted by calling RSADecrypt with the // corresponding RSA private key. // Input: pubPlaintextData - Data to be encrypted // cubPlaintextData - Size of data to be encrypted // pubEncryptedData - Pointer to buffer to receive encrypted data // pcubEncryptedData - Pointer to a variable that at time of call contains the size of // the receive buffer for encrypted data. When the method returns, this will contain // the actual size of the encrypted data. // pubPublicKey - the RSA public key to encrypt the data with // cubPublicKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if encryption failed //----------------------------------------------------------------------------- bool CCrypto::RSAEncrypt( const uint8 *pubPlaintextData, uint32 cubPlaintextData, uint8 *pubEncryptedData, uint32 *pcubEncryptedData, const uint8 *pubPublicKey, const uint32 cubPublicKey ) { VPROF_BUDGET( "CCrypto::RSAEncrypt", VPROF_BUDGETGROUP_ENCRYPTION ); bool bRet = false; Assert( cubPlaintextData > 0 ); // must pass in some data try // handle any exceptions crypto++ may throw { StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true ); RSAES_OAEP_SHA_Encryptor rsaEncryptor( stringSourcePublicKey ); // calculate how many blocks of encryption will we need to do AssertFatal( rsaEncryptor.FixedMaxPlaintextLength() <= ULONG_MAX ); uint32 cBlocks = 1 + ( ( cubPlaintextData - 1 ) / (uint32)rsaEncryptor.FixedMaxPlaintextLength() ); // calculate how big the output will be AssertFatal( rsaEncryptor.FixedCiphertextLength() <= ULONG_MAX / cBlocks ); uint32 cubCipherText = cBlocks * (uint32)rsaEncryptor.FixedCiphertextLength(); Assert( cubCipherText > 0 ); // ensure there is sufficient room in output buffer for result if ( cubCipherText > ( *pcubEncryptedData ) ) { AssertMsg2( false, "CCrypto::RSAEncrypt: insufficient output buffer for encryption, needed %d got %d\n", cubCipherText, *pcubEncryptedData ); return false; } // encrypt the message, using as many blocks as required CPoolAllocatedRNG rng; for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ ) { // encrypt either all remaining plaintext, or maximum allowed plaintext per RSA encryption operation uint32 cubToEncrypt = min( cubPlaintextData, (uint32)rsaEncryptor.FixedMaxPlaintextLength() ); // encrypt the plaintext rsaEncryptor.Encrypt( rng.GetRNG(), pubPlaintextData, cubToEncrypt, pubEncryptedData ); // adjust input and output pointers and remaining plaintext byte count pubPlaintextData += cubToEncrypt; cubPlaintextData -= cubToEncrypt; pubEncryptedData += rsaEncryptor.FixedCiphertextLength(); } Assert( 0 == cubPlaintextData ); // should have no remaining plaintext to encrypt *pcubEncryptedData = cubCipherText; bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAEncrypt: Encrypt() threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Decrypts the specified data with the specified RSA private key // Input: pubEncryptedData - Data to be decrypted // cubEncryptedData - Size of data to be decrypted // pubPlaintextData - Pointer to buffer to receive decrypted data // pcubPlaintextData - Pointer to a variable that at time of call contains the size of // the receive buffer for decrypted data. When the method returns, this will contain // the actual size of the decrypted data. // pubPrivateKey - the RSA private key key to decrypt the data with // cubPrivateKey - Size of the key (must be k_nSymmetricKeyLen) // Output: true if successful, false if decryption failed //----------------------------------------------------------------------------- bool CCrypto::RSADecrypt( const uint8 *pubEncryptedData, uint32 cubEncryptedData, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, const uint8 *pubPrivateKey, const uint32 cubPrivateKey ) { VPROF_BUDGET( "CCrypto::RSADecrypt", VPROF_BUDGETGROUP_ENCRYPTION ); bool bRet = false; Assert( cubEncryptedData > 0 ); // must pass in some data try // handle any exceptions crypto++ may throw { StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true ); RSAES_OAEP_SHA_Decryptor rsaDecryptor( stringSourcePrivateKey ); // calculate how many blocks of decryption will we need to do AssertFatal( rsaDecryptor.FixedCiphertextLength() <= ULONG_MAX ); uint32 cubFixedCiphertextLength = (uint32)rsaDecryptor.FixedCiphertextLength(); // Ensure encrypted data is valid and has length that is exact multiple of 128 bytes if ( 0 != ( cubEncryptedData % cubFixedCiphertextLength ) ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: invalid ciphertext length %d, needs to be a multiple of %d\n", cubEncryptedData, cubFixedCiphertextLength ); return false; } uint32 cBlocks = cubEncryptedData / cubFixedCiphertextLength; // calculate how big the maximum output will be size_t cubMaxPlaintext = rsaDecryptor.MaxPlaintextLength( rsaDecryptor.FixedCiphertextLength() ); AssertFatal( cubMaxPlaintext <= ULONG_MAX / cBlocks ); uint32 cubPlaintextDataMax = cBlocks * (uint32)cubMaxPlaintext; Assert( cubPlaintextDataMax > 0 ); // ensure there is sufficient room in output buffer for result if ( cubPlaintextDataMax >= ( *pcubPlaintextData ) ) { AssertMsg2( false, "CCrypto::RSADecrypt: insufficient output buffer for decryption, needed %d got %d\n", cubPlaintextDataMax, *pcubPlaintextData ); return false; } // decrypt the data, using as many blocks as required CPoolAllocatedRNG rng; uint32 cubPlaintextData = 0; for ( uint32 nBlock = 0; nBlock < cBlocks; nBlock++ ) { // decrypt one block (always of fixed size) int cubToDecrypt = cubFixedCiphertextLength; DecodingResult decodingResult = rsaDecryptor.Decrypt( rng.GetRNG(), pubEncryptedData, cubToDecrypt, pubPlaintextData ); if ( !decodingResult.isValidCoding ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: failed to decrypt\n" ); return false; } // adjust input and output pointers and remaining encrypted byte count pubEncryptedData += cubToDecrypt; cubEncryptedData -= cubToDecrypt; pubPlaintextData += decodingResult.messageLength; AssertFatal( decodingResult.messageLength <= ULONG_MAX ); cubPlaintextData += (uint32)decodingResult.messageLength; } Assert( 0 == cubEncryptedData ); // should have no remaining encrypted data to decrypt *pcubPlaintextData = cubPlaintextData; bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSADecrypt: Decrypt() threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Decrypts the specified data with the specified RSA PUBLIC key, // using no padding (eg un-padded signature). // Input: pubEncryptedData - Data to be decrypted // cubEncryptedData - Size of data to be decrypted // pubPlaintextData - Pointer to buffer to receive decrypted data // pcubPlaintextData - Pointer to a variable that at time of call contains the size of // the receive buffer for decrypted data. When the method returns, this will contain // the actual size of the decrypted data. // pubPublicKey - the RSA public key key to decrypt the data with // cubPublicKey - Size of the key // Output: true if successful, false if decryption failed //----------------------------------------------------------------------------- bool CCrypto::RSAPublicDecrypt_NoPadding( const uint8 *pubEncryptedData, uint32 cubEncryptedData, uint8 *pubPlaintextData, uint32 *pcubPlaintextData, const uint8 *pubPublicKey, const uint32 cubPublicKey ) { VPROF_BUDGET( "CCrypto::RSADecrypt", VPROF_BUDGETGROUP_ENCRYPTION ); bool bRet = false; Assert( cubEncryptedData > 0 ); // must pass in some data // BUGBUG taylor // This probably only works for reasonably small ciphertext sizes. try // handle any exceptions crypto++-5.2 may throw { StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true ); // 1. We need to use a Verifier because a Decryptor expects a private key, // which is encoded differently // 2. We are using neither PKCS1v15 padding nor SHA in any way, we are simply // using this object as a means of instantiating the key decryption function RSASSA_PKCS1v15_SHA_Verifier pub( stringSourcePublicKey ); // Ask for the data to be decrypted // Caveat: this may "succeed" even if the ciphertext is bogus, so it // is up to the caller to do any MAC or other sanity checking Integer x = pub.AccessKey().ApplyFunction(Integer(pubEncryptedData, cubEncryptedData)); // Result is an 'Integer', essentially a string of bytes for our // purposes though. uint32 nBytes = x.ByteCount(); if ( nBytes > *pcubPlaintextData ) { return false; } // don't tell it to encode to the full buffer size, because it will // pre-pad with zeros. Just squeeze it in to the first nBytes of the // buffer. x.Encode( pubPlaintextData, nBytes ); *pcubPlaintextData = nBytes; bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSAPublicDecrypt_NoPadding: Decrypt() threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Generates an RSA signature block for the specified data with the specified // RSA private key. The signature can be verified by calling RSAVerifySignature // with the RSA public key. // Input: pubData - Data to be signed // cubData - Size of data to be signed // pubSignature - Pointer to buffer to receive signature block // pcubSignature - Pointer to a variable that at time of call contains the size of // the pubSignature buffer. When the method returns, this will contain // the actual size of the signature block // pubPrivateKey - The RSA private key to use to sign the data // cubPrivateKey - Size of the key // Output: true if successful, false if signature failed //----------------------------------------------------------------------------- bool CCrypto::RSASign( const uint8 *pubData, const uint32 cubData, uint8 *pubSignature, uint32 *pcubSignature, const uint8 *pubPrivateKey, const uint32 cubPrivateKey ) { VPROF_BUDGET( "CCrypto::RSASign", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( pubPrivateKey ); Assert( cubPrivateKey > 0 ); Assert( pubSignature ); Assert( pcubSignature ); bool bRet = false; try // handle any exceptions crypto++ may throw { StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true ); RSASSA_PKCS1v15_SHA_Signer rsaSigner( stringSourcePrivateKey ); CPoolAllocatedRNG rng; size_t len = rsaSigner.SignMessage( rng.GetRNG(), (byte *)pubData, cubData, pubSignature ); AssertFatal( len <= ULONG_MAX ); *pcubSignature = (uint32)len; bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: SignMessage threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Verifies that signature block is authentic for given data & RSA public key // Input: pubData - Data that was signed // cubData - Size of data that was signed signed // pubSignature - Signature block // cubSignature - Size of signature block // pubPublicKey - The RSA public key to use to verify the signature // (must be from same pair as RSA private key used to generate signature) // cubPublicKey - Size of the key // Output: true if successful and signature is authentic, false if signature does not match or other error //----------------------------------------------------------------------------- bool CCrypto::RSAVerifySignature( const uint8 *pubData, const uint32 cubData, const uint8 *pubSignature, const uint32 cubSignature, const uint8 *pubPublicKey, const uint32 cubPublicKey ) { VPROF_BUDGET( "CCrypto::RSAVerifySignature", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( pubSignature ); Assert( pubPublicKey ); bool bRet = false; try // handle any exceptions crypto++ may throw { StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true ); RSASSA_PKCS1v15_SHA_Verifier pub( stringSourcePublicKey ); bRet = pub.VerifyMessage( pubData, cubData, pubSignature, cubSignature ); } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: VerifyMessage threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Generates an RSA signature block for the specified data with the specified // RSA private key. The signature can be verified by calling RSAVerifySignature // with the RSA public key. // Input: pubData - Data to be signed // cubData - Size of data to be signed // pubSignature - Pointer to buffer to receive signature block // pcubSignature - Pointer to a variable that at time of call contains the size of // the pubSignature buffer. When the method returns, this will contain // the actual size of the signature block // pubPrivateKey - The RSA private key to use to sign the data // cubPrivateKey - Size of the key // Output: true if successful, false if signature failed //----------------------------------------------------------------------------- bool CCrypto::RSASignSHA256( const uint8 *pubData, const uint32 cubData, uint8 *pubSignature, uint32 *pcubSignature, const uint8 *pubPrivateKey, const uint32 cubPrivateKey ) { VPROF_BUDGET( "CCrypto::RSASign", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( pubPrivateKey ); Assert( cubPrivateKey > 0 ); Assert( pubSignature ); Assert( pcubSignature ); bool bRet = false; try // handle any exceptions crypto++ may throw { StringSource stringSourcePrivateKey( pubPrivateKey, cubPrivateKey, true ); RSASS::Signer rsaSigner( stringSourcePrivateKey ); CPoolAllocatedRNG rng; size_t len = rsaSigner.SignMessage( rng.GetRNG(), (byte *)pubData, cubData, pubSignature ); AssertFatal( len <= ULONG_MAX ); *pcubSignature = (uint32)len; bRet = true; } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: SignMessage threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Verifies that signature block is authentic for given data & RSA public key // Input: pubData - Data that was signed // cubData - Size of data that was signed signed // pubSignature - Signature block // cubSignature - Size of signature block // pubPublicKey - The RSA public key to use to verify the signature // (must be from same pair as RSA private key used to generate signature) // cubPublicKey - Size of the key // Output: true if successful and signature is authentic, false if signature does not match or other error //----------------------------------------------------------------------------- bool CCrypto::RSAVerifySignatureSHA256( const uint8 *pubData, const uint32 cubData, const uint8 *pubSignature, const uint32 cubSignature, const uint8 *pubPublicKey, const uint32 cubPublicKey ) { VPROF_BUDGET( "CCrypto::RSAVerifySignature", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( pubSignature ); Assert( pubPublicKey ); bool bRet = false; try // handle any exceptions crypto++ may throw { StringSource stringSourcePublicKey( pubPublicKey, cubPublicKey, true ); RSASS::Verifier pub( stringSourcePublicKey ); bRet = pub.VerifyMessage( pubData, cubData, pubSignature, cubSignature ); } catch ( Exception e ) { DMsg( SPEW_CRYPTO, 2, "CCrypto::RSASign: VerifyMessage threw exception %s (%d)\n", e.what(), e.GetErrorType() ); } return bRet; } //----------------------------------------------------------------------------- // Purpose: Hex-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Input: pubData - Data to encode // cubData - Size of data to encode // pchEncodedData - Pointer to string buffer to store output in // cchEncodedData - Size of pchEncodedData buffer //----------------------------------------------------------------------------- bool CCrypto::HexEncode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData ) { VPROF_BUDGET( "CCrypto::HexEncode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( cubData ); Assert( pchEncodedData ); Assert( cchEncodedData > 0 ); if ( cchEncodedData < ( ( cubData * 2 ) + 1 ) ) { Assert( cchEncodedData >= ( cubData * 2 ) + 1 ); // expands to 2x input + NULL, must have room in output buffer *pchEncodedData = '\0'; return false; } ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData ); // Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs HexEncoder hexEncoder( pArraySinkOutput ); hexEncoder.Put( pubData, cubData ); hexEncoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); if ( len >= cchEncodedData ) { AssertMsg2( false, "CCrypto::HexEncode: insufficient output buffer for encoding, needed %d got %d\n", len, cchEncodedData ); return false; } pchEncodedData[len] = 0; // NULL-terminate return true; } //----------------------------------------------------------------------------- // Purpose: Hex-decodes a block of data. (Text -> binary representation.) // Input: pchData - Null-terminated hex-encoded string // pubDecodedData - Pointer to buffer to store output in // pcubDecodedData - Pointer to variable that contains size of // output buffer. At exit, is filled in with actual size // of decoded data. //----------------------------------------------------------------------------- bool CCrypto::HexDecode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData ) { VPROF_BUDGET( "CCrypto::HexDecode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pchData ); Assert( pubDecodedData ); Assert( pcubDecodedData ); Assert( *pcubDecodedData ); ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData ); // Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs HexDecoder hexDecoder( pArraySinkOutput ); hexDecoder.Put( (byte *) pchData, Q_strlen( pchData ) ); hexDecoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); if ( len > *pcubDecodedData ) { AssertMsg2( false, "CCrypto::HexDecode: insufficient output buffer for decoding, needed %d got %d\n", len, *pcubDecodedData ); return false; } *pcubDecodedData = len; return true; } static const int k_LineBreakEveryNGroups = 18; // line break every 18 groups of 4 characters (every 72 characters) //----------------------------------------------------------------------------- // Purpose: Returns the expected buffer size that should be passed to Base64Encode. // Input: cubData - Size of data to encode // bInsertLineBreaks - If line breaks should be inserted automatically //----------------------------------------------------------------------------- uint32 CCrypto::Base64EncodeMaxOutput( const uint32 cubData, const char *pszLineBreak ) { // terminating null + 4 chars per 3-byte group + line break after every 18 groups (72 output chars) + final line break uint32 nGroups = (cubData+2)/3; str_size cchRequired = 1 + nGroups*4 + ( pszLineBreak ? Q_strlen(pszLineBreak)*(1+(nGroups-1)/k_LineBreakEveryNGroups) : 0 ); return cchRequired; } //----------------------------------------------------------------------------- // Purpose: Base64-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Input: pubData - Data to encode // cubData - Size of data to encode // pchEncodedData - Pointer to string buffer to store output in // cchEncodedData - Size of pchEncodedData buffer // bInsertLineBreaks - If "\n" line breaks should be inserted automatically //----------------------------------------------------------------------------- bool CCrypto::Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32 cchEncodedData, bool bInsertLineBreaks ) { const char *pszLineBreak = bInsertLineBreaks ? "\n" : NULL; uint32 cchRequired = Base64EncodeMaxOutput( cubData, pszLineBreak ); (void)cchRequired; AssertMsg2( cchEncodedData >= cchRequired, "CCrypto::Base64Encode: insufficient output buffer for encoding, needed %d got %d\n", cchRequired, cchEncodedData ); return Base64Encode( pubData, cubData, pchEncodedData, &cchEncodedData, pszLineBreak ); } //----------------------------------------------------------------------------- // Purpose: Base64-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Input: pubData - Data to encode // cubData - Size of data to encode // pchEncodedData - Pointer to string buffer to store output in // pcchEncodedData - Pointer to size of pchEncodedData buffer; adjusted to number of characters written (before NULL) // pszLineBreak - String to be inserted every 72 characters; empty string or NULL pointer for no line breaks // Note: if pchEncodedData is NULL and *pcchEncodedData is zero, *pcchEncodedData is filled with the actual required length // for output. A simpler approximation for maximum output size is (cubData * 4 / 3) + 5 if there are no linebreaks. //----------------------------------------------------------------------------- bool CCrypto::Base64Encode( const uint8 *pubData, uint32 cubData, char *pchEncodedData, uint32* pcchEncodedData, const char *pszLineBreak ) { VPROF_BUDGET( "CCrypto::Base64Encode", VPROF_BUDGETGROUP_ENCRYPTION ); if ( pchEncodedData == NULL ) { AssertMsg( *pcchEncodedData == 0, "NULL output buffer with non-zero size passed to Base64Encode" ); *pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak ); return true; } const uint8 *pubDataEnd = pubData + cubData; char *pchEncodedDataStart = pchEncodedData; str_size unLineBreakLen = pszLineBreak ? Q_strlen( pszLineBreak ) : 0; int nNextLineBreak = unLineBreakLen ? k_LineBreakEveryNGroups : INT_MAX; const char * const pszBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; uint32 cchEncodedData = *pcchEncodedData; if ( cchEncodedData == 0 ) goto out_of_space; --cchEncodedData; // pre-decrement for the terminating null so we don't forget about it // input 3 x 8-bit, output 4 x 6-bit while ( pubDataEnd - pubData >= 3 ) { if ( cchEncodedData < 4 + unLineBreakLen ) goto out_of_space; if ( nNextLineBreak == 0 ) { memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); pchEncodedData += unLineBreakLen; cchEncodedData -= unLineBreakLen; nNextLineBreak = k_LineBreakEveryNGroups; } uint32 un24BitsData; un24BitsData = (uint32) pubData[0] << 16; un24BitsData |= (uint32) pubData[1] << 8; un24BitsData |= (uint32) pubData[2]; pubData += 3; pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ]; pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ]; pchEncodedData[2] = pszBase64Chars[ (un24BitsData >> 6) & 63 ]; pchEncodedData[3] = pszBase64Chars[ (un24BitsData ) & 63 ]; pchEncodedData += 4; cchEncodedData -= 4; --nNextLineBreak; } // Clean up remaining 1 or 2 bytes of input, pad output with '=' if ( pubData != pubDataEnd ) { if ( cchEncodedData < 4 + unLineBreakLen ) goto out_of_space; if ( nNextLineBreak == 0 ) { memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); pchEncodedData += unLineBreakLen; cchEncodedData -= unLineBreakLen; } uint32 un24BitsData; un24BitsData = (uint32) pubData[0] << 16; if ( pubData+1 != pubDataEnd ) { un24BitsData |= (uint32) pubData[1] << 8; } pchEncodedData[0] = pszBase64Chars[ (un24BitsData >> 18) & 63 ]; pchEncodedData[1] = pszBase64Chars[ (un24BitsData >> 12) & 63 ]; pchEncodedData[2] = pubData+1 != pubDataEnd ? pszBase64Chars[ (un24BitsData >> 6) & 63 ] : '='; pchEncodedData[3] = '='; pchEncodedData += 4; cchEncodedData -= 4; } if ( unLineBreakLen ) { if ( cchEncodedData < unLineBreakLen ) goto out_of_space; memcpy( pchEncodedData, pszLineBreak, unLineBreakLen ); pchEncodedData += unLineBreakLen; cchEncodedData -= unLineBreakLen; } *pchEncodedData = 0; *pcchEncodedData = pchEncodedData - pchEncodedDataStart; return true; out_of_space: *pchEncodedData = 0; *pcchEncodedData = Base64EncodeMaxOutput( cubData, pszLineBreak ); AssertMsg( false, "CCrypto::Base64Encode: insufficient output buffer (up to n*4/3+5 bytes required, plus linebreaks)" ); return false; } //----------------------------------------------------------------------------- // Purpose: Base64-decodes a block of data. (Text -> binary representation.) // Input: pchData - Null-terminated hex-encoded string // pubDecodedData - Pointer to buffer to store output in // pcubDecodedData - Pointer to variable that contains size of // output buffer. At exit, is filled in with actual size // of decoded data. // Note: if NULL is passed as the output buffer and *pcubDecodedData is zero, the function // will calculate the actual required size and place it in *pcubDecodedData. A simpler upper // bound on the required size is ( strlen(pchData)*3/4 + 1 ). //----------------------------------------------------------------------------- bool CCrypto::Base64Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters ) { return Base64Decode( pchData, ~0u, pubDecodedData, pcubDecodedData, bIgnoreInvalidCharacters ); } //----------------------------------------------------------------------------- // Purpose: Base64-decodes a block of data. (Text -> binary representation.) // Input: pchData - base64-encoded string, null terminated // cchDataMax - maximum length of string unless a null is encountered first // pubDecodedData - Pointer to buffer to store output in // pcubDecodedData - Pointer to variable that contains size of // output buffer. At exit, is filled in with actual size // of decoded data. // Note: if NULL is passed as the output buffer and *pcubDecodedData is zero, the function // will calculate the actual required size and place it in *pcubDecodedData. A simpler upper // bound on the required size is ( strlen(pchData)*3/4 + 2 ). //----------------------------------------------------------------------------- bool CCrypto::Base64Decode( const char *pchData, uint32 cchDataMax, uint8 *pubDecodedData, uint32 *pcubDecodedData, bool bIgnoreInvalidCharacters ) { VPROF_BUDGET( "CCrypto::Base64Decode", VPROF_BUDGETGROUP_ENCRYPTION ); uint32 cubDecodedData = *pcubDecodedData; uint32 cubDecodedDataOrig = cubDecodedData; if ( pubDecodedData == NULL ) { AssertMsg( *pcubDecodedData == 0, "NULL output buffer with non-zero size passed to Base64Decode" ); cubDecodedDataOrig = cubDecodedData = ~0u; } // valid base64 character range: '+' (0x2B) to 'z' (0x7A) // table entries are 0-63, -1 for invalid entries, -2 for '=' static const char rgchInvBase64[] = { 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; COMPILE_TIME_ASSERT( Q_ARRAYSIZE(rgchInvBase64) == 0x7A - 0x2B + 1 ); uint32 un24BitsWithSentinel = 1; while ( cchDataMax-- > 0 ) { char c = *pchData++; if ( (uint8)(c - 0x2B) >= Q_ARRAYSIZE( rgchInvBase64 ) ) { if ( c == '\0' ) break; if ( !bIgnoreInvalidCharacters && !( c == '\r' || c == '\n' || c == '\t' || c == ' ' ) ) goto decode_failed; else continue; } c = rgchInvBase64[(uint8)(c - 0x2B)]; if ( c < 0 ) { if ( c == -2 ) // -2 -> terminating '=' break; if ( !bIgnoreInvalidCharacters ) goto decode_failed; else continue; } un24BitsWithSentinel <<= 6; un24BitsWithSentinel |= c; if ( un24BitsWithSentinel & (1<<24) ) { if ( cubDecodedData < 3 ) // out of space? go to final write logic break; if ( pubDecodedData ) { pubDecodedData[0] = (uint8)( un24BitsWithSentinel >> 16 ); pubDecodedData[1] = (uint8)( un24BitsWithSentinel >> 8); pubDecodedData[2] = (uint8)( un24BitsWithSentinel ); pubDecodedData += 3; } cubDecodedData -= 3; un24BitsWithSentinel = 1; } } // If un24BitsWithSentinel contains data, output the remaining full bytes if ( un24BitsWithSentinel >= (1<<6) ) { // Possibilities are 3, 2, 1, or 0 full output bytes. int nWriteBytes = 3; while ( un24BitsWithSentinel < (1<<24) ) { nWriteBytes--; un24BitsWithSentinel <<= 6; } // Write completed bytes to output while ( nWriteBytes-- > 0 ) { if ( cubDecodedData == 0 ) { AssertMsg( false, "CCrypto::Base64Decode: insufficient output buffer (up to n*3/4+2 bytes required)" ); goto decode_failed; } if ( pubDecodedData ) { *pubDecodedData++ = (uint8)(un24BitsWithSentinel >> 16); } --cubDecodedData; un24BitsWithSentinel <<= 8; } } *pcubDecodedData = cubDecodedDataOrig - cubDecodedData; return true; decode_failed: *pcubDecodedData = cubDecodedDataOrig - cubDecodedData; return false; } //----------------------------------------------------------------------------- // Purpose: Generate a SHA1 hash // Input: pchInput - Plaintext string of item to hash (null terminated) // pOutDigest - Pointer to receive hashed digest output //----------------------------------------------------------------------------- bool CCrypto::GenerateSHA1Digest( const uint8 *pubInput, const int cubInput, SHADigest_t *pOutDigest ) { VPROF_BUDGET( "CCrypto::GenerateSHA1Digest", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubInput ); Assert( cubInput > 0 ); Assert( pOutDigest ); bool bSuccess = true; try { CryptoPP::SHA().CalculateDigest( *pOutDigest, pubInput, cubInput ); } catch(...) { bSuccess = false; } return bSuccess; } //----------------------------------------------------------------------------- // Purpose: Generate a hash Salt - be careful, over-writing an existing salt // will render the hashed value unverifiable. //----------------------------------------------------------------------------- bool CCrypto::GenerateSalt( Salt_t *pSalt ) { VPROF_BUDGET( "CCrypto::GenerateSalt", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pSalt ); bool bSuccess = true; try { CPoolAllocatedRNG rng; rng.GetRNG().GenerateBlock( (byte*)pSalt, sizeof(Salt_t) ); } catch(...) { bSuccess = false; } return bSuccess; } //----------------------------------------------------------------------------- // Purpose: Generate a SHA1 hash using a salt. // Input: pchInput - Plaintext string of item to hash (null terminated) // pSalt - Salt // pOutDigest - Pointer to receive salted digest output //----------------------------------------------------------------------------- bool CCrypto::GenerateSaltedSHA1Digest( const char *pchInput, const Salt_t *pSalt, SHADigest_t *pOutDigest ) { VPROF_BUDGET( "CCrypto::GenerateSaltedSHA1Digest", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pchInput ); Assert( pSalt ); Assert( pOutDigest ); int iInputLen = Q_strlen( pchInput ); uint8 *pubSaltedInput = new uint8[ iInputLen + sizeof( Salt_t ) ]; // Insert half the salt before the input string and half at the end. // This is probably unnecessary (to split the salt) but we're stuck with it for historical reasons. uint8 *pubCursor = pubSaltedInput; Q_memcpy( pubCursor, (uint8 *)pSalt, sizeof(Salt_t) / 2 ); pubCursor += sizeof( Salt_t ) / 2; Q_memcpy( pubCursor, pchInput, iInputLen ); pubCursor += iInputLen; Q_memcpy( pubCursor, (uint8 *)pSalt + sizeof(Salt_t) / 2, sizeof(Salt_t) / 2 ); bool bSuccess = true; try { CryptoPP::SHA().CalculateDigest( *pOutDigest, pubSaltedInput, iInputLen + sizeof( Salt_t ) ); } catch(...) { bSuccess = false; } delete [] pubSaltedInput; return bSuccess; } //----------------------------------------------------------------------------- // Purpose: Generates a random block of data //----------------------------------------------------------------------------- bool CCrypto::GenerateRandomBlock( uint8 *pubDest, int cubDest ) { CPoolAllocatedRNG rng; rng.GetRNG().GenerateBlock( pubDest, cubDest ); return true; } //----------------------------------------------------------------------------- // Purpose: Generate a keyed-hash MAC using SHA1 // Input: pubData - Plaintext data to digest // cubData - length of data // pubKey - key to use in HMAC // cubKey - length of key // pOutDigest - Pointer to receive hashed digest output //----------------------------------------------------------------------------- bool CCrypto::GenerateHMAC( const uint8 *pubData, uint32 cubData, const uint8 *pubKey, uint32 cubKey, SHADigest_t *pOutputDigest ) { VPROF_BUDGET( "CCrypto::GenerateHMAC", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( cubData > 0 ); Assert( pubKey ); Assert( cubKey > 0 ); Assert( pOutputDigest ); bool bSuccess = true; try { CryptoPP::HMAC< CryptoPP::SHA1 > hmac( pubKey, cubKey ); hmac.CalculateDigest( *pOutputDigest, pubData, cubData ); } catch(...) { bSuccess = false; } return bSuccess; } //----------------------------------------------------------------------------- // Purpose: Generate a keyed-hash MAC using SHA-256 //----------------------------------------------------------------------------- bool CCrypto::GenerateHMAC256( const uint8 *pubData, uint32 cubData, const uint8 *pubKey, uint32 cubKey, SHA256Digest_t *pOutputDigest ) { VPROF_BUDGET( "CCrypto::GenerateHMAC256", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( cubData > 0 ); Assert( pubKey ); Assert( cubKey > 0 ); Assert( pOutputDigest ); bool bSuccess = true; try { CryptoPP::HMAC< CryptoPP::SHA256 > hmac( pubKey, cubKey ); hmac.CalculateDigest( *pOutputDigest, pubData, cubData ); } catch(...) { bSuccess = false; } return bSuccess; } bool CCrypto::BGzipBuffer( const uint8 *pubData, uint32 cubData, CCryptoOutBuffer &bufOutput ) { bool bSuccess = true; try { std::string gzip_output; StringSource( (byte *)pubData, cubData, true, new Gzip( new StringSink( gzip_output ) ) ); bufOutput.Set( (uint8*)gzip_output.c_str(), (uint32)gzip_output.length() ); } catch( ... ) { bSuccess = false; } return bSuccess; } bool CCrypto::BGunzipBuffer( const uint8 *pubData, uint32 cubData, CCryptoOutBuffer &bufOutput ) { bool bSuccess = true; try { std::string gunzip_output; StringSource( (byte *)pubData, cubData, true, new Gunzip( new StringSink( gunzip_output ) ) ); bufOutput.Set( (uint8*)gunzip_output.c_str(), (uint32)gunzip_output.length() ); } catch( ... ) { bSuccess = false; } return bSuccess; } //! These are all needed to get around stack-overflow bug in Initialize() class HexDecoderTKS : public HexDecoder { public: HexDecoderTKS(BufferedTransformation *attachment, const int *pnDecodingArray) : HexDecoder(attachment) { BaseN_Decoder::IsolatedInitialize( MakeParameters( Name::DecodingLookupArray(), pnDecodingArray )( Name::Log2Base(), 4 ) ); } }; class Base32DecoderTKS : public Base32Decoder { public: Base32DecoderTKS(BufferedTransformation *attachment, const int *pnDecodingArray) : Base32Decoder(attachment) { BaseN_Decoder::IsolatedInitialize( MakeParameters( Name::DecodingLookupArray(), pnDecodingArray )( Name::Log2Base(), 5 ) ); } }; //----------------------------------------------------------------------------- // Purpose: Implement hex encoding / decoding using a custom lookup table. // This is a class because the decoding is done via a generated // reverse-lookup table, and to save time it's best to just create // that table once. //----------------------------------------------------------------------------- CCustomHexEncoder::CCustomHexEncoder( const char *pchEncodingTable ) { m_bValidEncoding = false; if ( Q_strlen( pchEncodingTable ) == sizeof( m_rgubEncodingTable ) ) { Q_memcpy( m_rgubEncodingTable, pchEncodingTable, sizeof( m_rgubEncodingTable ) ); BaseN_Decoder::InitializeDecodingLookupArray( m_rgnDecodingTable, m_rgubEncodingTable, 16, false ); m_bValidEncoding = true; } else { AssertMsg( false, "CCrypto::CustomHexEncoder: Improper encoding table\n" ); } } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CCustomHexEncoder::~CCustomHexEncoder() { } //----------------------------------------------------------------------------- // Purpose: Hex-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Uses the supplied custom encoding characters // Input: pubData - Data to encode // cubData - Size of data to encode // pchEncodedData - Pointer to string buffer to store output in // cchEncodedData - Size of pchEncodedData buffer //----------------------------------------------------------------------------- bool CCustomHexEncoder::Encode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData ) { VPROF_BUDGET( "CCrypto::CustomHexEncode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( cubData ); Assert( pchEncodedData ); Assert( cchEncodedData > 0 ); if ( !m_bValidEncoding ) return false; ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData ); // Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs HexEncoder hexEncoder( pArraySinkOutput ); hexEncoder.IsolatedInitialize( MakeParameters( Name::EncodingLookupArray(), (const uint8 *)m_rgubEncodingTable ) ); hexEncoder.Put( pubData, cubData ); hexEncoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); pchEncodedData[len] = 0; // NULL-terminate if ( len >= cchEncodedData ) { AssertMsg2( false, "CCrypto::CustomHexEncode: insufficient output buffer for encoding, needed %d got %d\n", len, cchEncodedData ); return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Hex-decodes a block of data. (Text -> binary representation.) // With custom encoding-table // Input: pchData - Null-terminated hex-encoded string // pubDecodedData - Pointer to buffer to store output in // pcubDecodedData - Pointer to variable that contains size of // output buffer. At exit, is filled in with actual size // of decoded data. //----------------------------------------------------------------------------- bool CCustomHexEncoder::Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData ) { VPROF_BUDGET( "CCrypto::CustomHexDecode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pchData ); Assert( pubDecodedData ); Assert( pcubDecodedData ); Assert( *pcubDecodedData ); if ( !m_bValidEncoding ) return false; ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData ); // Note: HexEncoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs HexDecoderTKS hexDecoder( pArraySinkOutput, (const int *)m_rgnDecodingTable ); hexDecoder.Put( (byte *) pchData, Q_strlen( pchData ) ); hexDecoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); if ( len > *pcubDecodedData ) { AssertMsg2( false, "CCrypto::CustomHexDecode: insufficient output buffer for decoding, needed %d got %d\n", len, *pcubDecodedData ); return false; } *pcubDecodedData = len; return true; } //----------------------------------------------------------------------------- // Purpose: Implement hex encoding / decoding using a custom lookup table. // This is a class because the decoding is done via a generated // reverse-lookup table, and to save time it's best to just create // that table once. //----------------------------------------------------------------------------- CCustomBase32Encoder::CCustomBase32Encoder( const char *pchEncodingTable ) { m_bValidEncoding = false; if ( Q_strlen( pchEncodingTable ) == sizeof( m_rgubEncodingTable ) ) { Q_memcpy( m_rgubEncodingTable, pchEncodingTable, sizeof( m_rgubEncodingTable ) ); BaseN_Decoder::InitializeDecodingLookupArray( m_rgnDecodingTable, m_rgubEncodingTable, 32, false ); m_bValidEncoding = true; } else { AssertMsg( false, "CCrypto::CustomBase32Encoder: Improper encoding table\n" ); } } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CCustomBase32Encoder::~CCustomBase32Encoder() { } //----------------------------------------------------------------------------- // Purpose: Base32-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Uses the supplied custom encoding table // Input: pubData - Data to encode // cubData - Size of data to encode // pchEncodedData - Pointer to string buffer to store output in // cchEncodedData - Size of pchEncodedData buffer //----------------------------------------------------------------------------- bool CCustomBase32Encoder::Encode( const uint8 *pubData, const uint32 cubData, char *pchEncodedData, uint32 cchEncodedData ) { VPROF_BUDGET( "CCrypto::CustomBase32Encode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pubData ); Assert( cubData ); Assert( pchEncodedData ); Assert( cchEncodedData > 0 ); if ( !m_bValidEncoding ) return false; ArraySink * pArraySinkOutput = new ArraySink( (byte *) pchEncodedData, cchEncodedData ); // Note: Base32Encoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs Base32Encoder base32Encoder( pArraySinkOutput ); base32Encoder.IsolatedInitialize( MakeParameters( Name::EncodingLookupArray(), (const uint8 *)m_rgubEncodingTable ) ); base32Encoder.Put( pubData, cubData ); base32Encoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); pchEncodedData[len] = 0; // NULL-terminate if ( len >= cchEncodedData ) { AssertMsg2( false, "CCrypto::CustomBase32Encode: insufficient output buffer for encoding, needed %d got %d\n", len, cchEncodedData ); return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Base32-decodes a block of data. (Text -> binary representation.) // With custom encoding table // Input: pchData - Null-terminated hex-encoded string // pubDecodedData - Pointer to buffer to store output in // pcubDecodedData - Pointer to variable that contains size of // output buffer. At exit, is filled in with actual size // of decoded data. //----------------------------------------------------------------------------- bool CCustomBase32Encoder::Decode( const char *pchData, uint8 *pubDecodedData, uint32 *pcubDecodedData ) { VPROF_BUDGET( "CCrypto::CustomBase32Decode", VPROF_BUDGETGROUP_ENCRYPTION ); Assert( pchData ); Assert( pubDecodedData ); Assert( pcubDecodedData ); Assert( *pcubDecodedData ); if ( !m_bValidEncoding ) return false; ArraySink * pArraySinkOutput = new ArraySink( pubDecodedData, *pcubDecodedData ); // Note: Base32Encoder now owns the pointer to pOutputSink and will free it when the encoder goes out of scope and destructs Base32DecoderTKS base32Decoder( pArraySinkOutput, (const int *)m_rgnDecodingTable ); base32Decoder.Put( (byte *) pchData, Q_strlen( pchData ) ); base32Decoder.MessageEnd(); uint32 len = pArraySinkOutput->TotalPutLength(); if ( len > *pcubDecodedData ) { AssertMsg2( false, "CCrypto::CustomBase32Decode: insufficient output buffer for decoding, needed %d got %d\n", len, *pcubDecodedData ); return false; } *pcubDecodedData = len; return true; } //----------------------------------------------------------------------------- // Purpose: Base32-encodes a block of data. (Binary -> text representation.) The output // is null-terminated and can be treated as a string. // Uses the supplied custom encoding table, and a bit-stream input source // (not necessarily an integer number of bytes). // Input: pBitStringData - Data to encode // pchEncodedData - Pointer to string buffer to store output in // cchEncodedData - Size of pchEncodedData buffer //----------------------------------------------------------------------------- bool CCustomBase32Encoder::Encode( CSimpleBitString *pBitStringData, char *pchEncodedData, uint32 cchEncodedData ) { // This is useful if you have, say, 125 bits of information and // want to encode them into 25 base32-encoded characters. uint32 cBits = pBitStringData->GetCurrNumBits(); uint32 cCharacters = (cBits / 5); uint32 cBitsRemainder = cBits % 5; if ( cBitsRemainder ) cCharacters++; // GTE because of NULL if ( cCharacters >= cchEncodedData ) return false; CSimpleBitString::iterator itBitString( *pBitStringData ); uint ich = 0; for ( ; ich < cCharacters; ++ich ) { uint32 unCodon = itBitString.GetNextBits( 5 ); // Pad w/ zero bits to integer num codons if ( ich == (cCharacters - 1) ) unCodon <<= cBitsRemainder; pchEncodedData[ich] = m_rgubEncodingTable[ unCodon ]; } // NULL pchEncodedData[ich] = 0; return true; } //----------------------------------------------------------------------------- // Purpose: Base32-decodes a block of data. (Text -> binary representation.) // With custom encoding table, and a BitString output // Input: pchData - Null-terminated base32-encoded string // pBitStringDecodedData - Pointer to BitString to receive decoded data //----------------------------------------------------------------------------- bool CCustomBase32Encoder::Decode( const char *pchData, CSimpleBitString *pBitStringDecodedData ) { // Note: 25 base32-encoded characters contain 125 bits of information. // Decoded into a byte buffer, this yields 15 bytes plus 5 bits of padding. // Decoded into a CSimpleBitString, it will yield all 125 bits while ( *pchData ) { uint32 unData = m_rgnDecodingTable[(unsigned)*pchData++]; if ( unData == 0xFFFFFFFF ) return false; pBitStringDecodedData->AppendBits( unData, 5 ); } return true; } #ifdef DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: validates memory structures //----------------------------------------------------------------------------- void CCrypto::ValidateStatics( CValidator &validator, const char *pchName ) { ValidateObj( g_tslistPAutoSeededRNG ); } #endif // DBGFLAG_VALIDATE //----------------------------------------------------------------------------- // Purpose: Given a plaintext password, check whether it matches an existing // hash //----------------------------------------------------------------------------- bool CCrypto::BValidatePasswordHash( const char *pchInput, EPasswordHashAlg hashType, const PasswordHash_t &DigestStored, const Salt_t &Salt, PasswordHash_t *pDigestComputed ) { VPROF_BUDGET( "CCrypto::BValidatePasswordHash", VPROF_BUDGETGROUP_ENCRYPTION ); bool bResult = false; size_t cDigest = k_HashLengths[hashType]; Assert( cDigest != 0 ); PasswordHash_t tmpDigest; PasswordHash_t *pOutputDigest = pDigestComputed; if ( pOutputDigest == NULL ) { pOutputDigest = &tmpDigest; } BGeneratePasswordHash( pchInput, hashType, Salt, *pOutputDigest ); bResult = ( 0 == Q_memcmp( &DigestStored, pOutputDigest, cDigest ) ); return bResult; } //----------------------------------------------------------------------------- // Purpose: Given a plaintext password and salt, generate a password hash of // the requested type. //----------------------------------------------------------------------------- bool CCrypto::BGeneratePasswordHash( const char *pchInput, EPasswordHashAlg hashType, const Salt_t &Salt, PasswordHash_t &OutPasswordHash ) { VPROF_BUDGET( "CCrypto::BGeneratePasswordHash", VPROF_BUDGETGROUP_ENCRYPTION ); bool bResult = false; size_t cDigest = k_HashLengths[hashType]; switch ( hashType ) { case k_EHashSHA1: bResult = CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)&OutPasswordHash.sha ); break; case k_EHashBigPassword: { // // This is a fake algorithm to test widening of the column. It's a salted SHA-1 hash with 0x01 padding // on either side of it. // size_t cDigestSHA1 = k_HashLengths[k_EHashSHA1]; size_t cPadding = ( cDigest - cDigestSHA1 ) / 2; AssertMsg( ( ( cDigest - cDigestSHA1 ) % 2 ) == 0, "Invalid hash width for k_EHashBigPassword, needs to be even." ); CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)( (uint8 *)&OutPasswordHash.bigpassword + cPadding ) ); Q_memset( (uint8 *)&OutPasswordHash, 0x01, cPadding ); Q_memset( (uint8 *)&OutPasswordHash + cPadding + cDigestSHA1 , 0x01, cPadding ); bResult = true; break; } case k_EHashPBKDF2_1000: bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 1000, OutPasswordHash ); break; case k_EHashPBKDF2_5000: bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 5000, OutPasswordHash ); break; case k_EHashPBKDF2_10000: bResult = CCrypto::BGeneratePBKDF2Hash( pchInput, Salt, 10000, OutPasswordHash ); break; case k_EHashSHA1WrappedWithPBKDF2_10000: bResult = CCrypto::BGenerateWrappedSHA1PasswordHash( pchInput, Salt, 10000, OutPasswordHash ); break; default: AssertMsg1( false, "Invalid password hash type %u passed to BGeneratePasswordHash\n", hashType ); bResult = false; } return bResult; } //----------------------------------------------------------------------------- // Purpose: Given a plaintext password and salt and a count of rounds, generate a PBKDF2 hash // with the requested number of rounds. //----------------------------------------------------------------------------- bool CCrypto::BGeneratePBKDF2Hash( const char* pchInput, const Salt_t &Salt, unsigned int rounds, PasswordHash_t &OutPasswordHash ) { PKCS5_PBKDF2_HMAC pbkdf; unsigned int iterations = pbkdf.DeriveKey( (byte *)&OutPasswordHash.pbkdf2, sizeof(OutPasswordHash.pbkdf2), 0, (const byte *)pchInput, Q_strlen(pchInput), (const byte *)&Salt, sizeof(Salt), rounds ); return ( iterations == rounds ); } //----------------------------------------------------------------------------- // Purpose: Given a plaintext password and salt and a count of rounds, generate a SHA1 hash wrapped with // a PBKDF2 hash with the specified number of rounds. // Used to provide a stronger password hash for accounts that haven't logged in in a while. //----------------------------------------------------------------------------- bool CCrypto::BGenerateWrappedSHA1PasswordHash( const char *pchInput, const Salt_t &Salt, unsigned int rounds, PasswordHash_t &OutPasswordHash ) { bool bResult; bResult = CCrypto::GenerateSaltedSHA1Digest( pchInput, &Salt, (SHADigest_t *)&OutPasswordHash.sha ); if ( bResult ) { PKCS5_PBKDF2_HMAC pbkdf; unsigned int iterations = pbkdf.DeriveKey( (byte *)&OutPasswordHash.pbkdf2, sizeof(OutPasswordHash.pbkdf2), 0, (const byte *)&OutPasswordHash.sha, sizeof(OutPasswordHash.sha), (const byte *)&Salt, sizeof(Salt), rounds ); bResult = ( iterations == rounds ); } return bResult; } //----------------------------------------------------------------------------- // Purpose: Given an existing password hash and salt, attempt to construct a stronger // password hash and return the new hash type. // // Currently the only transformation available is from a SHA1 (or BigPassword) // hash to a PBKDF2 hash with 10,000 rounds. In the future this function // may be extended to allow additional transformations. //----------------------------------------------------------------------------- bool CCrypto::BUpgradeOrWrapPasswordHash( PasswordHash_t &InPasswordHash, EPasswordHashAlg hashTypeIn, const Salt_t &Salt, PasswordHash_t &OutPasswordHash, EPasswordHashAlg &hashTypeOut ) { bool bResult = true;; if ( hashTypeIn == k_EHashSHA1 || hashTypeIn == k_EHashBigPassword ) { // // Can wrap a SHA1 hash with any PBKDF variant, but right now only 10,000 rounds is // implemented. // if ( hashTypeOut == k_EHashPBKDF2_10000 ) { hashTypeOut = k_EHashSHA1WrappedWithPBKDF2_10000; byte * pbHash; if ( hashTypeIn == k_EHashSHA1 ) { pbHash = (byte *)&InPasswordHash.sha; } else { // // Need to unroll BigPasswordHash into unpadded SHA1 // size_t cDigest = k_HashLengths[k_EHashBigPassword]; size_t cDigestSHA1 = k_HashLengths[k_EHashSHA1]; size_t cPadding = ( cDigest - cDigestSHA1 ) / 2; AssertMsg( ( ( cDigest - cDigestSHA1 ) % 2 ) == 0, "Invalid hash width for k_EHashBigPassword, needs to be even." ); pbHash = (byte *)&InPasswordHash.sha + cPadding; } PKCS5_PBKDF2_HMAC pbkdf; PasswordHash_t passOut; unsigned int iterations = pbkdf.DeriveKey( (byte *)passOut.pbkdf2, sizeof(passOut.pbkdf2), 0, pbHash, k_HashLengths[k_EHashSHA1], (const byte *)&Salt, sizeof(Salt), 10000 ); bResult = ( iterations == 10000 ); if ( bResult ) { Q_memcpy( &OutPasswordHash, &passOut, sizeof(OutPasswordHash) ); } } else { Assert( hashTypeOut == k_EHashPBKDF2_10000 ); bResult = false; } } else { bResult = false; Assert( false ); } return bResult; }