We will create a credit card form with a credit card number
field (ccNumber) as the sensitive field to protect, and
we will add login functionality to the form.
Create a new Microsoft Office InfoPath Project within
Microsoft Visual Studio .NET 2003. When prompted to select a form template for
your application, choose Create a new form template. Use
the field names and data types shown in Figure 1 to construct the main data
source of the form.
Figure 1 - Main data source of the InfoPath form
Drag the fields - except for the isLocked
field - onto the form and convert them into the controls shown in Figure 2.
Figure 2 - InfoPath form in design mode
Simulating a Password Control in InfoPath
InfoPath does not come with a Password
Control, so you must create your own. Two password fields are used in
this solution: password and passwordAgain
(see Figures 1 and 3). The user is required to enter the same password
twice, once in the password field and once in the passwordAgain field. You must add two validation checks on
both password fields: the password fields must not be blank, and the passwords
must be the same.
Password fields are usually masked with asterisks (*) for
privacy reasons. Since this functionality is not available in InfoPath, you
can set the font of the password and passwordAgain fields to Webdings or
Wingdings to simulate masking the passwords as they are
entered. This is the closest you will get to simulating a Password
Control in InfoPath.
Figure 3 - Wingdings font used to mask text when
typing in passwords
Enabling / Disabling the Unlock Button
The Unlock button is disabled
whenever the protected field (ccNumber) has been
decrypted or when the form is opened for the first time. It is enabled
whenever the form is in a protected state.
The isLocked field is used to
determine whether the form contains encrypted data. It always has one of two
values: an empty string or an encrypted version of the text "locked".
If it contains an empty string, form fields are shown in clear text, in which
case the Unlock button should be disabled. If the isLocked field contains an encrypted version of the text
"locked", it means that sensitive form fields are being protected,
and that the Unlock button should be enabled.
Use conditional formatting to enable/disable the Unlock button by checking whether the isLocked
field is blank.
Namespaces
Visual Studio adds two namespaces to the form's code by
default: System and Microsoft.Office.Interop.InfoPath.SemiTrust.
Three additional namespaces are required for encryption/decryption purposes: System.Text, System.IO, and System.Security.Cryptography (see Listing 1).
Listing 1 - Additional namespaces required
using System.Text;
using System.IO;
using System.Security.Cryptography;
Encryption / Decryption Methods
The two methods shown in Listings 2 and 3 are used to
encrypt and decrypt data. Both methods take two parameters: a symmetric key
and the text to encrypt or decrypt.
Listing 2 - Private method to encrypt data
private string EncryptField(SymmetricAlgorithm
SymmetricKey, string
ValueToEncrypt)
{
string base64enc = string.Empty;
try
{
byte[]dataToEncrypt =
Encoding.Unicode.GetBytes(ValueToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream csBase64 = new CryptoStream(ms,
new ToBase64Transform(),
CryptoStreamMode.Write);
CryptoStream csRijndael = new
CryptoStream(csBase64,
SymmetricKey.CreateEncryptor(), CryptoStreamMode.Write);
csRijndael.Write(dataToEncrypt, 0,
(int)dataToEncrypt.Length);
csRijndael.FlushFinalBlock();
base64enc =
Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)ms.Length);
}
catch (Exception ex)
{
thisXDocument.UI.Alert("The following
error occurred: " + ex.Message);
}
return base64enc;
}
Listing 3 - Private method to decrypt data
private string DecryptField(SymmetricAlgorithm
SymmetricKey, string
ValueToDecrypt)
{
string unencryptedString = string.Empty;
try
{
byte[]dataToDecrypt =
Convert.FromBase64String(ValueToDecrypt);
MemoryStream ms = new MemoryStream();
CryptoStream csRijndael = new
CryptoStream(ms, SymmetricKey.CreateDecryptor
(), CryptoStreamMode.Write);
csRijndael.Write(dataToDecrypt, 0,
(int)dataToDecrypt.Length);
csRijndael.FlushFinalBlock();
unencryptedString =
Encoding.Unicode.GetString(ms.GetBuffer(), 0, (int)
ms.Length);
}
catch
(System.Security.Cryptography.CryptographicException)
{
string msg = "Either the password you
entered is incorrect ";
msg += "or you are not authorized to
view protected data.";
thisXDocument.UI.Alert(msg);
}
catch (Exception)
{
string msg = "The InfoPath form has
been tampered with; ";
msg += "cannot unprotect data.";
thisXDocument.UI.Alert(msg);
}
return unencryptedString;
}
The symmetric key is generated from the password entered into
the form and the name of the user who is logged onto the system. The PasswordDeriveBytes class of the System.Security.Cryptography
namespace takes 4 parameters:
1.
Password
2.
Key salt
3.
Hash name
4.
Number of iterations to generate the key
This class is used to generate a key from the password. The
user name, which is automatically retrieved with System.Environment.UserName,
is used as the salt for the key to ensure its uniqueness. The private method that
generates the symmetric key is shown in Listing 4.
Listing 4 - Private method to generate a symmetric key
from a password and user name
private SymmetricAlgorithm GenerateKey(string
password)
{
string saltValue =
System.Environment.UserName;
SymmetricAlgorithm symmKey = new
RijndaelManaged();
byte[]saltValueBytes =
Encoding.ASCII.GetBytes(saltValue);
PasswordDeriveBytes passwordKey = new
PasswordDeriveBytes(password,
saltValueBytes, "SHA1", 3);
symmKey.Key =
passwordKey.GetBytes(symmKey.KeySize / 8);
symmKey.IV =
passwordKey.GetBytes(symmKey.BlockSize / 8);
return symmKey;
}
Event Handlers
Two event handlers are used in the form: one to handle the OnSaveRequest event for when the form is saved, and one to
handle the OnClick event on the Unlock
button.
The OnSaveRequest event handler
contains code to encrypt data and empty the two password fields (see Listing
5). The OnClick event handler for the button contains
code to decrypt encrypted data and empty the isLocked
field (see Listing 6).
Listing 5 - Code snippet from the OnSaveRequest
event handler
// Encrypt the sensitive fields
thisXDocument.DOM.selectSingleNode("/my:myFields/my:isLocked").text
=
EncryptField(symmKey, "locked");
string ccNumber =
thisXDocument.DOM.selectSingleNode("/my:myFields/my:ccNumber")
.text;
thisXDocument.DOM.selectSingleNode("/my:myFields/my:ccNumber").text
=
EncryptField(symmKey, ccNumber);
// Do not save the password; empty the password
thisXDocument.DOM.selectSingleNode("/my:myFields/my:loginBox/my:password").text
= string.Empty;
thisXDocument.DOM.selectSingleNode("/my:myFields/my:loginBox/my:passwordAgain")
.text = string.Empty;
Listing 6 - Code snippet from the btnUnlock_OnClick
event handler
// Decrypt the encrypted fields
string ccNumber =
thisXDocument.DOM.selectSingleNode("/my:myFields/my:ccNumber")
.text;
thisXDocument.DOM.selectSingleNode("/my:myFields/my:ccNumber").text
=
DecryptField(symmKey, ccNumber);
// Empty the isLocked field so that the Unlock
button is disabled
thisXDocument.DOM.selectSingleNode("/my:myFields/my:isLocked").text
=
string.Empty;