0

We have a user table in a SQL Server 2014 that I link to in an Access database front end and the password is in binary 64 so that the password cannot be seen if someone were to open the table somehow in SSMS.

But Access knows all this and completely converts it to the actual password. How do I get around this yet still use it to validate data entered into a login form?

1 Answers1

0

You hash the password. Storing passwords as plaintext without hashing is a major bad practice.

Read more about hashing on Wikipedia. The short version it's a one-way operation: if you have the password, you can create the hash, but if you have the hash, there's no way to get the password except trying to hash random passwords and see if they're the same.

However, hashing in VBA is rather complicated. There are more simple answers that use .Net hashing objects, but I use the CNG API, which has numerous advantages such as hardware crypto support, zero dependencies and flexibility in the choice of algorithm:

Public Declare PtrSafe Function BCryptOpenAlgorithmProvider Lib "BCrypt.dll" (ByRef phAlgorithm As LongPtr, ByVal pszAlgId As LongPtr, ByVal pszImplementation As LongPtr, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptCloseAlgorithmProvider Lib "BCrypt.dll" (ByVal hAlgorithm As LongPtr, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptCreateHash Lib "BCrypt.dll" (ByVal hAlgorithm As LongPtr, ByRef phHash As LongPtr, pbHashObject As Any, ByVal cbHashObject As Long, ByVal pbSecret As LongPtr, ByVal cbSecret As Long, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptHashData Lib "BCrypt.dll" (ByVal hHash As LongPtr, pbInput As Any, ByVal cbInput As Long, Optional ByVal dwFlags As Long = 0) As Long
Public Declare PtrSafe Function BCryptFinishHash Lib "BCrypt.dll" (ByVal hHash As LongPtr, pbOutput As Any, ByVal cbOutput As Long, ByVal dwFlags As Long) As Long
Public Declare PtrSafe Function BCryptDestroyHash Lib "BCrypt.dll" (ByVal hHash As LongPtr) As Long
Public Declare PtrSafe Function BCryptGetProperty Lib "BCrypt.dll" (ByVal hObject As LongPtr, ByVal pszProperty As LongPtr, ByRef pbOutput As Any, ByVal cbOutput As Long, ByRef pcbResult As Long, ByVal dfFlags As Long) As Long

Public Function NGHash(pData As LongPtr, lenData As Long, Optional HashingAlgorithm As String = "SHA1") As Byte()
    'Erik A, 2019
    'Hash data by using the Next Generation Cryptography API
    'Loosely based on https://learn.microsoft.com/en-us/windows/desktop/SecCNG/creating-a-hash-with-cng
    'Allowed algorithms:  https://learn.microsoft.com/en-us/windows/desktop/SecCNG/cng-algorithm-identifiers. Note: only hash algorithms, check OS support
    'Error messages not implemented
    On Error GoTo VBErrHandler
    Dim errorMessage As String

    Dim hAlg As LongPtr
    Dim algId As String

    'Open crypto provider
    algId = HashingAlgorithm & vbNullChar
    If BCryptOpenAlgorithmProvider(hAlg, StrPtr(algId), 0, 0) Then GoTo ErrHandler

    'Determine hash object size, allocate memory
    Dim bHashObject() As Byte
    Dim cmd As String
    cmd = "ObjectLength" & vbNullString
    Dim Length As Long
    If BCryptGetProperty(hAlg, StrPtr(cmd), Length, LenB(Length), 0, 0) <> 0 Then GoTo ErrHandler
    ReDim bHashObject(0 To Length - 1)

    'Determine digest size, allocate memory
    Dim hashLength As Long
    cmd = "HashDigestLength" & vbNullChar
    If BCryptGetProperty(hAlg, StrPtr(cmd), hashLength, LenB(hashLength), 0, 0) <> 0 Then GoTo ErrHandler
    Dim bHash() As Byte
    ReDim bHash(0 To hashLength - 1)

    'Create hash object
    Dim hHash As LongPtr
    If BCryptCreateHash(hAlg, hHash, bHashObject(0), Length, 0, 0, 0) <> 0 Then GoTo ErrHandler

    'Hash data
    If BCryptHashData(hHash, ByVal pData, lenData) <> 0 Then GoTo ErrHandler
    If BCryptFinishHash(hHash, bHash(0), hashLength, 0) <> 0 Then GoTo ErrHandler

    'Return result
    NGHash = bHash
ExitHandler:
    'Cleanup
    If hAlg <> 0 Then BCryptCloseAlgorithmProvider hAlg, 0
    If hHash <> 0 Then BCryptDestroyHash hHash
    Exit Function
VBErrHandler:
    errorMessage = "VB Error " & Err.Number & ": " & Err.Description
ErrHandler:
    If errorMessage <> "" Then MsgBox errorMessage
    Resume ExitHandler
End Function


Public Function HashBytes(Data() As Byte, Optional HashingAlgorithm As String = "SHA512") As Byte()
    HashBytes = NGHash(VarPtr(Data(LBound(Data))), UBound(Data) - LBound(Data) + 1, HashingAlgorithm)
End Function

Public Function HashString(str As String, Optional HashingAlgorithm As String = "SHA512") As Byte()
    HashString = NGHash(StrPtr(str), Len(str) * 2, HashingAlgorithm)
End Function

You can now use the HashString function to hash passwords. When someone enters a password, always use HashString(password) to look up the password or store a hashed password. You never store an actual unhashed password.

Of course, this also means that even you can not view passwords of users, only their hashes.

If you want to improve this further, you can use a salt to avoid rainbow table attacks. But only adding a hash will already substantially improve security.

Erik A
  • 31,639
  • 12
  • 42
  • 67
  • @Albert Yes, it does not matter if you hash values in tables stored in Access, linked tables, or anything. Of course, this is not related to the connection string, since this question is not related to the connection string – Erik A May 07 '20 at 20:36
  • 1
    Ah, I do see now - the password in the linked table is not the issue, it only the data in the table and that of a encrypted column. So, I do see this question in a different light. You are 100% correct - the question is about viewing data in the linked table, not the password used for the linked table. I think I will delete my post. – Albert D. Kallal May 08 '20 at 03:56
  • My question really had both of your posts in mind and both were excellent. I am looking for the best way to log into a SQL Server without having to hard code any credentials into the Access database but also was having a hard time with the morality of having an open table available in plain sight for all to see. I have been trying to get a connection string to work to SQL Server because right now nothing is working but I am going to introduce this has effort now as well to see which one is best for needs. Thank you both. I may have more questions if I can't figure this out. – JustAnotherFaceInTheCode May 11 '20 at 14:46
  • So the SQL Server table "User" is linked to from the Access database but when I open the table in Access it shows the actual password (it is some how converting it to plain text). How do I display the encrypted (for lack of a better word) password when someone opens the table in Access instead the converted plain text? – JustAnotherFaceInTheCode May 11 '20 at 20:41
  • Eh, you store the hash, not the password. You never store the password – Erik A May 11 '20 at 20:43
  • @JustAnotherFaceInTheCode I believe you are referring to different things. DO you have a user account for each user connecting to the database or do you have a user table within the database? You should be using a connection string but the issue you are rreferring to sounds like you are linking tables as I ahve seen that problem too. – Glenn Angel May 22 '20 at 02:47
  • I use the DNS ODBC to connect which is using Windows Authentication to the SQL Server so that I don't have to store the credentials in the ODBC string. – JustAnotherFaceInTheCode May 27 '20 at 19:04