0

I have read a number of pages now and am getting my mind around hashing passwords.

I created an MS Access Application which is connected to a MYSQL backend on a VPS. I'm using strong passwords for users, only allowing connections from Static IPS that I have allowed via Remote MYSQL and I am using an SSL connection with Cert and Key to the server.

My next step is to make sure the passwords are hashed and saved correctly but here is my dilemma:

  • User accounts are created on my site (PHP) before downloading my application and logging in using the username and password created.

I need to find a 'decently strong but not so stupid that I can't now use it' way of saving these passwords.

I am dealing with user names and addresses and potentially some confidential info.

Now my understanding (and I'm new to this hashing stuff) is that I should: - Create a random SALT for each new password - Store the salt in the table for users - Hash it using something like bCrypt or sCrypt (SALT + PASS) - Extra safety I could use HMAC with a secret key (store on a different server)

First of all - have i understood that correctly?

Second: - bCrpyt nor sCrypt are available on VBA on ms Access and since I have users login via this method I'm at a loss on where to go with this. - I did find a software package to buy (cryptoApi or something) that apparently cn give access to these in VBA adn php...

Any ideas or advice from here? Both php and vba will obviously need to use teh same systems. My current SHA1 isn't good enough by any standards!

Glenn Angel
  • 381
  • 1
  • 3
  • 14

1 Answers1

3

Some notes:

  1. HMAC is for message authentication, not hashing passwords.
  2. BCrypt or SCrypt are not implemented in the Windows API, while SHA256/384/512 are. SHA512 is plenty secure for hashing passwords.

If we go for SHA512, you can use a hashing approach I previously shared here. This code uses the Windows CNG API, and allowed hashing algorithms are listed here with corresponding OS support. A call is as simple as HashString("SomePassword" & "SomeSalt"). It returns a byte array, for convenience you could convert that to Base64 but storing it as binary is more efficient.

You can use the same API for generating random numbers (salts). I've outlined an approach here focused on integers, but you might want to generate random bytes and then prepend the string to those bytes.

An additional challenge is character encoding. PHP is UTF-8, Access is UTF-16 and doesn't really do UTF-8. If you limit possible characters to ASCII, you can use StrConv in VBA to cast the string to ANSI, and validate that there are no non-ASCII characters in there. If you want to convert strings to UTF-8 in VBA, that's another API call (WideCharToMultiByte) you need to use before hashing. Note that unicode in password is a UX hazard, because some operating systems express special characters as composite characters, and some don't, and that difference will cause passwords to mismatch, so there's an argument to be made for keeping it ASCII.

See the hashing code down below:

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
Erik A
  • 31,639
  • 12
  • 42
  • 67
  • wow ok this just blew my brain out :( So I am looking through what you have said and I get the idea but have no idea how to implement it. Obviously I can create this hash which eg. "hello" gives all these chinese characters. I can create a random salt with PHP and store in table, Get that and add to password, hash as SHA512 using your code. From here I am lost about the UTF 8 and 16 etc. Will the above not work as to what I am suggesting I can do? I dont need to generate a salt in VBA, just in PHP then store in table. – Glenn Angel May 22 '20 at 03:03
  • If you want to go the ASCII route, you can just use `HashBytes(StrConv("Hello", vbFromUnicode))`. You can verify this with an online string hasher. But then you need to make sure there are no unicode characters (e.g. emoji/Chinese characters/etc.) in the passwords. Else, you want to convert the string from UTF-16 to UTF-8, I recommend asking that in a separate thread. There are many on saving as UTF-8, but I can't find one on converting to UTF-8, am able to answer that but I think it deserves it's own question as it's a widely applicable subject. – Erik A May 22 '20 at 06:05
  • Great thanks @Erik I don't believe I will be dealing with anything other than basic Australian keyboard characters as this will be an Australian only application – Glenn Angel May 22 '20 at 06:35
  • That might be a dangerous assumption to make if you don't verify it on the PHP side, because people do strange things (say Elon Musk makes an account and wants to use his child's name as his password, or someone decides putting a poop emoji in their password is ultra-secure because no-one expects it). See [this answer](https://stackoverflow.com/a/6497818/7296893) on how to check it in PHP. – Erik A May 22 '20 at 06:50
  • Wiht the hashbytes using vbFromUnicode all I get is ???????????? as a string – Glenn Angel May 22 '20 at 07:34
  • First convert to ASCII, then hash, not the other way round, else you get that. Also, if you print the result to the immediate window that can result in questionmarks. Instead, use this function to inspect results: `Public Function ToHex(b() As Byte) As String Dim v As Variant For Each v In b ToHex = ToHex & Hex(v) Next End Function`. That also allows verification against online tools/known hashes. – Erik A May 22 '20 at 07:38
  • Try look in this site https://en.wikibooks.org/wiki/Visual_Basic_for_Applications/String_Hashing_in_VBA hope that helps . The Function SHA256 return string and not ???? – xShen May 22 '20 at 09:11
  • 1
    @xShen That code takes an unnecessary .Net dependency, has COM overhead + the insecurity that comes from passing passwords over late-bound COM, relies on an undocumented ordering of overloaded functions, and doesn't allow control over hardware crypto providers, I highly recommend not using it and just using the CNG code instead. Converting bytes to hex using MSXML only adds insult to injury, that's plain ridiculous. The function returns a hash which is binary by default. You can format hashes later with any function of your choosing. – Erik A May 22 '20 at 09:26