Encrypting and Decrypting Data by Using a PasswordEncrypting 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:
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 passwordC# 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:
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 keyC# 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 PasswordEncryptExamining 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:
The source code for PasswordEncrypt is shown in Listing 14.7. Listing 14.7 PasswordEncryptC# // 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 PasswordDecryptExamining 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:
The source code for PasswordDecrypt is shown in Listing 14.8. Listing 14.8 Source code for PasswordDecryptC# // 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 |