You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2219 lines
82 KiB
2219 lines
82 KiB
//========= 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 <sys/types.h> |
|
#include <sys/socket.h> |
|
#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 <emmintrin.h> |
|
#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<AES> 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<CAutoSeededRNG> 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<CAutoSeededRNG>::Node_t; |
|
} |
|
} |
|
|
|
~CPoolAllocatedRNG() |
|
{ |
|
g_tslistPAutoSeededRNG.Push( m_pRNGNode ); |
|
} |
|
|
|
CAutoSeededRNG &GetRNG() |
|
{ |
|
return m_pRNGNode->elem; |
|
} |
|
|
|
private: |
|
CTSList<CAutoSeededRNG>::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<PKCS1v15, SHA256>::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<PKCS1v15, SHA256>::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<SHA256> 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<SHA256> 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<SHA256> 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; |
|
} |
|
|
|
|