HashCryptStreams Using Hash and Cryptography objects
 
Both HashObject and Symmetic objects have similar usage patterns. Any differences come from the fact that the first (HashObject) only inspects the data to generate a hash or HMAC digest while the second (Symmetric) encrypts the data and you get a cipher as a result. So, there is an easy way and a hard way for the both of them. The easy way needs minimal code but may cost memory consumption, while the hard way enables you to split the process of hashing or encryption/decryption in chunks at the cost of a little more coding. 

The easy way - hash/en/de/crypt in single step
The hard way - hash/en/de/crypt in chunks
The block size - what you need to take care of when using symmetric algoruthms.

The Easy Way

Lets begin with the HashObject. You need to configure it - set the algorithm, a key if HMAC is to be generated and use it. In the examples below we will illustrate the usage in a scenario where a file is processed. This gives a nice and natural base for comparison between the different kinds of usage. Still, the same general usage pattern will occur with other data - from a database, from network connection and so on.

Set ho = Server.CreateObject("newObjects.crypt.HashObject")
' In NSBasic this would be:
' AddObject "newObjects.crypt.HashObject", ho
' in other environments the way you create the object may differ - check their
' documentation about how COM objects are created.
ho.InitHash "MD5" ' select MD5 algorithm
' Now open a file
Set sf = Server.CreateObject("newObjects.utilctls.SFMain")
Set file = sf.OpenFile("somefile",&H40) ' open it for reading only
' Now read all the file and hash it
binFileData = file.ReadBin(file.Size)
ho.HashData binFileData
' What remains is to get the hash
hash = ho.Value ' If we want it as hexdecimal string
' hash = ho.BinaryValue ' if we want it as binary data
...  

We read the file in binary mode (by using ReadBin and not ReadText). This is usually good for any kind of file. Sometimes the data must be processed as text explicitly. Then we need to know or establish a rule about how to convert it before processing. Why? Imagine you want to hash a text file - it can be in UNICODE format, but it can be in a singlebyte/multibyte encoding as well. Now the other party that will verify the hash may want to keep the data not in the format you have it - for example save it as ANSI (singlebyte encoding - the typical format for a text file in Windows) while you have it as UNICODE (for example directly obtained from a database query). Obviously the hash generated over the UNICODE text data will differ from the hash generated over the ANSI text data, because on a binary level they are different. So you need to impose a rule or obey an existing one when generating the hash. This explains the second parameter of the HashData method - code page. 

How this looks with symmetric encryption? The major difference is that you will not only pass data but also get encrypted/decrypted data back and do something with it. To illustrate this we will read one file encrypt it and write the encrypted data to another file:

Set cr = Server.CreateObject("newObjects.crypt.Symmetric")
' In NSBasic this would be:
' AddObject "newObjects.crypt.Symmetric", cr
' in other environments the way you create the object may differ - check their
' documentation about how COM objects are created.
cr.Init "DES" ' select DES algorithm
' Set a key
cr.Key = "0123456789ABCDEF"
' Configure padding
cr.PadType = -1 ' Select random bytes padding
' Now open a file
Set sf = Server.CreateObject("newObjects.utilctls.SFMain")
Set file = sf.OpenFile("somefile",&H40) ' open it for reading only
' Remember the file size
originalFileSize = file.Size 
' Now read all the file and hash it
binFileData = file.ReadBin(file.Size)
cipher = cr.Encrypt(binFileData)
' What remains is to save/send somewhere the cipher
' Note! The file size we recorded above is important!
' The size of the cipher is most probably not the same
' as the size of the original data because of the algorithm's
' block size. Thus when we decrypt we will need to know
' the original size and truncate the result.
...  

Note the comments about the block size and the need to know the size of the original data. Most symmetric algorithms process data in blocks and respectively the cipher they produce is not the same size as the original, but is multiple of the algorithm's block size. You may ask - "well but there are components and API-s that offer encryption/decryption without additional parameters". To achieve this you need a convention that will pack additional data with the data you want to encrypt. There are standards about that, but there are also many exceptions,  custom implementations and even alternate standards. With HashCryptStreams library you can implement any of them or devise your own scheme. As you may already guess the simplest method is to encrypt the file size with he file contents - most convenient is to put the size in a fixed size field before the file contents. An example doing that is shown in the next section.

The decryption is actually much the same - you just use the Decrypt method instead of Encrypt and keep in mind that the cipher's size is multiple of the block size and not the size of the original data. 

The Hard Way 

When the data is too much using the one-call approach will cost too much memory. So, the obvious way to go is performing the operation in multiple steps passing the data in chunks. In HashObject this is done through the HashUpdate and HashFinalize methods. The first one passes a chunk of data for processing and HashFinalize is called once - after the last chunk of data. The example from above re-written to use this approach will look like this:

Set ho = Server.CreateObject("newObjects.crypt.HashObject")
' In NSBasic this would be:
' AddObject "newObjects.crypt.HashObject", ho
' in other environments the way you create the object may differ - check their
' documentation about how COM objects are created.
ho.InitHash "MD5" ' select MD5 algorithm
' Now open a file
Set sf = Server.CreateObject("newObjects.utilctls.SFMain")
Set file = sf.OpenFile("somefile",&H40) ' open it for reading only
' Now read the file in a cycle and hash it
While Not file.EOS
  binFileData = file.ReadBin(256)
  ho.HashUpdate binFileData    
Wend
ho.HashFinalize
' What remains is to get the hash
hash = ho.Value ' If we want it as hexdecimal string
' hash = ho.BinaryValue ' if we want it as binary data
...  

So, we read 256 bytes from the file each turn and pass it to the HashObject. Finally when everything is read we call HashFinalize once and we have the digest in the same properties as before.

When a new HashUpdate/HashFinalize can be started? When you need to perform the operation several times over different data you will need to know how to reuse the object (i.e. perform the same without need to re-create it again). It is simple - just call Reset and start again. If the operation you have performed before is HMAC and you want to get rid of the key (i.e. the next operation will be hash and not HMAC) you call ResetKey instead.

Now lets do the same with the Symmetric encryption object. In order to illustrate a sensible usage we will also include the data size with the data passed for encryption and then recover it when decrypting.

Encryption
We assume that some of the variables (such as the file names, encryption keys etc.) are already initialized with appropriate values. 

' Create the objects we need
Set crypt = CreateObject("newObjects.crypt.Symmetric")
Set sf = CreateObject("newObjects.utilctls.SFMain")
Set sfbd = CreateObject("newObjects.utilctls.SFBinaryData")
' Open the files
Set infile = sf.OpenFile(infileName,&H40)
Set outfile = sf.CreateFile(outfileName)
' Init the Symmetric encryption object
crypt.Init alg ' alg is a string - the name of one of the supported algorithms 
crypt.Key = key ' is a key appropriate for the selected algorithm
crypt.PadType = -1
' We use 4 byte field to hold an long integer (4 byte) number
' So allocate 4 bytes
sfbd.Size = 4
' Set a big endian byte order (not that little endian is bad ;)
sfbd.ByteOrder = &H02
sfbd.Unit(0,vbLong) = infile.Size
' Write it first in the output
outfile.WriteBin crypt.Encrypt(sfbd.Value,False)
' Encrypt the file
While Not infile.EOS
  chunk = infile.ReadBin(256)
  If Not infile.EOS Then
    cipher = crypt.Encrypt(chunk,False)
  Else
    cipher = crypt.Encrypt(chunk,True)
  End If
  ' Write to the output file
  outfile.WriteBin cipher 
Wend
' We are done - we can close the files and move on

This example shows pretty well what kind of other components are often needed when working with the HashCryptStreams library. The SFBinaryData object is one of those that you will almost always need. It provides advanced manipulation of binary data blocks, with regard to the byte order and the field sizes. When you need to follow a standard or catch up with another application that uses its own custom encryption scheme you will need to prepare or/and read the header data packed with the actual data. Almost always this will be a binary data block with some fields with certain sizes. For example a field for data size, a field for a file name (with limited size) and so on. 

Note that the objects from the HashCryptStreams library seemingly support some of the operations provided by the SFBinaryData object. However there is no point in duplicating its features in every object that may need them. Therefore the features supported by the HashCryptStream objects are limited to the minimal set that would make their usage convenient by enabling you to init them with already prepared data. They do not support themselves the details you may need in the most real world situations (for example there is no byte order control in them, nor bit-wise operations over the data). Thus the SFBinaryData is a must have object in almost any encryption related code. The above example is very basic, it is very likely that you would want to pack more metadata with the encrypted content, some standards or specific application requirements will need even more complicated operations. When you are building code that works with them you will need to obey those requirements and a binary buffer manipulation is something you would need.

What about the arrays or the strings in VBScript, NSBasic? You may know some examples that use them to deal with binary data and be tempted to go that way. The problem is that the high level languages are type-less and even if the VB like ones offer some ways to convert the data to the right type these features are not complete and cover only part of the tasks you will need to perform. Furthermore the chance to make a mistake using type-less tools is very high and often hard to find. For example an array defined as Dim arr(10) is array of variants, no matter if you assign bytes to its elements. Nothing would prevent the application from assigning to some elements other data (for example string or a floating point number). Using ChrB/MidB/AscB and the other "B" functions in VBScript may look promising, but you will get in trouble when you need to deal with bigger than byte fields and use a byte order that is not native for the operating system.

After commenting all these details lets return to the example and perform the reverse operation - decryption.

Decryption

' Create the objects we need
Set crypt = CreateObject("newObjects.crypt.Symmetric")
Set sf = CreateObject("newObjects.utilctls.SFMain")
Set sfbd = CreateObject("newObjects.utilctls.SFBinaryData")
' Open the files
Set infile = sf.OpenFile(infileName,&H40)
Set outfile = sf.CreateFile(outfileName)
' Init the Symmetric encryption object
crypt.Init alg
crypt.Key = key
' We will decrypt only - so there is no need to care about the padding method this time.
' The encrypted file contains the original file size in its first 4 bytes
' However we cannot read just 4 bytes because the block size can be bigger
' (usually is). So, to ensure we need to read entire block in a memory stream.
Set mem = sf.CreateMemoryStream
' How much to read, better read more than needed than less
blocksToRead = (crypt.BlockSize / 4) + 1
mem.WriteBin crypt.Decrypt(infile.ReadBin(crypt.BlockSize),False)
' Why memory stream? Well, you can use SFBinaryData for everything.
' we just used that in this example to make the task easy to understand
' Now use the binary data object to consume the size
sfbd.Size = 4
mem.Pos = 0
sfbd.Value = mem.ReadBin(4)
' Now get what we need and write the rest to the output
sfbd.ByteOrder = &H02
FileSize = sfbd.Unit(0,vbLong)
' Write the rest of the block to the output
mem.CopyTo outfile, crypt.BlockSize ' The number of bytes just needs to be bigger than the actual content
' mem is no longer needed lets dispose of it explicitly (not actually needed, 
' it will be freed as the script completes anyway.
Set mem = Nothing
' Decrypt the rest
Do
  chunk = infile.ReadBin(256)
  If Not infile.EOS Then
    bindata = crypt.Decrypt(chunk,False)
  Else
    bindata = crypt.Decrypt(chunk,True)
  End If
  ' Write to the output file
  outfile.WriteBin bindata
Loop While Not infile.EOS
' Truncate the output file to the size we read before
outfile.Size = FileSize

If you think the above code is too long for such a task - remember we are doing everything on our own here. Typically you will pack such tasks in functions and call them as needed. If there were only predefined en/de-cryption oprations doing this or something else implicitly you would not have the chance to handle encrypted data that encodes the additional information in other manner than the built-in one.

newObjects Copyright 2001-2006 newObjects [ ]