http://www.developer.com/

Back to article

Mastering Windows Mobile Crypto API: The Basics


November 8, 2005

Where to Start...

Encryption and decryption is a pretty wide area, so nobody could cover it in one single article. No doubt, it would take a good heavy book to describe all basics of cryptology theory. Fortunately, for day-to-day tasks you don't need to study this in detail; Microsoft has done the hard job for you. So, here comes Crypto API, which is available in both handheld device and desktop versions. Hence, I won't discuss any specific encryption/decryption algorithm but rather dig in into practical scenarios you might face during application development.

There are two common methods I will overview: symmetric and asymmetric (which is also called public-key). The symmetric encryption technique is "the simplest" one. You have to use the same key to both encrypt and decrypt your data. This key is kept secret. Asymmetric encoding uses two keys: public and private. As in the previous case, the private key is not available outside your application, but the public key can be freely distributed among all target parties in question.

A Bit of Terminology

With all that said, let me define what I are talking about to avoid a misunderstanding. Your applications will use Crypto API to perform all required steps during data encoding or decoding. Crypto API, as a part of the OS, will interact with Cryptographic Service Providers (CSP), that actually implement cryptographic protocols and standards. Windows Mobile OS contains several built-in CSPs, but you can develop your own, in case you really need it. CSP implementation is out of the scope of recent articles.

The following tasks can be performed through Crypto API:

  • Generate cryptographic keys
  • Export cryptographic keys
  • Encrypt/Decrypt the data
  • Create digital signatures
  • Connect to a CSP

Regardless of the given CSP, data encryption itself is done using symmetric algorithms. Windows Mobile SDK documentation provides a lot of detailed info about Crypto API, so I forward you there for additional explanation. You now will move on to samples.

Enumerating Available CSPs

Prior to doing the coding, you should meet CSP face to face. In other words, you need to enumerate all CSPs installed on the device:

void CEncryptionDlg::OnButtonEnum()
{
   CFile file;
   if ( !file.Open(_T("\Providers.txt"),
                   CFile::modeCreate | CFile::modeWrite) )
      return;
   DWORD cbName;
   LPTSTR      pszName = NULL;
   DWORD       dwIndex = 0;
   DWORD dwType;
   DWORD dwErr;
   char szInfo[1024];
   BOOL bRet = FALSE;
   strcpy(szInfo,"Providersrn");
   file.Write(szInfo,strlen(szInfo));
   while((bRet = CryptEnumProviders(
      dwIndex,
      NULL,
      0,
      &dwType,
      NULL,
      &cbName
      )))
    {
      pszName = new TCHAR[cbName];
                if (CryptEnumProviders(
         dwIndex++,
         NULL,
         0,
         &dwType,
         pszName,
         &cbName
         ))
        {
         char szName[255];
         memset(szName,0,sizeof(szName));
         WideCharToMultiByte(CP_ACP,0,pszName,_tcslen(pszName),
                             szName,sizeof(szName),0,0);
         sprintf(szInfo,"Type %4.0d name %srn",dwType, szName);
         file.Write(szInfo,strlen(szInfo));
         }
         else
         {
         if (ERROR_NO_MORE_ITEMS != (dwErr = GetLastError()))
         {
            TRACE(_T("Error during CryptEnumProvider"),
               GetLastError ());
         }
      }
      delete [] pszName;
   }
}

This code snippet enumerates all available CSPs and stores their names in a text file. You can use these names later to identify a specific provider, if you need to.

CSP Providers and Containers

The starting point of any encryption or decryption is obtaining a handle of CSP provider. CryptAcquireContext does all the required job for you. Every CSP provider may have several 'containers' or 'users,' so it's important to specify a correct container name at both the sender and receiver sides to make cryptographic keys exchange successful. A code snippet below tries to get a CSP context and create a new one in case a given container doesn't exist yet:

BOOL CEncryptImpl :: Setup ()
{
   // Ensure that the default cryptographic client is set up.
   HCRYPTKEY hKey;
   // Attempt to acquire a handle to the key container.
   if (! CryptAcquireContext(&m_hProv, m_sContainer,
                             MS_DEF_PROV, PROV_RSA_FULL, 0))
   {
      // Some sort of error occured, create default key container.
      if (! CryptAcquireContext (&m_hProv, m_sContainer, MS_DEF_PROV,
                                 PROV_RSA_FULL, CRYPT_NEWKEYSET))
      {
         // Error creating key container!
         return FALSE;
      }
   }
   // Attempt to get handle to signature key.
   if (! CryptGetUserKey (m_hProv, AT_SIGNATURE, &hKey))
   {
      if (NTE_NO_KEY == GetLastError ())
      {
         // Create signature key pair.
         if (! CryptGenKey (m_hProv, AT_SIGNATURE, 0, &hKey))
         {
            // Error during CryptGenKey!
            return FALSE;
         }
         else
         {
            CryptDestroyKey (hKey);
         }
      }
      else
      {
         // Error during CryptGetUserKey!
         return FALSE;
      }
   }
   // Attempt to get handle to exchange key.
   if (! CryptGetUserKey (m_hProv, AT_KEYEXCHANGE, &hKey))
   {
      if (NTE_NO_KEY == GetLastError ())
      {
         // Create key exchange key pair.
         if (! CryptGenKey (m_hProv, AT_KEYEXCHANGE, 0, &hKey))
         {
            // Error during CryptGenKey!
            return FALSE;
         }
         else
         {
            CryptDestroyKey (hKey);
         }
      }
      else
      {
         // Error during CryptGetUserKey!
         return FALSE;
      }
   }
   m_bInitOK = TRUE;
   return TRUE;
}

As you can see, all it does is query a specified CSP/container pair and create new ones if needed.

Encryption and Decryption Using Password

Start with the simplest case: encoding and decoding strings using a password as a base for the encryption key. The following sample illustrates a common working flow during encryption and decryption:

#define ENCRYPT_ALG      CALG_RC4
#define ENCRYPT_KEY      "Dev@@Test"
...
/////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////
BOOL CEncryptImpl::Encrypt(const char* pszInData,
                           char* pszOutData, BOOL bEncrypt)
{
   if (!m_bInitOK)
      return FALSE;
   BOOL         bResult = TRUE;
   HKEY         hRegKey = NULL;
   HCRYPTPROV   hProv = NULL;
   HCRYPTKEY    hKey = NULL;
   HCRYPTKEY    hXchgKey = NULL;
   HCRYPTHASH   hHash = NULL;
   DWORD        dwLength;
   CByteArray arBuffer;
   char         szTemp [1024] = "";
   // Get handle to user default provider.
   if ( !CryptAcquireContext (& hProv, NULL, NULL,
       PROV_RSA_FULL, 0) )
   {
      TRACE(L"CryptAcquireContext returned errorn");
      return FALSE;
   }
   // Create hash object.
   if ( !CryptCreateHash (hProv, CALG_MD5, 0, 0, &hHash) )
   {
      bResult = FALSE;
      goto CleanUp;
   }
   // Hash password string.
   dwLength = sizeof (char) * strlen (ENCRYPT_KEY);
   if ( !CryptHashData(hHash, (BYTE *) ENCRYPT_KEY, dwLength, 0) )
   {
      bResult = FALSE;
      goto CleanUp;
   }
   // Create block cipher session key based on hash of the password.
   if ( !CryptDeriveKey (hProv, ENCRYPT_ALG, hHash,
                         CRYPT_EXPORTABLE, &hKey))
   {
      bResult = FALSE;
      goto CleanUp;
   }
   // Determine number of bytes to encrypt at a time.
   dwLength = sizeof (char) * strlen (pszInData);
   arBuffer.SetSize(dwLength);
   if (arBuffer.GetSize())
   {
      memcpy (arBuffer.GetData(), pszInData, dwLength);
      if ( bEncrypt )
      {
         // Encrypt data
         if ( !CryptEncrypt (hKey, 0, TRUE, 0, arBuffer.GetData(),
                             &dwLength, dwLength) )
         {
            bResult = FALSE;
            goto CleanUp;
         }
         BYTE* pb = (BYTE*) arBuffer.GetData();
         int b;
         for (DWORD i = 0; i < dwLength; i ++)
         {
            b = (*pb & 0xF0) >> 4;
            *pszOutData++ = (b <= 9) ? b + _T('0') : b - 10 +
                             _T('A');
            b = *pb & 0x0F;
            *pszOutData++ = (b <= 9) ? b + _T('0') : b - 10 +
                             _T('A');
         pb++;
         }
         *pszOutData++ = 0;
      }
      else
      {
         strcpy (szTemp, pszInData);
         if (! CryptDecrypt (hKey, 0, TRUE, 0, (BYTE *) szTemp,
             &dwLength))
         {
         bResult = FALSE;
         }
         strcpy (pszOutData, szTemp);
      }
   }
CleanUp:
   if ( hKey )
      CryptDestroyKey (hKey);
   if ( hHash )
      CryptDestroyHash (hHash);
   if ( hProv )
      CryptReleaseContext (hProv, 0);
   return bResult;
}

So, a regular work flow is as follows:

  • Connect to CSP to obtain its handle
  • Create a hash object with given algorithm
  • Fill it in with data based on the provided password
  • Create an encryption session key derived from hash object
  • Perform the actual encryption
  • Optionally, you can convert binary representation into strings as it was done in the sample above
  • Destroy the session key and hash, and release the CSP handle

Thus, if all parts of your application keep the same 'password' to create the session key, you may secure your data easily. The samples above deal with C strings, but you can modify it to work with arbitrary binary data with minimal effort.

Using Public-Key Encryption

This method gives you more flexibility and strength. The main difference from a 'password'-based technique is that you create a pair of keys: public and private for given container. The keys are mathematically linked, so to encrypt/decrypt some data you need both of them.

A typical scenario in this case follows. Suppose you want to create an encrypted message and send it over an unsecured network. Thus, you have a sender and a receiver of the message. The very first thing to do is to generate a pair of public/private keys at the receiver side and export the receiver's public key. This public key will be used later at the sender's side during message encryption. Crypto API allows exporting keys from CSP to to a binary large object (BLOB), so this data can be easily saved and transmitted.

If the sender has a public key of the receiver, it can make an encryption. Thus, data is encrypted by the sender using the receiver's public key (remember that the private and public keys are mathematically linked!) and decrypted by the receiver with its own private key. That's why such an encryption method is called 'asymmetric'. Obviously, they both have to utilize the same crypto provider. In the real world, the sender and the receiver may use asymmetric encoding to transmit the secret key (for 'symmetric' encryption), and then use it for the rest of an interactive session.

Coming back to the code, the very first step is to export public keys. For simplicity, consider a PDA-to-desktop encryption flow, so the desktop part has to export its public key:

BOOL CEncryptImpl::ExportKey(CFile& fileKey)
{
   BOOL         bResult = TRUE;
   HCRYPTKEY    hPublicKey = NULL;
   DWORD        dwKeyBlobLen = 0;
   CByteArray   baKeyBlob;
   if( !CryptGenKey(m_hProv,AT_KEYEXCHANGE,0,&hPublicKey))
   {
      TRACE(_T("Error during CryptGenKey: %Xn"),GetLastError());
      bResult = FALSE;
      goto CleanUp;
   }
   if(!CryptExportKey(hPublicKey,0,PUBLICKEYBLOB,0,NULL,
                      &dwKeyBlobLen))
   {
      TRACE(_T("Error during CryptExportKey-1: %Xn"),
            GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   baKeyBlob.SetSize(dwKeyBlobLen);
   if(!CryptExportKey(hPublicKey,0,PUBLICKEYBLOB,0,
      baKeyBlob.GetData(),&dwKeyBlobLen) )
   {
      TRACE(_T("Error during CryptExportKey-2: %Xn"),
            GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   fileKey.Write(&dwKeyBlobLen,sizeof(dwKeyBlobLen));
   fileKey.Write(baKeyBlob.GetData(),dwKeyBlobLen);
CleanUp:
   if ( hPublicKey )
      CryptDestroyKey (hPublicKey);
   return bResult;
}

You will find quite a lot of various algorithms for generating keys in the SDK docs, but not all of them are supported under Windows CE. You may experiment with different methods of key generation to find the one that best suits your needs. All samples in this article use default or CALC_RC2 algorithms.

After you place a file with the desktop public key on the PDA, a handheld can start encryption:

BOOL CEncryptImpl::EncryptData(CFile& fileKey, CFile& fileIn,
                               CFile& fileOut)
{
   BOOL         bResult = TRUE;
   HCRYPTKEY    hSessionKey = NULL;
   HCRYPTKEY    hExternPublicKey = NULL;
   DWORD        dwKeyBlobLen = 0;
   CByteArray   baKeyBlob;
   DWORD        dwCount = 0;
   BOOL         bEOF = 0;
   BYTE         pBuffer[BUFFER_SIZE];
   //////////////////////////////////////////////////////////////
   // 1. Get Random Session Key
   //////////////////////////////////////////////////////////////
   if(!CryptGenKey(m_hProv, CALG_RC2, CRYPT_EXPORTABLE,
                   &hSessionKey) )
   {
      TRACE(_T("Error during CryptGenKey: %Xn"),GetLastError());
      bResult = FALSE;
      goto CleanUp;
   }
   //////////////////////////////////////////////////////////////
   // 2. Obtain external public key
   //////////////////////////////////////////////////////////////
   if( fileKey.Read(&dwKeyBlobLen,sizeof(DWORD)) != sizeof(DWORD) )
   {
      TRACE(_T("Corrupted key file: %Xn"),GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   baKeyBlob.SetSize(dwKeyBlobLen);
   if ( fileKey.Read(baKeyBlob.GetData(),
        dwKeyBlobLen) != dwKeyBlobLen )
   {
      TRACE(_T("Corrupted key file: %Xn"),GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   if( !CryptImportKey(m_hProv,baKeyBlob.GetData(),dwKeyBlobLen,
                       0,0 ,&hExternPublicKey) )
   {
      TRACE(_T("Error during CryptImportKey: %Xn"),GetLastError ());
      return FALSE;
   }
   //////////////////////////////////////////////////////////////
   // 3. Export Session Key
   //////////////////////////////////////////////////////////////
   if(!CryptExportKey(hSessionKey,hExternPublicKey,SIMPLEBLOB,0,
                      NULL,&dwKeyBlobLen))
   {
      TRACE(_T("Error during CryptExportKey-1: %Xn"),GetLastError());
      bResult = FALSE;
      goto CleanUp;
   }
   baKeyBlob.SetSize(dwKeyBlobLen);
   if(!CryptExportKey(hSessionKey,hExternPublicKey,SIMPLEBLOB,0,
      baKeyBlob.GetData(),&dwKeyBlobLen))
   {
      TRACE(_T("Error during CryptExportKey-2: %Xn"),GetLastError());
      bResult = FALSE;
      goto CleanUp;
   }
   fileOut.Write(&dwKeyBlobLen,sizeof(dwKeyBlobLen));
    fileOut.Write(baKeyBlob.GetData(),dwKeyBlobLen);
   //////////////////////////////////////////////////////////////
   // 4. Encode Data
   //////////////////////////////////////////////////////////////
   memset(pBuffer,0,BUFFER_SIZE);
   do
   {
      dwCount = fileIn.Read(pBuffer, BLOCK_SIZE);
      if(dwCount < BLOCK_SIZE)
         bEOF = FALSE;
      else
         bEOF = TRUE;
      if (!CryptEncrypt (hSessionKey, 0, !bEOF, 0,
                         pBuffer, &dwCount, BUFFER_SIZE) )
      {
         TRACE(_T("Error during CryptEncrypt: %Xn"),GetLastError());
         bResult = FALSE;
         goto CleanUp;
      }
      fileOut.Write (pBuffer, dwCount );
   }
   while (bEOF);
   fileOut.Flush();
   //////////////////////////////////////////////////////////////
   // 5. Clean up
   //////////////////////////////////////////////////////////////
CleanUp:
   if ( hSessionKey )
      CryptDestroyKey (hSessionKey);
   if ( hExternPublicKey )
      CryptDestroyKey (hExternPublicKey);
   return bResult;
}

The desktop's public key is imported into the device's CSP and then the device's session key is exported to the encrypted file. This will allow a receiver to read it at the desktop and import into the desktop's CSP to perform a successful decryption. Please pay attention to the CryptEncrypt function. It takes several parameters that control its behavior. Usually, an encrypted message will require a bigger buffer than the original one, so you have to take care of the appropriate allocations.

The decryption process is similar to encryption:

BOOL CEncryptImpl::DecryptData(CFile& fileIn, CFile& fileOut)
{
   BOOL         bResult = TRUE;
   CRYPTKEY     hSessionKey = NULL;
   HCRYPTKEY    hPublicKey = NULL;
   DWORD        dwKeyBlobLen = 0;
   CByteArray   baKeyBlob;
   DWORD        dwCount = 0;
   BOOL         bEOF = 0;
   BYTE         pBuffer[BUFFER_SIZE];
   //////////////////////////////////////////////////////////////
   // 1. Obtain external public key
   //////////////////////////////////////////////////////////////
   if( fileIn.Read(&dwKeyBlobLen,sizeof(DWORD)) != sizeof(DWORD) )
   {
      TRACE(_T("Corrupted key file: 0x%Xn"),GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   baKeyBlob.SetSize(dwKeyBlobLen);
   if ( fileIn.Read(baKeyBlob.GetData(),
        dwKeyBlobLen) != dwKeyBlobLen )
   {
      TRACE(_T("Corrupted key file: 0x%Xn"),GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   if(!CryptGetUserKey(m_hProv,AT_KEYEXCHANGE,&hPublicKey))
   {
      TRACE(_T("CryptGetUserKey: 0x%Xn"),GetLastError ());
      bResult = FALSE;
      goto CleanUp;
   }
   if( !CryptImportKey(m_hProv,baKeyBlob.GetData(),dwKeyBlobLen,
                       hPublicKey,0,&hSessionKey) )
   {
      TRACE(_T("Error during CryptImportKey: 0x%Xn"),GetLastError ());
      return FALSE;
   }
   // Encrypt the source file and write to the destination file.
   do
   {
      // Read up to BLOCK_SIZE bytes from the source file.
      dwCount = fileIn.Read(pBuffer, BLOCK_SIZE);
      if(dwCount < BLOCK_SIZE)
         bEOF = FALSE;
      else
         bEOF = TRUE;
      // Decrypt the data.
      if ( !CryptDecrypt(hSessionKey, 0, !bEOF, 0,
                         pBuffer, &dwCount) )
      {
         TRACE(_T("Error during CryptDecrypt: 0x%Xn"),GetLastError());
         bResult = FALSE;
         goto CleanUp;
      }
      // Write the data to the destination file.
      fileOut.Write (pBuffer, dwCount );
   }
   while (bEOF);
   fileOut.Flush();
CleanUp:
   if ( hSessionKey )
      CryptDestroyKey (hSessionKey);
   return bResult;
}

All you need to do is to read and import the session key and then perform decryption.

Conclusion

This article very briefly covered the basics of Crypto API. Now, you have a good basis for your own experiments with different encryption techniques. You haven't yet learned how to sign and verify an encrypted message, but hopefully, the next article will deal with signatures and certificates.

Download

Download the accompanying code's zip file here.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date