Cryptographic Hashes in Qt (HMAC-SHA1, PBKDF2, Md5)

At times we need to generate cryptographic hashes for encryption purposes. This can be done in Qt using QCryptographicHash or QCA (Qt Cryptographic Architecture). QCA is a cross-platform crypto API, using Qt datatypes and conventions, hence we need to download and build it separately and include it in Qt. On the other hand QCryptographicHash class comes with Qt itself and can be used to generate cryptographic hashes.

Using QCA increases the size of our application as we need to include qca plugins and library in our application bundle to make it deployable (I’ll explain how to build and deploy QCA in my next post). Therefore I prefer to use qt’s inbuilt class QCryptographicHash if requirements can be completed using this.

Even after some research I couldn’t find a way to generate PBKDF2 using QCryptographicHash, therefore I am using QCA to generate PBKDF2 from a string/byte-array.

In this post I’ll explain how to generate HMAC-SHA1 and Md5 hash using QCryptographicHash, and generate PBKDF2 using QCA library.

Here is the code to generate HMAC-SHA1 of public key and a secret base string:

// Returns HMAC-SHA1 encrypted signature composed of public key and secret base string
QString hmacSha1(QByteArray key, QByteArray baseString)
{
    int blockSize = 64; // HMAC-SHA-1 block size, defined in SHA-1 standard
    if (key.length() > blockSize) { // if key is longer than block size (64), reduce key length with SHA-1 compression
        key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
    }
    QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
    QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
    // ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
    // Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)

    for (int i = 0; i < key.length(); i++) {
        innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
        outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
    }

    // result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
    QByteArray total = outerPadding;
    QByteArray part = innerPadding;
    part.append(baseString);
    total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1));
    QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1);

    QString result = QCA::arrayToHex(hashed);
    return result;
}

Above method will return the HMAC-SHA1 hex as a string. Now, lets see how to generate md5 hash from the content of a file.

QString md5Hash(QString path)
{
    QFile file(path);
    if(!file.exists())
    {
        qDebug()<<"File does not exist to read md5hash";
        return "";
    }
    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"error in reading file: "<<path;
        return "";
    }

    QByteArray fileContent(file.readAll());

    QByteArray fileHash = QCryptographicHash::hash(fileContent, QCryptographicHash::Md5);

    file.close();

    QString result = QString(fileHash.toHex());
   
    qDebug()<<"md5 : "<<result;

    return result;
}

Now, the last we are going to discuss here is PBKDF2. We will need to use QCA for this.
PBKDF2 of a string can be generated by passing the salt value, iterations and length of the key.
Here is the code to generate PBKDF2 from a password string passed in the function :

QString pbkdf2(QString password, QString salt, int length, int iterations)
{
    if(!QCA::isSupported("pbkdf2(sha1)", "qca-ossl"))
    {
       qDebug()<<"PBKDF version 2 with SHA1 not supported  ";
    }
    else
    {
        QCA::InitializationVector esalt(QCA::SecureArray(salt.toUtf8()));
        QCA::SecureArray epassword(password.toUtf8());
        QCA::SymmetricKey passwordOut = QCA::PBKDF2("sha1", "qca-ossl").makeKey (epassword, esalt, length, iterations);
        QString result = QCA::arrayToHex(passwordOut.toByteArray());
        return result;
     }
    return "";
}

Note: When using QCA classes remember to include the header <QtCrypto/QtCrypto>

Written By: Neha Gupta

Advertisements

Win32 File Watcher API to monitor Directory changes

Here I am going to explain how to use Win32 File Watcher API  to watch a directory as well as its sub directories for any change and find out the type of change.

You may use this C++ API to watch any directory for changes on Windows Platform in any C++ compatible application as I am using it in my Qt Application because of a bug in QFileSystemWatcher when watching a directory and its sub directories recursively.  To know more about QFileSystemWatcher bug click here.

We can use either ReadDirectoryChangesW or FindFirstChangeNotification to get the notification of any change in a directory or its subdirectories. But the added advantage of ReadDirectoryChangesW is that it gives the information of change and the name of the file that is changed. On the other hand  FindFirstChangeNotification only notifies that some change happened but no other information about the type of change or the name of file.

So lets start with How to implement  ReadDirectoryChangesW to watch a directory. Here is the code:

//include headers

#include <windows.h>
#include <Winbase.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>
#define MAX_DIRS 25
 #define MAX_FILES 255
 #define MAX_BUFFER 4096

 extern "C" {
 WINBASEAPI BOOL WINAPI
 ReadDirectoryChangesW( HANDLE hDirectory,
 LPVOID lpBuffer, DWORD nBufferLength,
 BOOL bWatchSubtree, DWORD dwNotifyFilter,
 LPDWORD lpBytesReturned,
 LPOVERLAPPED lpOverlapped,
 LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
 );
 }

 // all purpose structure to contain directory information and provide
 // the input buffer that is filled with file change data

 typedef struct _DIRECTORY_INFO {
    HANDLE hDir;
    TCHAR lpszDirName[MAX_PATH];
    CHAR lpBuffer[MAX_BUFFER];
    DWORD dwBufLength;
    OVERLAPPED Overlapped;
 }DIRECTORY_INFO, *PDIRECTORY_INFO, *LPDIRECTORY_INFO;

 DIRECTORY_INFO DirInfo[MAX_DIRS];   // Buffer for all of the directories
 TCHAR FileList[MAX_FILES*MAX_PATH]; // Buffer for all of the files
 DWORD numDirs;
//Method to start watching a directory. Call it on a separate thread so it wont block the main thread.  

void WatchDirectory(LPCWSTR path)
{
   char buf[2048];
   DWORD nRet;
   BOOL result=TRUE;
   char filename[MAX_PATH];
   DirInfo[0].hDir = CreateFile (path, GENERIC_READ|FILE_LIST_DIRECTORY, 
                                 FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
                                 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED,
                                 NULL);

   if(DirInfo[0].hDir == INVALID_HANDLE_VALUE)
   {
       return; //cannot open folder
   }
   
   lstrcpy( DirInfo[0].lpszDirName, path);
   OVERLAPPED PollingOverlap;
   
   FILE_NOTIFY_INFORMATION* pNotify;
   int offset;
   PollingOverlap.OffsetHigh = 0;
   PollingOverlap.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
   while(result)
   {
       result = ReadDirectoryChangesW(
                  DirInfo[0].hDir,// handle to the directory to be watched
                  &buf,// pointer to the buffer to receive the read results
                  sizeof(buf),// length of lpBuffer
                  TRUE,// flag for monitoring directory or directory tree
                  FILE_NOTIFY_CHANGE_FILE_NAME |
                  FILE_NOTIFY_CHANGE_DIR_NAME |
                  FILE_NOTIFY_CHANGE_SIZE,
                //FILE_NOTIFY_CHANGE_LAST_WRITE |
                //FILE_NOTIFY_CHANGE_LAST_ACCESS |
                //FILE_NOTIFY_CHANGE_CREATION,
                &nRet,// number of bytes returned
                &PollingOverlap,// pointer to structure needed for overlapped I/O
                NULL);
                
       WaitForSingleObject(PollingOverlap.hEvent,INFINITE);
       offset = 0;
       int rename = 0;
       char oldName[260];
       char newName[260];
       do
       {
           pNotify = (FILE_NOTIFY_INFORMATION*)((char*)buf + offset);
           strcpy(filename, "");
           int filenamelen = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName, pNotify->FileNameLength/2, filename, sizeof(filename), NULL, NULL);
           filename[pNotify->FileNameLength/2] = '';
           switch(pNotify->Action)
           {
               case FILE_ACTION_ADDED:
                   printf("\nThe file is added to the directory: [%s] \n", filename);
                   break;
               case FILE_ACTION_REMOVED:
                   printf("\nThe file is removed from the directory: [%s] \n", filename);
                   break;
               case FILE_ACTION_MODIFIED:
                   printf("\nThe file is modified. This can be a change in the time stamp or attributes: [%s]\n", filename);
                   break;
               case FILE_ACTION_RENAMED_OLD_NAME:
                   printf("\nThe file was renamed and this is the old name: [%s]\n", filename);
                   break;
               case FILE_ACTION_RENAMED_NEW_NAME:
                   printf("\nThe file was renamed and this is the new name: [%s]\n", filename);
                   break;
               default:
                   printf("\nDefault error.\n");
                   break;
            }

           offset += pNotify->NextEntryOffset;
       
        }while(pNotify->NextEntryOffset); //(offset != 0);
      }

    CloseHandle( DirInfo[0].hDir );

}

Hence this way you may read the changes in a directory and its sub directories (optional. Set 4th parameter to TRUE/FALSE in ReadDirectoryChangesW).

Written By: Neha Gupta