How important is security to you? It has always been very important to me, so scripts that I write for clients always take it into consideration. But I've been surprised at how often I stumble upon Perl scripts that contain hard-coded user credentials and private information such as hidden network Universal Naming Convention (UNC) addresses.
I ran into a situation in which I needed to regularly download a Web page from a secured Web server. The script had to run automatically, whether I was at my machine or not. This requirement meant that I needed to somehow store my credentials in the script so that the script could apply them when downloading the Web page. Storing credentials in a script is potentially a huge security risk. What if a copy of my script found its way to some public forum? I had to find a secure and dependable way of storing my credentials.
Crypt() Isn't the Answer
How do you avoid exposing passwords and other sensitive information in a script? You can't use the standard crypt() function because it's a one-way hash, which means that it can encrypt but not decrypt data. The crypt() function is useful if you want to compare a value with an encrypted value. The code at callout A in Listing 1 prompts the user for a secret value and uses the crypt() function to hash the value. The code then asks the user for the value again and, at callout B, passes the new specified value through the same crypt() function and compares the result with the original encrypted value. If the two encrypted values match, the user's second inputted value matched the first inputted value. If the two values don't match, the code prompts the user to try inputting another value. The code simply encrypts two values and checks whether they match. However, no decryption occurs. Note that the code at callout A uses a randomly picked two-character string to create a salt—a value used to prime the crypt() function.
Nor Is Obfuscation
Obfuscation is a simple type of encryption that makes sensitive data less obvious and more obscure. There are as many ways to obfuscate data as there are stars in the sky. For example, you can store a password backward and simply reverse the string at runtime, as in
$Password = reverse( "terceSyM" );
When the script runs, it reverses the string and sets $Password equal to the reversed string, which has the value "MySecret". The problem with this very simple form of obfuscation is that anyone can easily walk through the code and discover not only how to unobfuscate the data but also what the secret value is.
Nor Is Encryption
You can think of encryption as a more complex version of obfuscation. You use an algorithm to encrypt data into a form that you can later unencrypt. But when a script needs to be self-sufficient (i.e., requires no input from users to run), all data necessary to procure the secret data must be available to the script. If the script can discover the information needed to decrypt the data, then so can any intruder that gains access to the script.
Using Win32 Private Data
Microsoft takes security seriously; it has dedicated entire Windows APIs to providing security-related functions. Many security-related APIs let you work with Windows' Local Security Authority (LSA), a low-level security component that manages such things as the SAM (user) database and domain security. The Win32::Lanman extension exposes many of the LSA API functions. One particular set of LSA functions that I want to talk about is the PrivateData set of functions: Win32::Lanman::LsaStorePrivateData( $Machine, $Key, $Data ) and Win32::Lanman::LsaRetrievePrivateData( $Machine, $Key, \$Data ). These functions request that Windows store some piece of data to a secured part of the registry and retrieve the data, respectively. Windows manages the security and encryption for you. I explain how you can obtain the Win32::Lanman extension at the end of this article.
When you use LsaStorePrivateData() to store data, you pass in the name of the machine on which you want to store the private data (or an empty string for the local computer), the data you want to secure, and a key, which is simply a string that you use to identify the data you're storing. Note that because of a bug in the Win32::Lanman extension, LsaStorePrivateData() can store only data that doesn't contain nulls. You can use the function for anything that can be converted to a text string; just don't expect to use it for binary data such as the data in a graphics file.
After you store data, you can use LsaRetrievePrivateData() to retrieve it. You pass in the name of the machine on which the data is stored and the key you specified when storing it. You also pass in a reference to a scalar (such as \$Data) so that the function can assign the data to the specified variable.
When a script uses LsaStorePrivateData() to store data, LSA creates a new key in a secured part of the registry. LSA places permissions on the key so that only the user who created the key (the owner) and the Administrators group have read and write access. LSA then encrypts the data and stores the encrypted data in the secured key. If another user who isn't an administrator tries to store or retrieve the data, he or she will be denied access.
Safely Fetching Secured URLs
In "Progressive Perl for Windows: Fetching Web Data with Win32::Internet," March 2002, InstantDoc ID 23937, I discussed a script that uses the Perl Win32::Internet extension to download the Web page associated with a URL that the user specifies. If the Web page is password protected, the user must pass in user credentials. This requirement is no problem unless there's no user. In this case, I needed to have a script that ran unattended, so I modified the script to use the PrivateData functions exposed by Win32::Lanman to store my user credentials in a secured way. The result is GetSecureUrl.pl, which you can find in the Code Library on the Windows Scripting Solutions Web site (http://www.winscriptingsolutions.com).
GetSecureUrl.pl first creates the key that it will use to store the private data, as Listing 2 shows. I use the script's filename as the key. The script converts the filename to uppercase because PrivateData keys are case sensitive, then separates the filename from any path information. Eliminating the path information lets you move the script from directory to directory without any penalty.
As Listing 3 shows, if the user passes in an -s flag to the script to specify that he or she wants to store credentials, the script calls LsaStorePrivateData() and passes it a string containing the user ID and password separated by a semicolon. (Although this script uses a semicolon, you can use any character as a delimiter.) If the user passes in an -a flag to the script to specify that he or she wants to clear stored credentials, the script calls LsaStorePrivateData() and passes it an empty string, as Listing 4 shows.
If the user specified a username and password as parameters when invoking the script, the script uses these credentials, instead of any stored credentials, when downloading the specified Web page, as Listing 5 shows. If the user didn't specify credentials, the script queries the LSA for the stored data, as Listing 6 shows. If the LsaRetrievePrivateData() function is successful in retrieving the data, the script separates the resulting data string's username and password. The script uses the split() command and specifies a semicolon to split the string. As Listing 7 shows, the script passes the credentials to a Win32::Internet object's HTTP() method. The Win32::Internet library performs the authentication.
Win32::Lanman tracks its own errors. Thus, when one of the PrivateData function calls fails, you need to call Win32::Lanman::GetLastError() to discover what the error was. Listing 8 shows the code that makes this call.
This script requires Jens Helberg's Win32::Lanman extension, version 1.09 or later, which you can download from http://www.cpan.org/modules/by-authors/id/J/JH/JHELBERG. A big thank you goes to Helberg for creating and maintaining this extension. Alternatively, you can use Perl Package Manager (PPM) to download and install the extension from Jenda Krynicky's Perl repository, as in
ppm install http:// jenda.krynicky.cz/perl/ Win32-Lanman.ppd
The ability to use the Windows LSA functions to store secret data is a blessing. Unattended scripts and even services and daemons can safely store sensitive information such as credentials in an easy-to-get-to way. The OS manages security, so your script can worry about other important tasks such as what the script was written to perform in the first place.