Encrypting and Decrypting Data by Using a Password

Encrypting and decrypting data is achieved by calling CryptEncrypt and CryptDecrypt. These two functions require as an input a handle to a key that is used for encrypting or decrypting the data. The easiest way to get a handle to a key is to derive one using a password. This section discusses how to perform encryption and decryption by deriving the keys with passwords.

The simplest way to perform encryption and decryption with a password is to use the ManagedCryptoAPI class to do all of the work for you. To encrypt data using a password as the encryption key, follow these steps:

1.
Acquire an instance of the ManagedCryptoAPI class.

2.
Acquire byte arrays for text to encrypt and acquire the text password.

3.
Acquire a handle to a key container in a CSP by calling ManagedCryptoAPI.AcquireNamedContext or ManagedCryptoAPI.AcquireDefaultContext.

4.
Acquire the encrypted bytes by calling ManagedCryptoAPI.PasswordEncrypt.

5.
PasswordEncrypt returns an array of bytes holding the encrypted data. The Int32 that is passed by reference into PasswordEncrypt holds the number of bytes in the returned array that are actual encrypted data. The size of the returned array can be greater than the number of bytes that actually hold encrypted data.

BE CAREFUL WITH PASSWORD BASED ENCRYPTION

Although password-based encryption and decryption is convenient, the resulting encrypted text is not necessarily portable to other devices. If your application calls for encrypting and decrypting data that can be securely exchanged between two devices, see the section titled “Encrypting and Decrypting by Using a Handle to a Session Key.”


Listing 14.5 demonstrates how to encrypt data by using a password. It is taken from the sample application EncryptionDemo. The sample code acquires the data to be encrypted and the password from the user interface and then paints the encrypted byte values into the user interface.

Listing 14.5. How to encrypt data by using a password
C#
ManagedCryptoAPI l_Crypto = new ManagedCryptoAPI();

byte[] l_PlainTextBytes = System.Text.Encoding.ASCII.GetBytes
        (this.txtToEncrypt.Text);
byte[] l_PasswordBytes = System.Text.Encoding.ASCII.GetBytes
        (this.txtPassword.Text);

IntPtr l_hProvider = l_Crypto.AcquireNamedContext("KICKSTART");
m_TotalEncryptedBytes = 0;
m_EncryptedBytes = l_Crypto.PasswordEncrypt(l_PlainTextBytes, l_hProvider,
        l_PasswordBytes, ref m_TotalEncryptedBytes);

this.txtEncryptedBytes.Text = "";
for (int i = 0; i < m_TotalEncryptedBytes; i++)
{
    this.txtEncryptedBytes.Text += "[" + m_EncryptedBytes[i] + "] ";
}

VB
Dim l_Crypto As ManagedCryptoAPI = New ManagedCryptoAPI

Dim l_PlainTextBytes() As Byte = System.Text.Encoding.ASCII.GetBytes
         (Me.txtToEncrypt.Text)
Dim l_PasswordBytes() As Byte = System.Text.Encoding.ASCII.GetBytes
         (Me.txtPassword.Text)

Dim l_hProvider As IntPtr = l_Crypto.AcquireNamedContext("KICKSTART")
m_TotalEncryptedBytes = 0
m_EncryptedBytes = l_Crypto.PasswordEncrypt(l_PlainTextBytes, l_hProvider,
        l_PasswordBytes, m_TotalEncryptedBytes)

Me.txtEncryptedBytes.Text = ""

Dim i As Integer
For i = 0 To m_TotalEncryptedBytes - 1
    Me.txtEncryptedBytes.Text += "[" + Convert.ToString(m_EncryptedBytes(i))
         + "] "
Next i

To decrypt data by using a password as the encryption key, follow these steps:

1.
Acquire an instance of the ManagedCryptoAPI class.

2.
Acquire byte arrays for text to decrypt and the password.

3.
Acquire a handle to a key container in a CSP by calling ManagedCryptoAPI.AcquireNamedContext or ManagedCryptoAPI.AcquireDefaultContext.

4.
Decrypt the data by calling ManagedCryptoAPI.PasswordDecrypt.

5.
PasswordDecrypt returns an array of bytes holding the decrypted data. The Int32 that is passed by reference into PasswordDecrypt holds the number of bytes in the returned array that are actual decrypted data. The size of the returned array can be greater than the number of bytes that actually hold decrypted data.

Listing 14.6 demonstrates how to decrypt data by using a password as the decryption key. It is taken from the sample application EncryptionDemo.

Listing 14.6. How to decrypt data using a password as the decryption key
C#
ManagedCryptoAPI l_Crypto = new ManagedCryptoAPI();

byte[] l_PasswordBytes = System.Text.Encoding.ASCII.GetBytes
         (this.txtPassword.Text);

IntPtr l_hProvider = l_Crypto.AcquireNamedContext("KICKSTART");

Int32 l_TotalDecryptedBytes = 0;
byte[] l_DecryptedBytes = l_Crypto.PasswordDecrypt(m_EncryptedBytes,
        m_TotalEncryptedBytes, l_hProvider, l_PasswordBytes,
        ref l_TotalDecryptedBytes);

this.txtDecryptedText.Text = System.Text.Encoding.ASCII.GetString
        (l_DecryptedBytes, 0, l_DecryptedBytes.Length);

VB
Dim l_Crypto As ManagedCryptoAPI = New ManagedCryptoAPI
Dim l_PasswordBytes() As Byte = System.Text.Encoding.ASCII.GetBytes
         (Me.txtPassword.Text)

Dim l_hProvider As IntPtr = l_Crypto.AcquireNamedContext("KICKSTART")

Dim l_TotalDecryptedBytes As Int32 = 0
Dim l_DecryptedBytes() As Byte = l_Crypto.PasswordDecrypt(m_EncryptedBytes,
        m_TotalEncryptedBytes, l_hProvider, l_PasswordBytes,
        l_TotalDecryptedBytes)

Me.txtDecryptedText.Text = System.Text.Encoding.ASCII.GetString
         (l_DecryptedBytes, 0, l_DecryptedBytes.Length)

Looking Inside PasswordEncrypt

Examining the code inside PasswordEncrypt teaches how to encrypt data by using a password as the basis for the encryption key. PasswordEncrypt encrypts the data passed into it, using the following steps:

1.
A handle to a new hash object is obtained by calling the CryptoAPI function CreateHash. CreateHash is passed a handle to the key container of the CSP, which was passed in to the PasswordEncrypt method. The handle to the hash object is stored in the l_hHash variable.

2.
A hash is computed for the password bytes by calling the CryptoAPI function CryptHashData. The l_hHash handle is passed into CryptHashData.

3.
An encryption key for the CALG_RC4 algorithm is derived based on the hash by calling the CryptoAPI function CryptDeriveKey. The l_hHash handle is passed into CryptDeriveKey and a handle to an encryption key named l_hKey is passed out of CryptDeriveKey.

4.
A byte array named l_encryptedBytes is set up. It is large enough to hold the encrypted data even if there is some overflow from the encryption process.

5.
The data is encrypted by calling the CryptoAPI function CryptEncrypt. The handle to the encryption key, l_hKey is passed into CryptEncrypt.

6.
CryptEncrypt fills l_encryptedBytes with encrypted data and sets the value out_NumEncryptedBytes with the number of bytes inside l_encryptedBytes with encrypted data.

7.
l_encryptedBytes is returned to the caller.

The source code for PasswordEncrypt is shown in Listing 14.7.

Listing 14.7. PasswordEncrypt
C#
// Encrypts data by using the bytes passed in as the key for the encryption
// This method allocates enough memory to encrypt the data even if CryptoAPI
// needs more bytes than in_BytesToEncrypt holds. The allocation is done
// dynamically
public byte[] PasswordEncrypt(byte [] in_BytesToEncrypt, IntPtr in_hProvider,
        byte[] in_passwordBytes, ref Int32 out_NumEncryptedBytes)
{
   byte[] l_encryptedBytes = new byte[2 * in_BytesToEncrypt.Length];
   // We are not going to catch exceptions. If things go wrong,
   // any system-generated exceptions might help the user of this code
   // understand more about what is going on. We need a finally clause because
   // we want to release the CryptoAPI resources if something goes wrong
   try
   {
      // Step 1: Get a handle to a new hash object that will hash
      // the password bytes
      IntPtr l_hHash = IntPtr.Zero;
      if (!CryptCreateHash(in_hProvider, CALG_MD5, IntPtr.Zero, 0,
              ref l_hHash))
      {
         throw new Exception("Could not create a hash object!");
      }

      // Step 2: hash the password data....
      // l_hHash - reference to hash object
      // in_passwordBytes - bytes to add to hash
      // in_passwordBytes.Length - length of data to compute hash on
      // 0 - extra flags

      // Note: We don't actually get the hash bytes back, we just have a
      // handle to the hash object that did the computation.  It is holding
      // those bytes internally, so we don't want or need them
      if (!CryptHashData(l_hHash, in_passwordBytes, in_passwordBytes.Length,
              0))
      {
         throw new Exception("Failure while hashing password bytes!");
      }

      // Step 3: Derive an encryption key based on hashed data
      // in_hProvider - Handle to provider we previously acquired
      // CALG_RC4 - Popular encryption algorithm
      // l_hHash - Handle to hash object which will hand over the
      //  hash as part of the key derivation
      // CRYPT_EXPORTABLE - Means the key's bytes could be exported into a
      //  byte array, using CryptExportKey
      // l_hKey - Handle to the key we are deriving
      IntPtr l_hKey = IntPtr.Zero;
      if (!CryptDeriveKey(in_hProvider, CALG_RC4, l_hHash, CRYPT_EXPORTABLE,
               ref l_hKey))
      {
         throw new Exception("Failure when trying to derive the key!");
      }

      // Step 4: Acquire enough memory to assure that encryption succeeds
      // even allowing for some overflow
      for (int i = 0; i < in_BytesToEncrypt.Length; i++)
      {
         l_encryptedBytes[i] = in_BytesToEncrypt[i];
      }
      out_NumEncryptedBytes = in_BytesToEncrypt.Length;

      // Step 5: Do the encryption
      // l_hKey - Previously acquired key for encryption
      // IntPtr.Zero - Indicates that we don't want any additional hashing
      // true - Passed in because this is the only and last data to be
      // encrypted in this session
      // 0 - Additional flags (none)
      // l_encryptedBytes - in/out - bytes to be encrypted in place in this
      //    buffer
      // l_datalength - Length of data (number of bytes) to be encrypted
      // l_encryptedBytes.Length - Lets CryptoAPI know how big the buffer it.
      //  2X data size is plenty
      if (!CryptEncrypt(l_hKey, IntPtr.Zero, true ,0, l_encryptedBytes,
              ref out_NumEncryptedBytes,I encryptedBytes.Length))
      {
         throw new Exception("Failure when calling CryptEncrypt!");
      }
   }
   finally
   {
      // Release resources
   }
   return l_encryptedBytes;
}

VB
' Encrypts data by using the bytes passed in as the key for the encryption
' This method allocates enough memory to encrypt the data even if CryptoAPI
' needs more bytes than in_BytesToEncrypt holds. The allocation is
' done dynamically
Public Function PasswordEncrypt(ByVal in_BytesToEncrypt() As Byte, ByVal
        in_hProvider As IntPtr, ByVal in_passwordBytes() As Byte, ByRef
        out_NumEncryptedBytes As Int32) As Byte()
   Dim l_encryptedBytes(2 * in_BytesToEncrypt.Length) As Byte ' = New Byte(2 *
        in_BytesToEncrypt.Length)

   ' We are not going to catch exceptions. If things go wrong, any
   ' system-generated exceptions might help the user of this code understand
   ' more about what is going on. We need a finally clause because we want
   ' to release the CryptoAPI resources if something goes wrong
   Try
      ' Step 1: Get a handle to a new hash object that will hash the
      ' password bytes
      Dim l_hHash As IntPtr = IntPtr.Zero

      If (CryptCreateHash(in_hProvider, Convert.ToInt32(CALG_MD5), IntPtr.Zero,
               Convert.ToUInt32(0), l_hHash) = False) Then
         Throw New Exception("Could not create a hash object!")
      End If

      ' Step 2: hash the password data....
      ' l_hHash - reference to hash object   in_passwordBytes –
      ' bytes to add to hash
      ' in_passwordBytes.Length - length of data to compute hash on
      ' 0 - extra flags

      ' Note: We don't actually get the hash bytes back, we just have a
      ' handle to the hash object that did the computation.  It is holding
      ' those bytes internally, so we don't want or need them
      If (CryptHashData(l_hHash, in_passwordBytes, in_passwordBytes.Length,
              Convert.ToUInt32(0)) = False) Then
         Throw New Exception("Failure while hashing password bytes!")
      End If

      ' Step 3: Derive an encryption key based on hashed data
      ' in_hProvider - Handle to provider we previously acquired
      ' CALG_RC4 - Popular encryption algorithm
      ' l_hHash - Handle to hash object which will hand over the hash as
      ' part of the key derivation CRYPT_EXPORTABLE - Means the key's bytes
      ' could be exported into a byte array, using CryptExportKey
      ' l_hKey - Handle to the key we are deriving
      Dim l_hKey As IntPtr = IntPtr.Zero
      If (CryptDeriveKey(in_hProvider, CALG_RC4, l_hHash, CRYPT_EXPORTABLE,
              l_hKey) = False) Then
         Throw New Exception("Failure when trying to derive the key!")
      End If

      ' Step 4: Acquire enough memory to assure that encryption succeeds even
      ' allowing for some overflow
      Dim i As Integer
      For i = 0 To in_BytesToEncrypt.Length - 1
         l_encryptedBytes(i) = in_BytesToEncrypt(i)
      Next i

      out_NumEncryptedBytes = in_BytesToEncrypt.Length

      ' Step 5: Do the encryption
      ' l_hKey - Previously acquired key for encryption
      ' IntPtr.Zero - Indicates that we don't want any additional hashing
      ' true - Passed in because this is the only and last data to be encrypted
      ' in this session
      ' 0 - Additional flags (none)
      ' l_encryptedBytes - in/out - bytes to be encrypted in place in
      '     this buffer
      ' l_datalength - Length of data (number of bytes) to be encrypted
      ' l_encryptedBytes.Length - Lets CryptoAPI know how big the buffer it.
      '   2X data size is plenty
      If (CryptEncrypt(l_hKey, IntPtr.Zero, True, Convert.ToUInt32(0),
              l_encryptedBytes, out_NumEncryptedBytes, l_encryptedBytes.Length)
               = False) Then
         Throw New Exception("Failure when calling CryptEncrypt!")
      End If
   Finally
      ' Release resources
   End Try
   Return l_encryptedBytes
End Function

Looking Inside PasswordDecrypt

Examining the code inside PasswordDecrypt teaches how to decrypt data by using a password as a basis for the decryption key. PasswordEncrypt decrypts the data passed into it through the following steps:

1.
A byte array named l_decryptedBytes is set up that is as large as the number of encrypted bytes passed in.

2.
A hash is created and used on the password bytes and, an encryption key is created as in steps 1–3 for PasswordEncrypt.

3.
The data is decrypted by calling the CryptoAPI function CryptDecrypt. The handle to the encryption key, l_hKey, is passed into CryptDecrypt.

4.
CryptDecrypt fills the l_decryptedBytes array with decrypted data and sets out_NumDecryptedBytes to the number of bytes in the array to the number of bytes actually holding decrypted data.

5.
PasswordDecrypt returns l_decryptedBytes.

The source code for PasswordDecrypt is shown in Listing 14.8.

Listing 14.8. Source code for PasswordDecrypt
C#
// Decrypts data by using the bytes passed in as the key for the decryption.
// This method allocates enough memory to decrypt the data even if CryptoAPI
// needs more bytes than in_BytesToDecrypt holds. The allocation is done
// dynamically.
public byte[] PasswordDecrypt(byte [] in_BytesToDecrypt, Int32
        in_NumBytesToDecrypt, IntPtr in_hProvider, byte[] in_passwordBytes,
        ref Int32 out_NumDecryptedBytes)
{
   byte[] l_decryptedBytes = new byte[in_BytesToDecrypt.Length];

   for (int i = 0; i < in_BytesToDecrypt.Length; i++)
   {
      l_decryptedBytes[i] = in_BytesToDecrypt[i];
   }

   // We are not going to catch exceptions. If things go wrong, any
   // system-generated exceptions might help the user of this code
   // understand more about what is going on. We need a finally
   // clause because we want to release the CryptoAPI resources if
   // something goes wrong.
   try
   {
      // Step 1: Get a handle to a new hash object that will hash the
      // password bytes.
      IntPtr l_hHash = IntPtr.Zero;

      if (!CryptCreateHash(in_hProvider, CALG_MD5, IntPtr.Zero, 0,
              ref l_hHash))
      {
         throw new Exception("Could not create a hash object!");
      }

      // Step 2: Hash the password data....
      // l_hHash - reference to hash object
      // in_passwordBytes - bytes to add to hash
      // in_passwordBytes.Length - length of data to compute hash on
      // 0 - extra flags

      // Note: We don't actually get the hash bytes back, we just have a
      // handle to the hash object that did the computation. It is holding
      // those bytes internally, so we don't want or need them.
      if (!CryptHashData(l_hHash, in_passwordBytes,
              in_passwordBytes.Length, 0))
      {
         throw new Exception("Failure while hashing password bytes!");
      }

      // Step 3: Derive an encryption key based on hashed data
      // in_hProvider - Handle to provider we previously acquired
      // CALG_RC4 - Popular encryption algorithm
      // l_hHash - Handle to hash object which will hand over the hash as part
      //   of the key derivation
      // CRYPT_EXPORTABLE - Means the key's bytes could be exported into a
      //   byte array, using CryptExportKey
      // l_hKey - Handle to the key we are deriving
      IntPtr l_hKey = IntPtr.Zero;
      if (!CryptDeriveKey(in_hProvider, CALG_RC4, l_hHash, CRYPT_EXPORTABLE,
              ref l_hKey))
      {
         throw new Exception("Failure when trying to derive the key!");
      }

      // And now decrypt the data
      out_NumDecryptedBytes = in_NumBytesToDecrypt;

      if (!CryptDecrypt(l_hKey, IntPtr.Zero, true , 0, l_decryptedBytes,
              ref out_NumDecryptedBytes))
      {
         throw new Exception("Failure when trying to decrypt the data");
      }
   }
   finally
   {
      // Release resources
   }

   return l_decryptedBytes;
}
VB
' Decrypts data by using the bytes passed in as the key for the decryption
' This method allocates enough memory to decrypt the data even if CryptoAPI
' needs more bytes than in_BytesToDecrypt holds. The allocation is done
' dynamically.
Public Function PasswordDecrypt(ByVal in_BytesToDecrypt() As Byte, ByVal
        in_NumBytesToDecrypt As Int32, ByVal in_hProvider As IntPtr,
        ByVal in_passwordBytes() As Byte, ByRef out_NumDecryptedBytes As Int32)
         As Byte()
   Dim l_decryptedBytes(in_BytesToDecrypt.Length) As Byte
   Dim i As Integer
   For i = 0 To in_BytesToDecrypt.Length - 1
      l_decryptedBytes(i) = in_BytesToDecrypt(i)
   Next i

   ' We are not going to catch exceptions. If things go wrong, any
   ' system-generated exceptions might help the user of this code understand
   ' more about what is going on. We need a finally clause because we want
   ' to release the CryptoAPI resources if something goes wrong.
   Try
      ' Step 1: Get a handle to a new hash object that will hash the
      ' password bytes
      Dim l_hHash As IntPtr = IntPtr.Zero

      If (CryptCreateHash(in_hProvider, Convert.ToInt32(CALG_MD5), IntPtr.Zero,
              Convert.ToUInt32(0), l_hHash) = False) Then
         Throw New Exception("Could not create a hash object!")
      End If

      ' Step 2: Hash the password data....
      ' l_hHash - reference to hash object   in_passwordBytes - bytes to
      '   add to hash
      ' in_passwordBytes.Length - length of data to compute hash on
      ' 0 - extra flags

      ' Note: We don't actually get the hash bytes back, we just have a
      ' handle to the hash object that did the computation.  It is holding
      ' those bytes internally, so we don't want or need them
      If (CryptHashData(l_hHash, in_passwordBytes, in_passwordBytes.Length,
              Convert.ToUInt32(0)) = False) Then
         Throw New Exception("Failure while hashing password bytes!")
      End If

      ' Step 3: Derive an encryption key based on hashed data
      ' in_hProvider - Handle to provider we previously acquired
      ' CALG_RC4 - Popular encryption algorithm
      ' l_hHash - Handle to hash object which will hand over the hash as part
      '   of the key derivation
      ' CRYPT_EXPORTABLE - Means the key's bytes could be exported into a byte
      '   array, using CryptExportKey
      ' l_hKey - Handle to the key we are deriving
      Dim l_hKey As IntPtr = IntPtr.Zero
      If (CryptDeriveKey(in_hProvider, CALG_RC4, l_hHash, CRYPT_EXPORTABLE,
              l_hKey) = False) Then
         Throw New Exception("Failure when trying to derive the key!")
      End If

      ' And now decrypt the data
      out_NumDecryptedBytes = in_NumBytesToDecrypt

      If (CryptDecrypt(l_hKey, IntPtr.Zero, True, Convert.ToUInt32(0),
              l_decryptedBytes, out_NumDecryptedBytes) = False) Then
         Throw New Exception("Failure when trying to decrypt the data")
      End If

   Finally
      ' Release resources
   End Try

   Return l_decryptedBytes
End Function

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.79.147