With hashing, you simply store the hash value of a user's password in the database. However, if two users have the same password, then the hash values for these two passwords would be identical. Imagine the hacker seeing that the two hash values are identical; it would not be hard for him to guess that the two passwords must be the same. For example, users often like to use their own names (or birth dates, or common words found in the dictionary) as passwords. Hence, hackers often like to use dictionary attacks to correctly guess users' passwords. To reduce the chance of dictionary attacks, you can add a "salt" to the hashing process so that no two identical passwords can generate the same hash values. For example, instead of hashing a user's password, you can hash his password together with his other information, such as email address, birth date, last name, first name, etc. The idea is to ensure that each user will have a unique password hash value. While this idea of using the user's information as a salt for the hashing process sounds good, it is quite easy for hackers to guess. A better approach would be to randomly generate a number to be used as the salt and then hash it together with the user's password.
The following subroutine,
Salted_Hashing_SHA1(), generates a random number using the
RNGCryptoServiceProvider class, which returns a list of randomly generated bytes (the salt). It then combines the salt with the original password and performs a hash on it.
Private Sub Salted_Hashing_SHA1() '---Random Number Generator--- Dim salt(8) As Byte Dim rng As New RNGCryptoServiceProvider rng.GetBytes(salt) '---ask the user to enter a password--- Console.Write("Please enter a password: ") Dim password As String = Console.ReadLine() '---add the salt to the password--- password &= ASCII.GetString(salt) '---hash the password--- Dim data() As Byte = ASCII.GetBytes(password) Dim passwordHash() As Byte Dim sha As New SHA1CryptoServiceProvider() passwordHash = sha.ComputeHash(data) '---ask the user to enter the same password again--- Console.Write("Please enter password again: ") password = Console.ReadLine() Console.WriteLine(ASCII.GetString(salt)) '---adding the salt to the second password--- password &= ASCII.GetString(salt) '---hash the second password and compare it with the first--- data = ASCII.GetBytes(password) If ASCII.GetString(passwordHash) = _ ASCII.GetString(sha.ComputeHash(data)) Then Console.WriteLine("Same password") Else Console.WriteLine("Incorrect password") End If End Sub
Note that if you use salted hashing for storing passwords, the "salt" used for each password must be stored separately from the main hash database so that hackers do not have a chance to obtain it easily.
Encryption and Decryption
In the previous section, you saw how hashing works. Hashing is a one-way process, which means that once a value is hashed, you can't obtain its original value by reversing the process. This characteristic is particularly well suited for authentications as well as digitally signing a document.
In reality, there are many situations that require information to be performed in a two-way process. For example, if you send a secret message to a recipient, you would need to be able to "scramble" it so that only the recipient can see it. This process of scrambling is known as encryption. Undoing the scrambling process to obtain the original message is known as decryption. There are two main types of encryption, symmetric and asymmetric.
Symmetric encryption is also sometimes known as private key encryption. With private key encryption, you encrypt a secret message using a key that only you know. To decrypt the message, you need to use the same key. Private key encryption is effective only if the key can be kept a secret. If too many people know the key, its effectiveness is reduced.
Imagine you are trying to send a secret message to your faraway friend, Susan, using a private key. In order for Susan to decrypt the secret message, she must know the private key. So you need to send it to her. But if the secrecy of the key is compromised somehow (such as people eavesdropping on your conversation), then the message is no longer secure. Moreover, if Susan tells another friend about the private key, her friend can then also decrypt the message. Despite the potential weakness of private key encryption, it is very easy to implement and, computationally, it does not take up too many resources.
For private key encryption (symmetric) encryptions, the .NET framework supports the DES, RC2, Rijndael, and TripleDES algorithms.
To demonstrate symmetric encryption, I will use the
RijndaelManaged class in the following
SymmetricEncryption() function. Three parameters are required--the string to be encrypted, the private key, and the initialization vector (IV). The IV is a random number used in the encryption process to ensure that no two strings will give the same cipher text (the encrypted text) after the encryption process. You will need the same IV later on when decrypting the cipher text.
Private Function SymmetricEncryption( _ ByVal str As String, _ ByVal key As Byte(), _ ByVal IV As Byte()) As String Dim memStream As New IO.MemoryStream Try '---creates a new instance of the RijndaelManaged class--- Dim RMCrypto As New RijndaelManaged '---creates a new instance of the CryptoStream class--- Dim CryptStream As New CryptoStream(memStream, _ RMCrypto.CreateEncryptor(key, IV), _ CryptoStreamMode.Write) Dim SWriter As New StreamWriter(CryptStream) '---encrypting the string--- SWriter.Write(str) SWriter.Close() CryptStream.Close() '---return the encrypted data as a string--- Return System.Convert.ToBase64String(memStream.ToArray) Catch err As Exception Console.WriteLine(err.ToString) Return (String.Empty) End Try End Function
In the previous function, the encrypted string is returned as a Base64-encoded string. Note the allowable key sizes for the
RijndaelManaged class. You can check the allowable key sizes using the following code:
Dim ks() As KeySizes Dim RMCrypto As New RijndaelManaged ks = RMCrypto.LegalKeySizes '---print out the various key sizes--- Console.WriteLine(ks(0).MaxSize) ' 256 Console.WriteLine(ks(0).MinSize) ' 128 Console.WriteLine(ks(0).SkipSize) ' 64
The valid key sizes are: 16 bytes (128 bit), 24 bytes (128 bits + 64 bits), and 32 bytes (256 bits).
Also, you can get the system to generate a random key and IV (which you need to supply in the current example) automatically:
'---generate key--- RMCrypto.GenerateKey() Dim key As Byte() = RMCrypto.Key Console.WriteLine("Key : " & System.Convert.ToBase64String(key)) '---generate IV--- RMCrypto.GenerateIV() Dim IV As Byte() = RMCrypto.IV Console.WriteLine("IV : " & System.Convert.ToBase64String(IV))
If the IV is null when it is used, the
GenerateIV() method is called automatically. Valid size for the IV is 16 bytes.
To decrypt a string encrypted using the
RijndaelManaged class, you can use the following
Private Function SymmetricDecryption( _ ByVal str As String, _ ByVal key As Byte(), _ ByVal IV As Byte()) _ As String Try Dim s As String '---converts the encrypted string into a byte array--- Dim b As Byte() = System.Convert.FromBase64String(str) '---converts the byte array into a memory stream for ' decryption--- Dim memStream As New MemoryStream(b) Dim RMCrypto As New RijndaelManaged Dim CryptStream As New CryptoStream(memStream, _ RMCrypto.CreateDecryptor(key, IV), _ CryptoStreamMode.Read) '---decrypting the stream--- Dim SReader As New StreamReader(CryptStream) s = SReader.ReadToEnd '---converts the decrypted stream into a string--- s.ToString() SReader.Close() Return s Catch err As Exception Console.WriteLine(err.ToString) Return String.Empty End Try End Function