| Make Passwords Secure with the Crypto API Use cryptography to add an extra dimension of security to password-protected applications and components. |
In many computing environments, limiting the availability of some or all of an application's functionality to particular users is critical. You might want only certain employees to be able to modify a set of data fields in a spreadsheet or database. Only the owner of an e-mail account should be able to obtain that account's incoming messages. You might wish to restrict use of demo software to only those customers who have paid or registered for it. The number of scenarios that require secured functionality is limitless.
|
Implementing the most popular approach to secured functionalityrequiring the user to enter a password and enabling functionality only if the password is validis straightforward with Visual Basic. However, in its simplest form, password protection is highly insecure because it introduces fairly easy ways for unauthorized users to obtain valid passwords. Cryptography is the extra ingredient you need to secure functionality in your applications. I'll show you how to add basic password protection to VB applications and how to use Microsoft's Crypto API to make that protection secure, whether you implement local or remote password authentication. In the process, you'll also gain insights into how the operating system itself uses cryptography to implement its own security features.
Here's an example of code that adds basic password protection to an application and thereby implements a simple password model (see fig. 1). The code acts as a gateway into the application, creating the object that performs the secured functionality only if the user enters a correct password:
Private Sub cmdDoOp_Click()
Dim Password As String
Dim PasswordValid As Boolean
Dim MainOp As clsFunctionality
Password = txtPassword.Text
Select Case Password
Case "Password1"
PasswordValid = True
Case "Password2"
PasswordValid = True
End Select
If PasswordValid Then
Set MainOp = New clsFunctionality
MainOp.Operation
Else
MsgBox "Invalid Password"
End If
End Sub
| Figure 1 | The Simple Password Model Offers Low Security. |
Despite this model's simplicity, you might apply it to a wide variety of security scenarios beyond the one in which the user enters a password into a dialog box. For example, you can extend the model to apply to password-based security of objects on a local system or across a network by passing passwords as parameters to a COM object.
You can also read passwords from a file or databasea scenario common for applications that support multiple users, such as databases or e-mail clients. You can also sometimes use a hard-coded password with shareware or demo software, although it's obviously a poor approach from a security perspective.
Add Cryptography
As versatile as the simple password approach is, it's also
terribly insecure. It requires either that a password file reside
somewhere on the system, or that you hardcode passwords into an
executable. Someone who gets hold of the password file or
executable can extract the passwords with relative ease.
Encrypting the password file adds only limited security, because
someone could still read all the passwords if the encryption key
for the password file were compromised.
The solution is to use a hash. A hash is a fixed value, typically 128 bits, that you derive by means of an algorithm from a variable-length block of text, such as a password. A given block of text always generates the same hash value under a given algorithm, but it's virtually impossible to obtain the original text that produced a hash value. By storing hash values instead of passwords, you guarantee that even if the password source is compromised, the original passwords won't reveal themselves.
| Figure 2 | Protect Passwords With the Hashed Model. |
You can download the sample program. A portion of this program illustrates how to implement a hashed password model under the Crypto API (see fig. 2). You first use the CryptAcquireContext API call, which in this example is wrapped by the AcquireContext function defined elsewhere in the sample program. The PROV_RSA_FULL provider is the default 40-bit provider (it's not nearly as secure as the 128-bit provider, but at least you know it's available on every Windows system). The GetHashedData function wraps the CryptCreateHash and CryptHashDataString API functions, which create a hash object and add data into the hash. The CryptGetHashParam API function retrieves the hashed value, which plays the same role as the plain-text password in the simple password example.
The two examples you have seen so far involve local management and verification of passwords. But certain scenarios require that password verification occur remotely (see fig. 3):
In the sample program, an object of type clsRemoteServer models the remote server. Although it's part of the sample application, you can easily imagine implementing it as a remote object. This object receives some information that identifies the current user and the password the user entered, and it returns a result indicating whether the operation is permitted. Code from the frmValidate1 module in the sample program illustrates this:
Dim rs As New clsRemoteServer
Password = txtPassword.Text
PasswordValid = rs.IsUserValid1("Dan", _
Password)
If PasswordValid Then
Set MainOp = New clsFunctionality
MainOp.Operation
Else
MsgBox "Invalid Password"
End If
Even if the remote system uses the hashed password approach I described earlier, this remote password model suffers from several new security flaws unless you can somehow guarantee the security of the connection between the local and remote system. These flaws are that it transmits the password to the remote object in plain text, it transmits the username to the remote object in plain text, or it returns the authentication result in plain text.
| Figure 3 | Use Remote Verification. |
Security is still better than with the simple password model because nobody can obtain a complete list of passwords (assuming the remote system is secure). Also, it's possible to disable a compromised password, because the remote system stores the passwords. However, as long as the security breach remains undetected, the impact on the user whose password was compromised, and on the systems he or she uses, is potentially severe, because many people tend to use the same password for multiple systems and applications.
A better approach, shown in the frmValidate2.frm module in the sample code, is to first hash the password, then transmit it to the remote object. This does not help secure the application, because once the user ID and hash value are intercepted, someone can still use them to trick the remote server into authenticating the application. But at least the hashed value applies only to the current application and won't allow an attacker to impersonate the user on other systems and in other applications.
Neither the plain-text nor the hashed approach to remote authentication addresses the fact that the remote system returns the authentication value in plain text. If someone intercepts the connection and pretends to be the remote system (or somehow hacks the application to go to a different remote server), the intruder could enable the application simply by returning a successful authentication result no matter what ID and password the user entered.
Apply the Challenge/Response Model
One of the cleverest solutions for providing remote password
verificationthe challenge/response password modeladdresses
this problem by not sending the password to the remote system in
any form (see Fig. 4). It's certainly hard to steal a password
that is never transmitted.
So how does one go about verifying a password without seeing it? To understand how to accomplish this, you need to take into consideration two facts:
| Figure 4 | Eliminate Password Transmission. |
Given these two facts, you can create a protocol that compares the two passwords without actually sending the password between the two systems. The client application obtains the password either by prompting the user or by other means. It then generates a random block of data that it encrypts using a key derived from the hashed password. The client sends the random block, along with user identification, to the remote verification computer. That machine retrieves the known hashed password for the specified user and encrypts the random block of data using a key derived from that hashed password. It then sends the encrypted data back to the client application.
The client application now has two encrypted blocks of data. Given that they were both encrypted from the same random block of data, if the two passwords match, the two encrypted data blocks will match as well, meaning the password is correct and the operation can proceed.
The frmValidate3.frm module in the sample code demonstrates how to implement challenge/response verification. This portion of the client code uses the CryptGenRandom API to generate 128 random bytes of data:
Call CryptGenRandom(hcsp, 128, _
Challenge)
Response = rs.IsUserValid3("Dan", _
Challenge)
' See modUtilities for function
' that converts password into key
hkey = GetPasswordKey(hcsp, _
HexHashData, m_Alg)
BufferLength = Len(Challenge)
datalength = BufferLength
OriginalDatalength = datalength
' Encrypt it - compare length of
' RC2 vs RC4 cypher
res = CryptEncrypt(hkey, 0, 1, 0, _
Challenge, datalength, _
BufferLength)
If Challenge = Response Then
PasswordValid = True
End If
The rs.IsUserValid3 method represents the operation of the remote computer; it encrypts the data block using the password known to the remote machine, and returns the encrypted data as the Response variable. The GetPasswordKey function in the clsFunctionality.bas module uses the CryptDeriveKey API function to derive a symmetrical key based on the hashed password.
The CryptEncrypt API function encrypts the data on the local machine in the Challenge variable. The function then compares the encrypted Challenge and Response strings to determine whether the password is valid.
How Secure is It?
How secure is this approach? It's impossible to obtain the
password directly during verification because the password is
never transmitted. And it's impossible to impersonate the remote
system by returning a fake authentication, because you must have
both the password and random data in order to return a string
that enables the operation on the client. Even a person who
captures a valid remote response once won't be able to use it
again because the challenge data changes on each request. It
doesn't even matter that the user ID is transmitted in plain
text, because although the user ID does play a role when the
remote machine chooses the password, it plays no role in the
interaction between the two systems.
The challenge/response approach, however, is not invulnerable to attacks. An attacker might intercept both the challenge and the response strings and perform an exhaustive search for the key used to encrypt the challenge string (by trying every possible key against the challenge string until one results in the response string).
If a fast PC could try 1,000 encryptions per second (not an unreasonable number given the math involved), it would take an average of about 17.5 years to find the key. Of course, supercomputer arrays, such as those the National Security Agency (NSA) uses, are much fasterso fast you might as well use the original plain-text approach for all the security challenge/response would provide under these circumstances. But for general commercial use, the challenge/response approach isn't badespecially if you change the password every now and then.
If you want better protection from attack, upgrade to the 128-bit cryptographic service provider. The average time to find a key with 128-bit encryption at 1,000 attempts per second is 5.4 x 1027 years. Even the NSA would have a hard time with that. In fact, 128-bit encryption makes it much more likely that someone would use a dictionary attackan attempt to use every word in a dictionary as a passwordor break into your office to look for a scrap of paper with the password on it rather than try an exhaustive search.
The challenge/response approach I've described here is the same one Windows uses to authenticate users on domains and Web sites. Cryptography is the foundation of all system security. In learning how you can use the Crypto API to add secure functionality to your VB applications, you've also gleaned some fundamentals about how cryptography applies to security applications in general.
Many more applications of cryptography to security exist than I've been able to cover here. For example: the use of public key encryption to verify identity or to transmit keys or passwords securely, and the use of hashing to digitally sign data to verify that it remains unchanged. But what you've read here, and the sample program, should be enough for you to implement many types of cryptographic-based security in your VB applicationsand whet your appetite for learning more.
Daniel Appleman is the president of Desaware Inc., a developer of add-on products and components for Microsoft visual development tools, including Visual Basic. He is the author of Visual Basic Programmer's Guide to the Win32 API and Developing COM/ActiveX Components with Visual Basic 6 (Sams). He is a cofounder of Apress, a computer book publishing company. You can contact him at dan@desaware.com.