//
// 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