// // CBlowfish.cpp // // (c)Copyright 2004 Steven J. Eschweiler. All Rights Reserved. // // Based on the C implementation of the Blowfish algorithm by Paul Kocher. // #ifdef _WIN32 #include <winsock2.h> // Always include before windows.h for SocketTools 5.x to work #include <windows.h> #include <cassert> #else #include <assert.h> #endif #include <iostream> #include <string> #include <fstream> // Needed for file access #include "CBlowfish.h" using namespace std; CBlowfish::CBlowfish(unsigned char *key, int keyLen, unsigned char *pWorkBuffer, unsigned long ulWorkBufferSize) { Init(m_pKey, keyLen, pWorkBuffer, ulWorkBufferSize); } CBlowfish::CBlowfish(std::string keyHEX, unsigned char *pWorkBuffer, unsigned long ulWorkBufferSize) { // Convert keyHEX to unsigned char * int keyLen = static_cast<int>(keyHEX.length())/2; assert(keyLen<=56); std::string::iterator iterInput=keyHEX.begin(); for (int i=0; i<keyLen; i++) { m_pKey[i] = HexInterpret(*iterInput++) << 4 | HexInterpret(*iterInput++); } // Call Init Init(m_pKey, keyLen, pWorkBuffer, ulWorkBufferSize); // Should zero out the key to remove it from memory for additional security for (int i=0; i<keyLen; i++) m_pKey[i] = 0; } void CBlowfish::Init(unsigned char *key, int keyLen, unsigned char *pWorkBuffer, unsigned long ulWorkBufferSize) { int i, j, k; unsigned long data, datal, datar; // Set these up m_ucEncryptedStringBuffer = NULL; m_nEncryptedStringBufferLength = 0; #ifdef _WIN32 CancelReset(); #endif m_pWorkBuffer = pWorkBuffer; m_ulWorkBufferSize = ulWorkBufferSize; // A little debug stuff here assert(key!=0); assert(keyLen>0); assert(keyLen<=56); assert(pWorkBuffer!=0); assert((ulWorkBufferSize%8)==0); // m_ulWorkBufferSize must be a multiple of 8... // I think there is a limit on key length imposed by this blowfish algorithm. // We play it safe. if (keyLen>56) keyLen=56; // Create internal m_ctx for (i = 0;i < 4;i++) { for (j = 0;j < 256;j++) m_ctx.S[i][j] = BLOWFISH_ORIG_S[i][j]; } // Continue creating internal m_ctx j = 0; for (i = 0;i < BLOWFISH_N + 2;++i) { data = 0x00000000; for (k = 0;k < 4;++k) { data = (data << 8) | key[j]; j = j + 1; if (j >= keyLen) j = 0; } m_ctx.P[i] = BLOWFISH_ORIG_P[i] ^ data; } datal = 0x00000000; datar = 0x00000000; for (i = 0;i < BLOWFISH_N + 2;i += 2) { Encrypt(&datal, &datar); m_ctx.P[i] = datal; m_ctx.P[i+1] = datar; } for (i = 0;i < 4;++i) { for (j = 0;j < 256;j += 2) { Encrypt(&datal, &datar); m_ctx.S[i][j] = datal; m_ctx.S[i][j+1] = datar; } } } CBlowfish::~CBlowfish(void) { if ( m_ucEncryptedStringBuffer != NULL ) { delete [] m_ucEncryptedStringBuffer; m_ucEncryptedStringBuffer = NULL; } m_nEncryptedStringBufferLength = 0; //m_pWorkBuffer = 0; pointless and potentially dangerous } #ifdef _WIN32 bool CBlowfish::FileEncrypt(std::string strInputFile,std::string strOutputFile, HWND hwnd4Notify,UINT mMsgID4Notify,WPARAM wParam4ProgressBar,int nRangeMin,int nRangeMax, unsigned char *pszUserHeader,unsigned long ulUserHeaderSize) #else bool CBlowfish::FileEncrypt(char *pszInputFile,char *pszOutputFile, unsigned char *pszUserHeader,unsigned long ulUserHeaderSize) #endif { ifstream ifile; ofstream ofile; unsigned long ulChunkSize, ulChunkSizePadded; __int64 llNumIterations, llLoopVar; BLOWFISH_HEADER bh; // Initialize header memset(&bh,0,sizeof(bh)); // A little debug code here //assert(pszInputFile!=0); //assert(pszOutputFile!=0); // pszInputFile and pszOutputFile can not be the same. The reason // is that we read some data from one file, and then process it, // and then place it in the output file. //if (strcmp(pszInputFile,pszOutputFile)==0) if (strInputFile==strOutputFile) return false; // Get Filesize #ifdef _WIN32 bh.original_filesize = GetInt64FileSize(strInputFile); #endif // Open files ifile.open(strInputFile.c_str(),ios::binary); if( !ifile.is_open() ) return false; ofile.open(strOutputFile.c_str(),ios::binary|ios::trunc); if( !ofile.is_open() ) { ifile.close(); return false; } // Determine Input Filesize - place in our Blowfish Header #ifndef _WIN32 ifile.seekg(0,ios::end); bh.original_filesize = ifile.tellg(); ifile.seekg(0,ios::beg); #endif // Handle case where original file is zero bytes! if (bh.original_filesize==0) { // Write User Header Size ofile.write(reinterpret_cast<char *>(&ulUserHeaderSize),sizeof(ulUserHeaderSize)); // Write User Header if (ulUserHeaderSize) ofile.write(reinterpret_cast<char *>(pszUserHeader),ulUserHeaderSize); // Write Blowfish Header ofile.write(reinterpret_cast<char *>(&bh),sizeof(bh)); // Close files ofile.close(); ifile.close(); return true; } // Our other piece of info in our Blowfish Header... bh.original_filesize_plus_padding = bh.original_filesize; bh.original_filesize_plus_padding += ((bh.original_filesize%8)?(8-(bh.original_filesize%8)):0); #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " bh.original_filesize_plus_padding = " << bh.original_filesize_plus_padding << endl; #endif assert(bh.original_filesize_plus_padding!=0); // Write User Header Size ofile.write(reinterpret_cast<char *>(&ulUserHeaderSize),sizeof(ulUserHeaderSize)); // Write User Header if (ulUserHeaderSize) ofile.write(reinterpret_cast<char *>(pszUserHeader),ulUserHeaderSize); // Write Blowfish Header ofile.write(reinterpret_cast<char *>(&bh),sizeof(bh)); // Calculate the number of iterations required to read file. // NOTE: This uses original_filesize as opposed to the version // in the FileDecrypt() function. llNumIterations = (bh.original_filesize/m_ulWorkBufferSize); llNumIterations += ((bh.original_filesize%m_ulWorkBufferSize)?1:0); #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " llNumIterations = " << llNumIterations << endl; #endif assert(llNumIterations!=0); // OK... Let's process each iteration for(llLoopVar=0; llLoopVar<llNumIterations; llLoopVar++) { #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " Iteration = " << llLoopVar << endl; #endif #ifdef _WIN32 // Keep Windows Happy // ProcessWindowsMessages(); if (hwnd4Notify) PostMessage(hwnd4Notify,mMsgID4Notify,wParam4ProgressBar, static_cast<LPARAM>((((llLoopVar*static_cast<__int64>((nRangeMax-nRangeMin)))/llNumIterations)+static_cast<__int64>(nRangeMin))) ); if (m_bCancel) { // Close files ofile.close(); ifile.close(); return false; } #endif // On each iteration, we operate on a chunk. // The chunk size is going to equal either: // // 1. m_ulWorkBufferSize <- Because file is larger than work buffer and we are on FIRST iteration // 2. original_filesize <- Because file is smaller than work buffer and we are on FIRST & LAST & ONLY iteration // 3. original_filesize%m_ulWorkBufferSize <- Because file is larger than work buffer and we are on the LAST iteration // First order of business is to determine which of the three // possible chunk sizes we need for this iteration if( llLoopVar==(llNumIterations-1) ) { ulChunkSize = static_cast<unsigned long>((bh.original_filesize%m_ulWorkBufferSize) ? (bh.original_filesize%m_ulWorkBufferSize) : (m_ulWorkBufferSize-(bh.original_filesize%m_ulWorkBufferSize))); } else { ulChunkSize = m_ulWorkBufferSize; } #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " ulChunkSize = " << ulChunkSize << endl; #endif assert(ulChunkSize!=0); // Now we read this chunk (for this iteration) from the input file... ifile.read(reinterpret_cast<char *>(m_pWorkBuffer),ulChunkSize); // OK, let's encrypt this data stream. Note that EncryptDataStream() // will pad with zero bytes if needed. ulChunkSizePadded = EncryptDataStream(m_pWorkBuffer,ulChunkSize); #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " ulChunkSizePadded = " << ulChunkSizePadded << endl; #endif assert(ulChunkSizePadded!=0); // Write the chunk to the Output file ofile.write(reinterpret_cast<char *>(m_pWorkBuffer),ulChunkSizePadded); } // Close files ofile.close(); ifile.close(); #ifdef _WIN32 if (hwnd4Notify) PostMessage(hwnd4Notify,mMsgID4Notify,wParam4ProgressBar,nRangeMax); #endif return true; } #ifdef _WIN32 bool CBlowfish::FileDecrypt(std::string strInputFile,std::string strOutputFile, HWND hwnd4Notify,UINT mMsgID4Notify,WPARAM wParam4ProgressBar,int nRangeMin,int nRangeMax, unsigned char *pszUserHeader,unsigned long ulUserHeaderSize) #else bool CBlowfish::FileDecrypt(char *pszInputFile,char *pszOutputFile, unsigned char *pszUserHeader,unsigned long ulUserHeaderSize) #endif { ifstream ifile; ofstream ofile; unsigned long ulUserHeaderSizeRead, ulChunkSize, ulChunkSizePadded; __int64 llTotalInputFileSize, llCalculatedTotalInputFileSize, llNumIterations, llLoopVar; BLOWFISH_HEADER bh; // Initialize header memset(&bh,0,sizeof(bh)); // pszInputFile and pszOutputFile can not be the same. The reason // is that we read some data from one file, and then process it, // and then place it in the output file. //if (strcmp(pszInputFile,pszOutputFile)==0) if (strInputFile==strOutputFile) return false; // Determine Filesize without Header #ifdef _WIN32 llTotalInputFileSize = GetInt64FileSize(strInputFile); #endif // Open files ifile.open(strInputFile.c_str(),ios::binary); if( !ifile.is_open() ) return false; ofile.open(strOutputFile.c_str(),ios::binary|ios::trunc); if( !ofile.is_open() ) { ifile.close(); return false; } // OK... there is the following in the encrypted file: // // 1. User Header Size // 2. User Header // 3. Blowfish Header // 4. Blowfish Encrypted File Data // 5. 0 or more Padding Bytes (containing zeros) for the Blowfish Encrypted File Data // // #2 #4 and #5 won't exist in a zero byte original file // // Determine Filesize without Header #ifndef _WIN32 ifile.seekg(0,ios::end); llTotalInputFileSize = ifile.tellg(); ifile.seekg(0,ios::beg); #endif // Read User Header Size ifile.read(reinterpret_cast<char *>(&ulUserHeaderSizeRead),sizeof(ulUserHeaderSizeRead)); assert(ulUserHeaderSizeRead==(ulUserHeaderSize)); ulUserHeaderSize = ulUserHeaderSizeRead; // Read User Header if (ulUserHeaderSize) ifile.read(reinterpret_cast<char *>(pszUserHeader),ulUserHeaderSize); // Read Blowfish Header ifile.read(reinterpret_cast<char *>(&bh),sizeof(bh)); // Handle case where original file is zero bytes! if (bh.original_filesize==0) { // Close files ofile.close(); ifile.close(); return true; } // We will ensure that the entire file is the correct size... // // I've heard of FTP adding padding bytes. Are these padding // bytes removed after download? If they are and all zero bytes // are removed, we could have a problem because we also may // append zero bytes. // // Either way, we *WILL* play it safe here!!! // llCalculatedTotalInputFileSize = sizeof(ulUserHeaderSize) + ulUserHeaderSizeRead + sizeof(bh) + bh.original_filesize_plus_padding; if (llTotalInputFileSize!=llCalculatedTotalInputFileSize) return false; assert(llTotalInputFileSize==llCalculatedTotalInputFileSize); // Calculate the number of iterations required to read file. // NOTE: This uses original_filesize_plus_padding as opposed to the version // in the FileEncrypt() function. llNumIterations = (bh.original_filesize_plus_padding/m_ulWorkBufferSize); llNumIterations += ((bh.original_filesize_plus_padding%m_ulWorkBufferSize)?1:0); #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " llNumIterations = " << llNumIterations << endl; #endif assert(llNumIterations!=0); // OK... Let's process each iteration for(llLoopVar=0; llLoopVar<llNumIterations; llLoopVar++) { #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " Iteration = " << llLoopVar << endl; #endif #ifdef _WIN32 // Keep Windows Happy // ProcessWindowsMessages(); if (hwnd4Notify) PostMessage(hwnd4Notify,mMsgID4Notify,wParam4ProgressBar, static_cast<LPARAM>((((llLoopVar*static_cast<__int64>((nRangeMax-nRangeMin)))/llNumIterations)+static_cast<__int64>(nRangeMin))) ); if (m_bCancel) { // Close files ofile.close(); ifile.close(); return false; } #endif // On each iteration, we operate on a chunk. // The chunk size is going to equal either: // // 1. m_ulWorkBufferSize <- Because file is larger than work buffer and we are on FIRST iteration // 2. original_filesize_plus_padding <- Because file is smaller than work buffer and we are on FIRST & LAST & ONLY iteration // 3. original_filesize_plus_padding%m_ulWorkBufferSize <- Because file is larger than work buffer and we are on the LAST iteration // First order of business is to determine which of the three // possible chunk sizes we need for this iteration if( llLoopVar==(llNumIterations-1) ) { ulChunkSizePadded = static_cast<unsigned long>((bh.original_filesize_plus_padding%m_ulWorkBufferSize) ? (bh.original_filesize_plus_padding%m_ulWorkBufferSize) : (m_ulWorkBufferSize-(bh.original_filesize_plus_padding%m_ulWorkBufferSize))); } else { ulChunkSizePadded = m_ulWorkBufferSize; } #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " ulChunkSizePadded = " << ulChunkSizePadded << endl; #endif assert(ulChunkSizePadded!=0); // Now we read this chunk (for this iteration) from the input file... ifile.read(reinterpret_cast<char *>(m_pWorkBuffer),ulChunkSizePadded); // OK, let's decrypt this data stream. Note that DecryptDataStream() // will requires a padded data stream as we have here. if (DecryptDataStream(m_pWorkBuffer,ulChunkSizePadded)==false) return false; // Determine size of chunk to write to the output file if( llLoopVar==(llNumIterations-1) ) ulChunkSize = static_cast<unsigned long>((bh.original_filesize%m_ulWorkBufferSize) ? (bh.original_filesize%m_ulWorkBufferSize) : (m_ulWorkBufferSize-(bh.original_filesize%m_ulWorkBufferSize))); else ulChunkSize = m_ulWorkBufferSize; #ifdef DEBUG_BLOWFISH_STDOUT cout << __FUNCTION__ << " ulChunkSize = " << ulChunkSize << endl; #endif assert(ulChunkSize!=0); // Write paddless chunk to output file ofile.write(reinterpret_cast<char *>(m_pWorkBuffer),ulChunkSize); } // Close files ofile.close(); ifile.close(); #ifdef _WIN32 if (hwnd4Notify) PostMessage(hwnd4Notify,mMsgID4Notify,wParam4ProgressBar,nRangeMax); #endif return true; } void CBlowfish::Encrypt(unsigned long *xl, unsigned long *xr) { unsigned long Xl; unsigned long Xr; unsigned long temp; short i; Xl = *xl; Xr = *xr; for (i = 0;i < BLOWFISH_N;++i) { Xl = Xl ^ m_ctx.P[i]; Xr = F(Xl) ^ Xr; temp = Xl; Xl = Xr; Xr = temp; } temp = Xl; Xl = Xr; Xr = temp; Xr = Xr ^ m_ctx.P[BLOWFISH_N]; Xl = Xl ^ m_ctx.P[BLOWFISH_N+1]; *xl = Xl; *xr = Xr; } void CBlowfish::Decrypt(unsigned long *xl, unsigned long *xr) { unsigned long Xl; unsigned long Xr; unsigned long temp; short i; Xl = *xl; Xr = *xr; for (i = BLOWFISH_N + 1;i > 1;--i) { Xl = Xl ^ m_ctx.P[i]; Xr = F(Xl) ^ Xr; // Exchange Xl and Xr temp = Xl; Xl = Xr; Xr = temp; } // Exchange Xl and Xr temp = Xl; Xl = Xr; Xr = temp; Xr = Xr ^ m_ctx.P[1]; Xl = Xl ^ m_ctx.P[0]; *xl = Xl; *xr = Xr; } bool CBlowfish::StringEncrypt(const char *pszInputString) { if (m_ucEncryptedStringBuffer != NULL) { delete [] m_ucEncryptedStringBuffer; m_ucEncryptedStringBuffer = NULL; m_nEncryptedStringBufferLength = 0; } if (pszInputString==NULL) return true; int nStringLength = static_cast<int>(strlen(pszInputString)) + 1; // 1 to add NULL if (nStringLength==1) { return true; } int nStringLengthSafe = nStringLength + 8; m_ucEncryptedStringBuffer = new unsigned char[nStringLengthSafe]; // Copy data, including terminating NULL memset(m_ucEncryptedStringBuffer,0,nStringLengthSafe); strcpy(reinterpret_cast<char *>(m_ucEncryptedStringBuffer),pszInputString); m_nEncryptedStringBufferLength = EncryptDataStream(m_ucEncryptedStringBuffer,nStringLength); return true; } std::string CBlowfish::StringDecrypt(unsigned char *pszEncryptedStringBuffer,int nEncryptedStringBufferLength) { m_strDecryptedString = ""; if (nEncryptedStringBufferLength==0) return m_strDecryptedString; unsigned char * pucTmpEncryptedStringBuffer = new unsigned char[nEncryptedStringBufferLength]; memcpy(pucTmpEncryptedStringBuffer,pszEncryptedStringBuffer,nEncryptedStringBufferLength); if (DecryptDataStream(pucTmpEncryptedStringBuffer, nEncryptedStringBufferLength)==false) { return m_strDecryptedString; } int nDecryptedStringLength = static_cast<int>(strlen(reinterpret_cast<char *>(pucTmpEncryptedStringBuffer))) + 1; char *pszDecryptedString = new char[nDecryptedStringLength]; strcpy(pszDecryptedString,reinterpret_cast<char *>(pucTmpEncryptedStringBuffer)); m_strDecryptedString = pszDecryptedString; delete [] pszDecryptedString; delete [] pucTmpEncryptedStringBuffer; return m_strDecryptedString; } // EncryptDataStream() encrypts data in place. The data array should // always be on an 8-byte boundry. If it is not, EncryptDataStream() // will add up to 7 additional bytes of zero's to the end of the data stream. // Therefore, BE SURE that you ALLOCATE data on an 8 byte boundry, even if the // data within it is not on an 8-byte boundry. For example, if there is only // one byte of data in the data array (and len=1), then make sure that the // data array is allocated to at least 8 bytes for the additional padding. // // EncryptDataStream() returns the length, in bytes, of the encrypted // data stream and is a mutiple of 8. // unsigned long CBlowfish::EncryptDataStream(unsigned char *data, unsigned long data_len) { unsigned long padded_data_len, padding_len, ulLoopVar; BLOWFISH_Block EncryptionBlock; unsigned char *pdata; // Calculate padding sizes, if any if ( (data_len%8) != 0 ) padding_len = 8-(data_len%8); else padding_len = 0; padded_data_len = data_len + padding_len; // Pad with zero bytes, if needed for (ulLoopVar=0; ulLoopVar<padding_len; ulLoopVar++) data[(data_len)+ulLoopVar] = 0; // Good to go. Let's encrypt! pdata=data; for (ulLoopVar=padded_data_len; ulLoopVar>=8; ulLoopVar-=8) { BytesToBlock(EncryptionBlock,pdata); Encrypt(&EncryptionBlock.l,&EncryptionBlock.r); BlockToBytes(pdata+=8,EncryptionBlock); } return padded_data_len; } // Pass the padded data length bool CBlowfish::DecryptDataStream(unsigned char *data, unsigned long padded_data_len) { unsigned long ulLoopVar; BLOWFISH_Block EncryptionBlock; unsigned char *pdata; // Ensure padded data length is a multiple of 8 assert( (padded_data_len%8)==0 ); // Just to be safe, we test for this if ((padded_data_len%8)!=0) { return false; } // Good to go. Let's encrypt! pdata=data; for (ulLoopVar=padded_data_len; ulLoopVar>=8; ulLoopVar-=8) { BytesToBlock(EncryptionBlock,pdata); Decrypt(&EncryptionBlock.l,&EncryptionBlock.r); BlockToBytes(pdata+=8,EncryptionBlock); } return true; } #ifdef _WIN32 __int64 CBlowfish::GetInt64FileSize(std::string strFilename) { WIN32_FIND_DATA FindFileData; HANDLE hFind; __int64 int64filesize=-1; // Assume error condition hFind = ::FindFirstFile( strFilename.c_str(), &FindFileData ); if( hFind != INVALID_HANDLE_VALUE ) { int64filesize = ( ((__int64)FindFileData.nFileSizeHigh) << 32 ) + FindFileData.nFileSizeLow; FindClose(hFind); } return int64filesize; // indicates error } void CBlowfish::Cancel() { m_bCancel = true; } void CBlowfish::CancelReset() { m_bCancel = false; } #endif