When does salting passwords become too secure? I have a couple of functions that Encrypt and Decrypt as users passwords, but I am concerned if it might be overkill.
First I have my encryption method which takes the encrypted password and the salt and puts it all in one string (up to 256 Characters) in my database. In addition to that it actually only encrypts the password with a 32 Character string of my original 128 Character Salt String, which the function chooses at random.
Public Function EncryptPassword(Password As String) As String
Dim EPassword As String = String.Empty
' Generate Random 128 Base64 Salt String
Dim Salt As String = Var.Simple3Des.GenerateSalt
' Divide into Substrings, and combine into splitable string
Dim SmallSalts As String = Salt.Substring(0, 32) + "." + Salt.Substring(32, 32) + "." + Salt.Substring(64, 32) + "." + Salt.Substring(96, 32)
' Create the Salt Array
Dim SaltArray = Split(SmallSalts, ".")
' Randomly Choose part of the array to actually use as salt
Dim rnd As New Random
Dim TrueSalt As String = SaltArray(rnd.Next(0, SaltArray.Length))
' Encrypt The Password
Dim Security As New Var.Simple3Des(TrueSalt)
EPassword = Security.EncryptData(Password)
' Divide up the salt and password and place into same string
Dim PasswordString As String = Salt.Substring(0, 16) + EPassword.Substring(0, 6) + Salt.Substring(16, 112) + EPassword.Substring(6, EPassword.Length - 6)
Return PasswordString
End Function
I then use the same formula to Decry-pt the password, by trying all possible sub-string combinations until it finds the right one.
Public Function DecryptPassword(NtID As String)
' Grab The Users Encrypted Password
Dim UserID As Integer = GetAppUserID(NtID)
Dim User As Users = Var.db.Web.Users.Find(UserID)
Dim EPassword = User.Password
' Divided the Encrypted Password Into Salt and Actual Password
Dim Salt As String = EPassword.Substring(0, 16) + EPassword.Substring(22, 112)
Dim Password As String = EPassword.Substring(16, 6) + EPassword.Substring(134, EPassword.Length - 134)
Dim DPassword As String = String.Empty
' Try each substring of Salt until password is Decrypted.
Try
If DPassword = String.Empty Then
Dim Security As New Var.Simple3Des(Salt.Substring(0, 32))
DPassword = Security.DecryptData(Password)
End If
Catch ex As Exception
DPassword = String.Empty
End Try
Try
If DPassword = String.Empty Then
Dim Security As New Var.Simple3Des(Salt.Substring(32, 32))
DPassword = Security.DecryptData(Password)
End If
Catch ex As Exception
DPassword = String.Empty
End Try
Try
If DPassword = String.Empty Then
Dim Security As New Var.Simple3Des(Salt.Substring(64, 32))
DPassword = Security.DecryptData(Password)
End If
Catch ex As Exception
DPassword = String.Empty
End Try
Try
If DPassword = String.Empty Then
Dim Security As New Var.Simple3Des(Salt.Substring(96, 32))
DPassword = Security.DecryptData(Password)
End If
Catch ex As Exception
DPassword = String.Empty
End Try
Return DPassword
End Function
My question is
A. Aside from possible perfomance issues, what other dangers does this method pose?
B. Is this overkill, is salting and storing the salt/password like this even necessary?
C. If this is unnecessary what other methods could I use to salt and store salt/password?
Here is a great site that talks about the sort of thing you are interested in: https://crackstation.net/hashing-security.htm. The overall point of what they say is that:
If you are not careful yourself, messing with algorithms that are crafted to be secure can actually reduce their security.
It is overkill because you have to assume that an attacker will get your code before they can crack your database, so they will know your scheme.
Simply storing the hash and the salt should be fine. That is sort of the point of the hash and the salt.
I have used the crackstation page as a general reference on hashing security quite often. I highly recommend reading it as will probably contain plenty of information that you will find relevant and that I omitted here.
Related
My program below checks if the userName and the password is in the database( written in visual basic and uses Access database). The program works however, when I type in the userName or password in a different case it still works. For example, if my database has the userName as "john" and the password as "johnspassword", my program accepts the username as "JOHN" and password as "JOHNSPASSWORD".
how do i resolve this problem?
Dim con As New OleDbConnection("Provider=Microsoft.jet.oledb.4.0;data source=C:\Users\jacob\Desktop\MS Office\project.mdb")
Dim cmd As OleDbCommand = New OleDbCommand("SELECT * FROM tblUsers WHERE UserID = '" & txtUserName_Field.Text & "' AND userPassword = '" & txtUserPassword_Field.Text & "' ", con)
con.Open()
Dim sdr As OleDbDataReader = cmd.ExecuteReader()
'If the record can be queried, it means passing verification, then open another form.
Dim empty =
Me.Controls.OfType(Of TextBox)().Where(Function(txt) txt.Text.Length = 0)
If empty.Any Then
MessageBox.Show(String.Format("Please fill in all the fields required"))
Else
If (sdr.Read() = True) Then
MessageBox.Show("The is valid!")
Form4.Show()
Me.Hide()
Else
MessageBox.Show("Invalid name or password!")
End If
End If
con.Close()
End Sub
If you use a hash of the password instead then you solve two problems you have:
You should not store passwords as plain text
A hash will make the password case-sensitive
The Rfc2898DeriveBytes Class is suitable for creating the hash; you'll need a randomly-generated salt stored in the database for each user too.
There are many sites, e.g., Salted Password Hashing - Doing it Right, with explanations of why salting and hashing are desirable.
You will still have to decide if you need the username to be case-sensitive.
EDIT
It appears that Access doesn't have an efficient (i.e. sargable) way to do a case-sensitive comparison, so you can simply get the username from the database and check it in your program, something like this:
Option Infer On
Option Strict On
Imports System.Data.OleDb
Imports System.Security.Cryptography
Public Class SomeClass
'TODO: decide on the sizes for the salt and hash
'TODO: create binary fields in the database of appropriate sizes
'TODO: consider storing the number of iterations in the database
Const SALTLENGTH As Integer = 8
Const HASHLENGTH As Integer = 16
Const PBKDF2ITERATIONS As Integer = 20000
Friend Function PBKDF2Hash(password As String, salt As Byte(), iterations As Integer, hashSize As Integer) As Byte()
Dim hasher As New Rfc2898DeriveBytes(password, salt, iterations)
Return hasher.GetBytes(hashSize)
End Function
Function IsLoginValid(username As String, password As String) As Boolean
Dim salt(SALTLENGTH - 1) As Byte
Dim hashedPassword(HASHLENGTH - 1) As Byte
Dim usernameIsValid = False
Dim csb As New OleDbConnectionStringBuilder With {
.Provider = "Microsoft.jet.oledb.4.0",
.DataSource = "C:\Users\jacob\Desktop\MS Office\project.mdb"
}
Using conn As New OleDbConnection(csb.ConnectionString)
'TODO: use the actual column names
Using cmd As New OleDbCommand("SELECT UserID, salt, password FROM tblUsers WHERE UserID = ?", conn)
'TODO: use type of column as specified in the database
cmd.Parameters.Add(New OleDbParameter With {.OleDbType = OleDbType.VarWChar, .Value = username})
conn.Open()
Dim rdr = cmd.ExecuteReader()
If rdr.HasRows Then
rdr.Read()
If String.Compare(rdr.GetString(0), username, StringComparison.Ordinal) = 0 Then
rdr.GetBytes(1, 0, salt, 0, SALTLENGTH)
rdr.GetBytes(2, 0, hashedPassword, 0, HASHLENGTH)
usernameIsValid = True
End If
End If
conn.Close()
End Using
End Using
Dim expectedHash = PBKDF2Hash(password, salt, PBKDF2ITERATIONS, HASHLENGTH)
If usernameIsValid AndAlso hashedPassword.SequenceEqual(expectedHash) Then
Return True
End If
Return False
End Function
Private Sub bnLogin_Click(sender As Object, e As EventArgs) Handles bnLogin.Click
Dim username = txtUserName_Field.Text
Dim password = txtUserPassword_Field.Text
If username.Length = 0 OrElse password.Length = 0 Then
MessageBox.Show("Please fill in all the fields required.")
Exit Sub
End If
If IsLoginValid(username, password) Then
' user has supplied valid credentials
Else
MessageBox.Show("Invalid username or password.")
End If
End Sub
End Class
Of course, you still have to create the code to put the appropriate data in the database when the user is registered.
I've just added in a function to hash and salt passwords which are stored in an Access database "Memo" field.
The hashing/salting works fine, but I can't find anything on the internet which tells me how to then decrypt them.
I did see somewhere that says you can't, but instead have to get the password from the database, then hash the entered password (for a log on screen) and compare the 2 strings. I've tried this, but the 2 strings are then different, so I cannot log in.
The algorithms for creating the hash/salt are
Public Shared Function createRandomSalt() As String
Dim mix As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!£$%^&*()-_=+{}][#'~#:;?/>.<,\|"
Dim salt As String = ""
Dim rnd As New Random
Dim sb As New StringBuilder
For i As Integer = 1 To 50
Dim x As Integer = rnd.Next(0, mix.Length - 1)
salt &= (mix.Substring(x, 1))
Next
Return salt
End Function
Public Shared Function Hash512(ByVal password As String, ByVal salt As String)
Dim convertedToBytes As Byte() = Encoding.UTF8.GetBytes(password & salt)
Dim hashType As HashAlgorithm = New SHA512Managed()
Dim hashBytes As Byte() = hashType.ComputeHash(convertedToBytes)
Dim hashedResult As String = Convert.ToBase64String(hashBytes)
Return hashedResult
End Function
Then, when logging in, I'm trying the following
sql = "SELECT * FROM [Users] WHERE [User_ID] = ?"
Dim sCmd As New OleDb.OleDbCommand(sql, mainDBconnection)
sCmd.Parameters.Add("#ID", OleDb.OleDbType.VarChar).Value = txtUser.Text
mainDBadapter = New OleDb.OleDbDataAdapter(sCmd)
mainDBset = New DataSet
mainDBadapter.Fill(mainDBset)
For Each userRow In mainDBset.Tables(0).Rows
Dim password As String = ""
password = mainDBset.Tables(0).Rows(0).Item("Password")
Dim checkPassword As String = (frmSystemSettings.Hash512(password, frmSystemSettings.createRandomSalt))
If userRow.Item("User_ID") = txtUser.Text And password = checkPassword Then
Am I doing something wrong? How can I compare the entered password to the encrypted password in the database?
The problem is you are using a random salt when hashing the entered password. Since that is different from the random salt you used when storing the hash to the DB you get different hashes.
You have to do the following:
Before storing the password to the DB create a random salt, hash the password with it and store the salt together with the password in the database
When a user enters his password retrieve that user's salt from the database, use it to hash the entered password and compare the result to the hash from the database.
Oh, and you seem to never use the password the user entered. In your code you retrieve the hash from the DB into password, hash that hash again into checkpassword and compare those. Of course you have to hash the entered password.
I have used the following function to encrypt my password:
HashPasswordForStoringInConfigFile(Password, "MD5")
Now I want to decrypt the password again.
Note I'm showing the encrypted password in a grid-view and I want to decrypt it when the particular row goes in edit mode.
The simple answer is "you can't"
The idea of hashing is to generate a "safe" code from the real password, where that code can be stored in clear text; in a database (or text file) somewhere where other users might be seeing it somehow.
When someone tries to login, your system would compute another hash from the new login and then compare with the existing hash from your existing database, if the hash matches, then you know it's the correct password and then you can allow them to login, otherwise, it's not the same password / login-failed.
The reason why you cannot reverse the hash is because a hash is computed by doing the following steps:
1) Taking the password into some algorithm to:
2) Generate a very large string, then:
3) Chop that string and:
4) Take a part of it as your "hash"
So you see, even if you are superman in decoding and can figure the algorithm out, and know the hash code, and managed to reverse it back into the original form, then you would still have parts of the password missing, hence unsuccessful.
This is why Hashes are secure.
I hope this explains it.
you can make encrypt and decrypt function like this to encrypt and decrypt your text , and further u can use as per your need to display the decrypt text
here is the function
Public Function Encrypt(ByVal plainText As String) As String
Dim passPhrase As String = "yourPassPhrase"
Dim saltValue As String = "mySaltValue"
Dim hashAlgorithm As String = "MD5"
Dim passwordIterations As Integer = 2
Dim initVector As String = "#1B2c3D4e5F6g7H8"
Dim keySize As Integer = 256
Dim initVectorBytes As Byte() = Encoding.ASCII.GetBytes(initVector)
Dim saltValueBytes As Byte() = Encoding.ASCII.GetBytes(saltValue)
Dim plainTextBytes As Byte() = Encoding.UTF8.GetBytes(plainText)
Dim password As New PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations)
Dim keyBytes As Byte() = password.GetBytes(keySize \ 8)
Dim symmetricKey As New RijndaelManaged()
symmetricKey.Mode = CipherMode.CBC
Dim encryptor As ICryptoTransform = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)
Dim memoryStream As New MemoryStream()
Dim cryptoStream As New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
cryptoStream.FlushFinalBlock()
Dim cipherTextBytes As Byte() = memoryStream.ToArray()
memoryStream.Close()
cryptoStream.Close()
Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
Return cipherText
End Function
and for decrypt use this
Public Function Decrypt(ByVal cipherText As String) As String
Dim passPhrase As String = "yourPassPhrase"
Dim saltValue As String = "mySaltValue"
Dim hashAlgorithm As String = "MD5"
Dim passwordIterations As Integer = 2
Dim initVector As String = "#1B2c3D4e5F6g7H8"
Dim keySize As Integer = 256
' Convert strings defining encryption key characteristics into byte
' arrays. Let us assume that strings only contain ASCII codes.
' If strings include Unicode characters, use Unicode, UTF7, or UTF8
' encoding.
Dim initVectorBytes As Byte() = Encoding.ASCII.GetBytes(initVector)
Dim saltValueBytes As Byte() = Encoding.ASCII.GetBytes(saltValue)
' Convert our ciphertext into a byte array.
Dim cipherTextBytes As Byte() = Convert.FromBase64String(cipherText)
' First, we must create a password, from which the key will be
' derived. This password will be generated from the specified
' passphrase and salt value. The password will be created using
' the specified hash algorithm. Password creation can be done in
' several iterations.
Dim password As New PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations)
' Use the password to generate pseudo-random bytes for the encryption
' key. Specify the size of the key in bytes (instead of bits).
Dim keyBytes As Byte() = password.GetBytes(keySize \ 8)
' Create uninitialized Rijndael encryption object.
Dim symmetricKey As New RijndaelManaged()
' It is reasonable to set encryption mode to Cipher Block Chaining
' (CBC). Use default options for other symmetric key parameters.
symmetricKey.Mode = CipherMode.CBC
' Generate decryptor from the existing key bytes and initialization
' vector. Key size will be defined based on the number of the key
' bytes.
Dim decryptor As ICryptoTransform = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)
' Define memory stream which will be used to hold encrypted data.
Dim memoryStream As New MemoryStream(cipherTextBytes)
' Define cryptographic stream (always use Read mode for encryption).
Dim cryptoStream As New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
' Since at this point we don't know what the size of decrypted data
' will be, allocate the buffer long enough to hold ciphertext;
' plaintext is never longer than ciphertext.
Dim plainTextBytes As Byte() = New Byte(cipherTextBytes.Length - 1) {}
' Start decrypting.
Dim decryptedByteCount As Integer = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
' Close both streams.
memoryStream.Close()
cryptoStream.Close()
' Convert decrypted data into a string.
' Let us assume that the original plaintext string was UTF8-encoded.
Dim plainText As String = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
' Return decrypted string.
Return plainText
End Function
and call the function you will get the result.
i try to make function for lost password in my website using vb.net the code below is fore encrypt
Function getMD5Hash(ByVal strToHash As String) As String
Dim md5Obj As New System.Security.Cryptography.MD5CryptoServiceProvider
Dim bytesToHash() As Byte = System.Text.Encoding.ASCII.GetBytes(strToHash)
bytesToHash = md5Obj.ComputeHash(bytesToHash)
Dim strResult As String = ""
For Each b As Byte In bytesToHash
strResult += b.ToString("x2")
Next
Return strResult
End Function
MD5 is a hashing algorithm, not a bidirectional encryption system.
For lost password, you should send a password reset token instead of sending the end user the password.
I'm using the same function to hash values for comparison during login as I am to hash the passwords when users register:
Public Shared Function Compute(ByVal text As String, ByVal algorithm As String, Optional ByVal salt() As Byte = Nothing) As String
If salt Is Nothing Then
Dim saltSize As Integer = 8
salt = New Byte(saltSize - 1) {}
Dim rng As New RNGCryptoServiceProvider
rng.GetNonZeroBytes(salt)
End If
Dim textBytes As Byte() = Encoding.UTF8.GetBytes(text)
Dim saltedTextBytes() As Byte = New Byte(textBytes.Length + salt.Length - 1) {}
For i As Integer = 0 To textBytes.Length - 1
saltedTextBytes(i) = textBytes(i)
Next i
For i As Integer = 0 To salt.Length - 1
saltedTextBytes(textBytes.Length + i) = salt(i)
Next i
Dim hash As HashAlgorithm
If algorithm Is Nothing Then
algorithm = ""
End If
Select Case algorithm.ToUpper
Case "SHA1" : hash = New SHA1Managed
Case "SHA256" : hash = New SHA256Managed
Case "SHA384" : hash = New SHA384Managed
Case "SHA512" : hash = New SHA512Managed
Case Else : hash = New MD5CryptoServiceProvider
End Select
Dim hashBytes As Byte() = hash.ComputeHash(saltedTextBytes)
Dim saltedHash() As Byte = New Byte(hashBytes.Length + salt.Length - 1) {}
For i As Integer = 0 To hashBytes.Length - 1
saltedHash(i) = hashBytes(i)
Next i
For i As Integer = 0 To salt.Length - 1
saltedHash(hashBytes.Length + i) = salt(i)
Next i
Dim hashValue As String = Convert.ToBase64String(saltedHash)
Return Left(hashValue, 36)
End Function
My problem is that when I try to log in on an account whose password was hashed by this function, the hashed values don't match up. I think I'm skipping a step or something.
Here's the code for user account creation:
' The email address needs to be valid
Dim pattern As String = "^(?("")("".+?""#)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])#))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$"
Dim match As Match = Regex.Match(txtEmail.Text, pattern)
If match.Success Then
'Hash the user's password before entering it into the database.
Dim pass As String = Crypt.Compute(txtPass.Text, "SHA512", Nothing)
' Enter the information from the form into the database.
Dim sql As String = "INSERT INTO Users(Username, Password, EmailAddress) " & _
"VALUES(#User, #Pass, #Email)"
Dim cmd As New SqlCommand(sql, conn)
cmd.Parameters.AddWithValue("#User", txtName.Text)
cmd.Parameters.AddWithValue("#Pass", pass)
cmd.Parameters.AddWithValue("#Email", txtEmail.Text)
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
Else
lblError.Text = "Invalid email address. Please correct."
lblError.ForeColor = Drawing.Color.Red
End If
There are more checks that aren't included here that aren't relevant to my problem.
Here's my user login:
Dim pass As String = Crypt.Compute(txtPass.Text, "SHA512", Nothing)
Dim UserData As New DataSet
Dim UserAdapter As New SqlDataAdapter
UserAdapter.SelectCommand = New SqlCommand("SELECT * FROM Users " & _
"WHERE Username = #User AND Password = #Pass", conn)
UserAdapter.SelectCommand.Parameters.AddWithValue("#User", txtUser.Text)
UserAdapter.SelectCommand.Parameters.AddWithValue("#Pass", pass)
UserAdapter.Fill(UserData)
If UserData.Tables(0).Rows.Count <> 1 Then
lblError.Text = "Invalid username or password."
lblError.ForeColor = Drawing.Color.Red
Session("LoginAttempt") = CInt(Session("LoginAttempt")) + 1
Else
Session("LoggedIn") = True
Response.Redirect("Home.aspx")
End If
As far as I can see, there is no difference in the hashing I've done here.
Does anyone have any ideas?
When you creating an account by inserting into the table, you are using txtName.Text for the username, but when checking the credentials you are using txtUser.Text.
Why are you using a random salt? Doesn't the salt have to be the same for every encryption? I've pasted your code into a new project, and when I run the Compute method twice in a row for the same password, I get two different results... obviously that won't work. Try passing in a salt value instead of Nothing, and use the same salt for creating accounts and comparing login. Here's some sample code that works:
Dim thePass As String = "MyPassword"
Dim theSalt As String = "salt"
Dim pass As String = Compute(thePass, "SHA512", Encoding.UTF8.GetBytes(theSalt))
Console.WriteLine(pass)
Dim pass2 As String = Compute(thePass, "SHA512", Encoding.UTF8.GetBytes(theSalt))
Console.WriteLine(pass2) 'pass and pass2 are identical
Hope this helps!
Unless I'm missing it (not really familiar with the language), you don't store the salt anywhere.
You have to use the same salt you've used when creating the account for the verification.
On a side note: You can either generate a random salt for every user account or use a fixed salt for all accounts. Either method works. The first is theoretically more secure, but if the salt is long enough, both are fine for practical purposes.