Click here to Skip to main content
Click here to Skip to main content

Cryptography using the Win32 Cryptography API

, 9 Aug 2003
Rate this:
Please Sign up or sign in to vote.
An ATL COM Object Demonstrating How-to Use the Win32 Crypto API

Sample Image - ContextMenu.jpg

Overview

The purpose of this article is to demonstrate how easy it is to combine the Win32 Crypto API with a simple Windows Explorer context menu shell extension to give users a way to quickly encrypt and decrypt files through the windows shell. The Source code implements an in-proc COM server that provides two interfaces:

  • ICrypto: Provides general encryption/decryption and Base64 encoding/decoding methods
  • ICryptoShellExt: Uses ICrypto to extend the Windows Explorer context menu to provide four menu items shown in the screen shot above

I wrapped the Win32 cryptography behavior and Base64 encoding/decoding in a COM interface named ICrypto (Crypto.h/Crypto.cpp in the Source). The Base64 encoding/decoding implementation was taken from the Microsoft SOAP SDK. The only modification that I made was a slight enhancement to the decoding algorithm to increase decoding performance. ICryptoShellExt implements the shell extension interfaces necessary for extending the Windows Context menu. ICrypto provides general methods for encrypting/decrypting files, Base64 encoding/decoding and generating digital signatures.

Supported Platforms:

Windows '95 OSR2 (Internet Explorer 3.02 and higher)
Windows '98
Windows ME
Windows NT
Windows 2000

Full support for UNICODE

Win32 Crypto API Functions Used:

CryptCreateHash
CryptEncrypt
CryptDestroyKey
CryptDestroyHash
CryptDecrypt
CryptImportKey
CryptReleaseContext
CryptAcquireContext
CryptGetUserKey
CryptGenKey
CryptExportKey
CryptSignHash

ICrypto Methods:

    
    HRESULT 
    EncryptDoc( [in] BSTR bstrSrc, [in, optional] VARIANT varDestination );    
        // Description: Encrypts a file and stores the encrypted results 
        // in either another file or overwrites the same file

                          
    HRESULT 
    DecryptDoc( [in] BSTR bstrSrc, [in, optional] VARIANT varDestination );
        // Description: Decrypts a file and stores the decrypted results 
        // in either another file or overwrites the same file
                

    HRESULT 
    Base64EncodeString( [in] BSTR bstrSrc, [out, retval] BSTR* pbstrResult );    
        // Description: Base64 encodes the argument string and returns the 
        // result through the argument pbstrResult
    
    
    HRESULT 
    Base64DecodeString( [in] BSTR bstrSrc, [out, retval] BSTR* pbstrResult );    
        // Description: Base64 decodes the argument string and returns 
        // the result through the argument pbstrResult
        

    HRESULT 
    EncryptString( [in] BSTR bstrSrc, [out, retval]    BSTR* pbstrResult );    
        // Description: Encrypts the argument string and returns the result 
        // through the argument pbstrResult


    HRESULT 
    DecryptString( [in] BSTR bstrSrc, [out, retval]    BSTR* pbstrResult );
        // Description: Decrypts the argument string and returns the result 
        // through the argument pbstrResult
    

    HRESULT 
    VerifyDigitalSignature( [in] BSTR bstrDigSig, 
                            [out, retval] BOOL* pbMatches );
        // Description: Returns whether the last encryption/decryption 
        // operation generated the same digital signature as the argument 
        // through pbMatches.


    HRESULT 
    Base64EncodeFile( [in] BSTR bstrSrc, 
                      [in, optional] VARIANT varDestination );    
        // Description: Base64 encodes a file and stores the encoded results 
        // in either another file or overwrites the same file.


    HRESULT 
    Base64DecodeFile(  [in] BSTR bstrSrc, 
                       [in, optional] VARIANT varDestination );
        // Description: Base64 decodes a file and stores the decoded results 
        // in either another file or overwrites the same file.


    HRESULT 
    Base64EncodeFileToString( [in] BSTR bstrSrc, 
                              [out, retval] BSTR* pbstrResult );
        // Description: Base64 encodes a file and stores the encoded results
        // in the argument string


    HRESULT 
    Base64DecodeFileToString( [in] BSTR bstrSrc, 
                              [out, retval] BSTR* pbstrResult );
        // Description: Base64 decodes a file and stores the decoded results
        // in the argument string    
    
    

ICrypto Properties:

    
    HRESULT 
    DigitalSignature( [out, retval] BSTR* pbstrResult );    
        // Description: Returns the digital signature generated from the 
        // last encryption/decryption operation Base64 encoded
        
        
    HRESULT 
    get_ContainerName( [out, retval] BSTR* pbstrResult );    
        // Description: Returns the key container name for the CSP 
        // (Cryptographic Service Provider). 
        // This container name property should be an application unique 
        // string identifying the container that the CSP will use for storing
        // the keys


    HRESULT 
    put_ContainerName( [in] BSTR bstrContainerName );    
        // Description: Sets the key container name for the CSP 
        // (Cryptographic Service Provider). 
        // This container name property should be an application unique 
        // string identifying the container that the CSP will use for 
        // storing the keys
    
    

Digital Signatures

Digital signatures are calculated when the data is being encrypted using what the Crypto API documentation refers to as a hash. I maintain the hash object internally before doing any encryption or decryption. Once the encryption/decryption is complete and the digital signature is calculated, ICrypto provides access to it via the property named DigitalSignature. The DigitalSignature is stored internally as a BLOB. However, when a client requests the Digital Signature ICrypto passes it out as a Base64 encoded string (to ease use for scripting clients). Scripting clients typically have a difficult time with binary data (sometimes handled as a SAFEARRAY of variants).
Typically, you would perform the encryption on a file or memory, then immediate get the Digital Signature property from ICrypto and store it. Later when you decrypt the file or memory you would call VerifyDigitalSignature to determine if the digital signature matches (if the contents of the file or memory has changed since you encrypted).

ContainerName Property

The container name should be an application unique identifier for the key container. ICrypto uses this property when acquiring a handle to the context (CryptAcquireContext). If you fail to provide a container name, then ICrypto uses its own container name. Here's some code taken from the ICrypto implementation that shows how the container name is used during initialization:

BOOL CCrypto::InitializeContainer() const 
    { 
    USES_CONVERSION; 
    BOOL bSuccess = FALSE; 
    int nStringID= IDS_UNKNOWN_ERROR; 

    
    ATLASSERT( !m_hContext ); 
    bSuccess = CryptAcquireContext( &m_hContext, 
              W2T(m_bstrContainerName.m_str), MS_DEF_PROV, PROV_RSA_FULL, 0 ); 
    
     // Container doesn't exist - Possibly try to create a new container 
    if( !bSuccess ) 
        { 
        int nLastError = GetLastError(); 
        
        if( nLastError == NTE_BAD_KEYSET ) 
            { 
                // Creating the key container for the first time 
            bSuccess = CryptAcquireContext( &m_hContext, 
                          W2T(m_bstrContainerName.m_str), MS_DEF_PROV, 
                          PROV_RSA_FULL, CRYPT_NEWKEYSET ); 
            if( !bSuccess ) 
             nStringID = IDS_ERROR_INITIALIZATION_CREATE_NEW_CONTAINER_FAILED; 
            } 
        else nStringID = LookupCryptErrorStringID( nLastError ); 
        } 
    if( !bSuccess ) 
        CRYPTO_OUTPUT_DEBUGSTRING( nStringID ); 
    
    return bSuccess; 
    }  

Initialization

Once I "finished" the first revision of the implementation I decided it was time to start testing on various Windows platforms (other than my production machine). The first platform that I tried was a clean machine running Windows '98 SP1. The test harness immediately failed. After several hours of debugging and reading, I discovered an important article in MSDN regarding initialization of the key container. I found a sample in the knowledge base for "best practices" for initialization and basically cut-and-pasted that sample into ICrypto. Be sure to see how CCrypto::Initialize() works if you plan to use the Win32 Crypto API.

Samples:

The following code samples demonstrate how to use ICrypto with a C++ and Java Script client. See also CryptoShellExt.cpp in the Source code for another sample in ATL/C++.

ICrypto C++ Client

How to use ICrypto with a C++ Client:

Steps:

  1. Download the source, unzip it to a folder and build the project
  2. Be sure the CryptoAPI.dll is located somewhere in your include path 
  3. Import the type information for ICrypto by inserting the following code in the source file in your project that is going to use ICrypto:
    #import "CryptoAPI.DLL" named_guids raw_interfaces_only
  4. Create the instance of ICrypto and initialize the container name for your application (for more information about Cryptographic Service Provider key container names, see: http://msdn.microsoft.com/library/psdk/crypto/cryptoref1_0wvo.htm)
    .
    .
    CComPtr<ICrypto> pCrypto;
    
    if( SUCCEEDED( pCrypto.CoCreateInstance( CLSID_Crypto ) ) )
       {
       ATLASSERT( pCrypto != NULL );
       if( pCrypto )
          {
          pCrypto->put_ContainerName( L"MY_APPLICATION_CSP_CONTAINER_NAME" );
    .
    .
    .  
  5. Use the functions in the API to encrypt/decrypt files:
    if( SUCCEEDED( pCrypto->EncryptDoc( bstrFileName, vtMissing ) )
       {
          // Grab the digital signature to verify the file during decryption
          // later
       pCrypto->get_DigitalSignature( &bstrDigitalSignature );
       }
    else
       {
       IErrorInfo* pErrorInfo = NULL;
       if( ::GetErrorInfo( 0, &pErrorInfo ) == S_OK &&  pErrorInfo  != NULL) 
        {
        CComBSTR    bstrErrorMsg;
            
        pErrorInfo->GetDescription( &bstrErrorMsg );
        ::MessageBox( NULL, _bstr_t(bstrErrorMsg.m_str), NULL, 
                      MB_OK | MB_ICONEXCLAMATION );
        pErrorInfo->Release();
        }       
       }

ICrypto Java Script Client

How to use ICrypto with a Java Script Client:

Encrypting a File

function OnEncryptFile()
   {
   try
      {
      var Crypto = new ActiveXObject( "CryptoAPI.Crypto" );

      Crypto.ContainerName = "MY_APPLICATION_CSP_CONTAINER_NAME";
      Crypto.EncryptDoc( g_strFileToEncrypt );
      g_strDigitalSignature = Crypto.DigitalSignature;
      delete Crypto;
      }
   catch( exception )
      {
      window.alert( exception.description );
      }
  

Verifying the Digital Signature

function OnVerifySignature()
   {
   try
      {
      var Crypto = new ActiveXObject( "CryptoAPI.Crypto" );

      Crypto.ContainerName = "MY_APPLICATION_CSP_CONTAINER_NAME";
      Crypto.DecryptDoc( g_strFileToDecrypt );
      if( !Crypto.VerifyDigitalSignature( g_strDigitalSignature ) )
         {
         // The digital signature doesn't match - The file has been modified
         .
         .
         .
         }
      delete Crypto;
      }
   catch( exception )
      {
      window.alert( exception.description );
      }

In-memory Encryption Binary Format

 

File Encryption Binary Format

 

ICryptoShellExt

Guarding Against Multiple Encryptions

Guarding against the user encrypting a file more than once was an interesting problem to solve. CryptEncrypt returns NTE_DOUBLE_ENCRYPT when you try to encrypt data more than once. However, since ICrypto adds other information to the file (i.e. version, key size) it would need to try and extract the encrypted data and run it through CryptEncrypt to check the return code.

After coming up with a couple of other alternatives (that I didn't like), I decided to run the problem by one of my colleagues. He immediately came up with multiple solutions. One of which I used in ICryptoShellExt. Before encrypting a file, he suggested that I attempt to decrypt the file to a temp file. If the decryption was indeed successful, then the file was already encrypted. Otherwise, the file indeed needed to be encrypted for the first time. Here is the code that handles this case in ICryptoShellExt:

.
.
.

//    Change to hour glass icon
HCURSOR    hCursor = SetCursor( LoadCursor( NULL, IDC_WAIT ) );
switch( Action )
    {
    case CCryptoShellExt::cryptEncrypt:
        {
        USES_CONVERSION;
        _variant_t    varDblEncryptFilename = GetDblCryptTempFilename();

        //    Try to decrypt first to guard against multiple encryptions
        if( FAILED( pCrypto->DecryptDoc( m_pbstrFiles[ nIndex ].m_str, 
                                         varDblEncryptFilename ) ) )
        {
            ( lpctstrDestination != NULL ) ?
                CHECK_HR( pCrypto->EncryptDoc( 
               m_pbstrFiles[ nIndex ].m_str, _variant_t(bstrDestination) ) ):
                CHECK_HR( pCrypto->EncryptDoc( m_pbstrFiles[ nIndex ].m_str, 
                          vtMissing ) );
            }

            //    Cleanup the intermediate file
            _unlink( W2A(varDblEncryptFilename.bstrVal) );                                
            break;
        }
.
.
.

Further Enhancements

The shell extension could easily be extended to include a quick view feature to display the decrypted contents using the associated viewer associated with the file type using ShellExecute(...).

There are several samples in MSDN that demonstrate how to hash in a password during encryption. The shell extension and ICrypto implementations could be enhanced to provide a way to password protect encrypted files. The examples show how to hash in a password as part of the signed hash.

Referring to the in-memory binary format, the private key is included in the encrypted results which increases the size of the result by approximately 76 bytes (typical size of the encrypted private key). Splitting the private key from the in-memory encrypted string could be accomplished by changing the ICrypto interface to return the encrypted string and private key separately. 

ICrypto could be enhanced to include properties for setting the CSP algorithm used for hashing and encrypting block and stream ciphers. It currently uses CALG_RC2 for block encryption, CALG_RC4 for stream encryption and CALG_MD5 for hashing. See MSDN for more information about the different algorithms supported by the Win32 crypto API.

ICrypto could be enhanced to include properties for tweaking the number of bytes read from a file at-a-time. For large files, the implementation could be altered to incorporate caching and reading larger blocks of data for enhancing performance. The current implementation reads in 512 bytes at-a-time.

Reporting Defects and Suggestions

Please report all defects to me by email: mailto:slater_chad@hotmail.com

Please also feel free to submit suggestions and comments. I wrote this article in hopes to help others and to gain feedback from other developers. Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Chad Slater
Technical Lead Extensis
United States United States
I'm a technical lead working at Extensis in Portland OR. I'm currently working with Visual Studio 2010, C# and cross-platform C++ for Mac OS X and Windows.
 
I love coding and working with smart, easy-going engineers

Comments and Discussions

 
QuestionDoes this work with Windows Server 2008 x64? PinmemberKovorka19-Jan-12 22:51 
QuestionA question Pinmemberlangziwuwu8-Nov-11 15:35 
GeneralNot able to complie in VS2008 PinmemberSiva Koyi24-Mar-10 1:07 
GeneralGreat! PinmemberMurat DOGANCAY9-Jan-10 4:52 
NewsBug In Base64Encoder PinmemberJonghwa Lee30-Jan-08 22:06 
QuestionCryptAcquireContext() on windows 98 Pinmemberrajeevktripathi9-Feb-06 2:37 
AnswerRe: CryptAcquireContext() on windows 98 PinmemberMaurizio Vairani17-Mar-10 1:16 
Generalencryption using rsa algorithms Pinmembersukhendra singh8-Oct-05 7:41 
GeneralUrgent : how to create a exportable key PinmemberKarthik Murugan10-Jan-05 17:23 
GeneralRe: Urgent : how to create a exportable key PinmemberKarthik Murugan11-Jan-05 16:59 
GeneralPKCS7 sign PinmemberAndré Luis13-Dec-04 7:33 
GeneralI am New TO CryptoGraphy PinmemberThatsAlok5-Dec-04 20:44 
Generalpost to codeguru PinsussAnonymous1-Dec-04 8:35 
Question::InlineIsEqualGUID() not exist? PinmemberWatcher_bj29-Apr-04 22:34 
AnswerRe: ::InlineIsEqualGUID() not exist? PinmemberChristoph Bail19-Aug-05 7:03 
AnswerRe: ::InlineIsEqualGUID() not exist? Pinmemberdaleboy23-Nov-05 17:51 
AnswerRe: ::InlineIsEqualGUID() not exist? Pinmemberjangmoonjae1-Aug-07 14:34 
GeneralCrypt API with Mixed platforms Pinmemberbsyossi28-Apr-04 3:45 
Generalfile getting corupted Pinsusssachin tandon12-Apr-04 6:06 
GeneralRe: file getting corupted PinmemberPjNetFire10-Aug-07 18:41 
GeneralEncrypt/Decrypt on different machines PinmemberBharat Bansal14-Mar-04 23:13 
GeneralOn they fly decryption Pinmembersumudu17-Aug-03 6:51 
GeneralPlease help!! Pinmemberadsilva4-Aug-03 20:11 
GeneralSmartCard PinsussRkumarJP23-Jun-03 12:04 
GeneralWho can help me Pinmemberwsnb123456715-Mar-03 1:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 10 Aug 2003
Article Copyright 2001 by Chad Slater
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid