Home > Delphi > CryptoAPI and hashing

CryptoAPI and hashing

February 10, 2014 Leave a comment Go to comments

I will openly admit that I have little experience with the Microsoft Cryptography API. My previous hands-on experience with using certificates with Delphi was panic strikken to say the least. I had to support HTTPS via Indy, using OpenSSL to do the job; and I found that Delphi’s support for modern certificate standards (back then) were quite thin on the ground. Also, using openSSL to encrypt streams of data was likewise an undocumented black art. I was able to piece together a working unit from snippets I found online mixed with my own C/C++ port – but it was a terrible solution to what must be considered a modern, everyday programming task.

Anyways, I am putting together a library to solve “common use” of the Microsoft Cryptography API. A set of routines that can be dropped into a project and used to support the most widely used features. Rather than simply porting the headers (which has been done by several programmers before me) I want simplified wrapper classes that are easy to use.

My own needs included, the most common requirements asked for by Delphi developers seems to be (in no particular order):

  • Generating a hash of a stream, string or memory buffer
  • Creating keys on the fly
  • Encrypting or decrypting a file based on a key
  • Signing a file using a key
  • Signing an XML document using a key
  • Signing a PDF document using a key
  • Signature verification

Note: The term “key” here is ambiguous, it may refer to a crypto-key or a crypto-hash. You dont encrypt using a password (old school) but rather by generating a unique hash of the password (if you are rolling your own security that is, certificates and keys have stricts guidelines for use.

My uncle’s hashed haggis

Right – on with the code! Generating a Hash from a pice of data is fairly straight forward, and it goes a little something like this:

  TCryptoAPI = Class
  protected
    class function  rsaMakeHash(const aHashType:ULONG;
          const HashBufLen:Integer;
          const aData:TStream;var aText:String):Boolean;
  public
    class function  rsaMakeSHAHash(const aData:TStream;var aText:String):Boolean;
    class function  rsaMakeMD5Hash(const aData:TStream;var aText:String):Boolean;
  End;

class function TCryptoAPI.rsaMakeHash(const aHashType:ULONG;
          const HashBufLen:Integer;
          const aData:TStream;var aText:String):Boolean;
var
  mProvider: HCRYPTPROV;
  mHash:  HCRYPTHASH;
  mTotal:   Int64;
  mRead:    Int64;
  mBuffer:  PByte;
  mHashBuffer: packed array[1..512] of byte;
  mHashByteLen:  Integer;
  x:  Integer;
Begin
  setLength(aText,0);
  result:=False;

  if aData<>NIL then
  begin
    if aData.Size>0 then
    begin
      aData.Position:=0;
      if CryptAcquireContext(@mProvider, nil, nil,
      PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) then
      Begin
        try
          if CryptCreateHash(mProvider,aHashType,0,0,@mHash) then
          begin
            try
              mBuffer:=Allocmem(1024);
              try
                mTotal:=aData.Size;
                repeat
                  mRead:=aData.Read(mBuffer^,SizeOf(mBuffer));
                  if mRead>0 then
                  begin
                    if not CryptHashData(mHash,mBuffer,mRead, 0) then
                    break;
                  end;
                  mTotal:=mTotal - mRead;
                until mTotal<1;

                mHashByteLen:=HashBufLen;
                if CryptGetHashParam(mHash, HP_HASHVAL, @mHashBuffer,
                @mHashByteLen, 0) then
                Begin
                  for x:=1 to mHashByteLen do
                  aText:=aText + IntToHex(mHashBuffer[x],2);
                  result:=True;
                end;
              finally
                FreeMem(mbuffer);
              end;
            finally
              CryptDestroyHash(mHash);
            end;
          end;
        finally
          (* Release crypto provider context *)
          CryptReleaseContext(mProvider, 0);
        end;
      end;
    end;
  end;
end;

class function TCryptoAPI.rsaMakeMD5Hash(const aData: TStream;
  var aText: String): Boolean;
Begin
  result:=TCryptoAPI.rsaMakeHash(CALG_MD5,16,aData,atext);
end;

class function TCryptoAPI.rsaMakeSHAHash(const aData:TStream;
      var aText:String):Boolean;
Begin
  result:=TCryptoAPI.rsaMakeHash(CALG_SHA1,20,aData,atext);
end;

Well, that wasnt to hard. Then of course comes the creation of keys, or a key-pair. You have one private key and one public key. That is fairly straightforward as well, although I need to get a full overview of the crypt flags (scavenger the C headers):

class function TCryptoAPI.rsaMakeKeys(var aPrivate,
  aPublic: TStream): Boolean;
const
  RSA1024BIT_KEY = $04000000;
var
  mProvider: HCRYPTPROV;
  mKeyPair: HCRYPTKEY;
  buflen: DWORD;
begin
  aPrivate:=NIL;
  aPublic:=NIL;
  result:=False;

  (* Get crypto-API context *)
  if CryptAcquireContext(@mProvider, nil, nil, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT) then
  Begin
    try
      if CryptGenKey(mProvider, AT_KEYEXCHANGE,
      RSA1024BIT_KEY or CRYPT_EXPORTABLE, @mKeyPair) then
      Begin
        try
          (* Query size of private buffer *)
          if CryptExportKey(mKeyPair, 0, PRIVATEKEYBLOB, 0, nil, @buflen) then
          Begin
            (* set private buffer to default size *)
            aPrivate:=TMemoryStream.Create;
            aPrivate.Size:=bufLen;

            (* export private key to buffer *)
            if CryptExportKey(mKeyPair, 0, PRIVATEKEYBLOB, 0,
            PByte(TMemoryStream(aPrivate).Memory), @buflen) then
            Begin

              (* Query size of pubic buffer *)
              if CryptExportKey(mKeyPair, 0, PUBLICKEYBLOB, 0, nil, @buflen) then
              Begin
                (* set public buffer to default size *)
                aPublic:=TMemoryStream.Create;
                aPublic.Size:=bufLen;

                (* export public key to buffer *)
                if CryptExportKey(mKeyPair, 0, PUBLICKEYBLOB, 0,
                PByte(TMemoryStream(aPublic).Memory), @buflen) then
                Begin
                  aPrivate.Position:=0;
                  aPublic.Position:=0;
                  result:=True;
                end else
                begin
                  FreeAndNIL(aPrivate);
                  FreeAndNIL(aPublic);
                  RaiseLastOSError;
                end;
              end;
            end else
            begin
              FreeAndNIL(aPrivate);
              RaiseLastOSError;
            end;
          end;
        finally
          (* Release key-pair *)
          CryptDestroyKey(mKeyPair);
        end;
      end;
    finally
      (* Release crypto provider context *)
      CryptReleaseContext(mProvider, 0);
    end;
  end;
end;

Now the fun really begins, namely to encrypt and decrypt something (!)

To be continued shortly …

  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: