Thank you to everyone who has replied with some great ideas.
@Thom_McGrath, thanks for the lead on the haveibeenpwned API. I’ve implemented it in my password requirements for when users are setting their passwords. If it helps anyone else, here is my method for checking a prospective password against the list of known breached passwords:
Private Function IsPasswordCompromised(password as String) As Boolean
// Checks if a password appears in known data breaches using the haveibeenpwned.com k-anonymity API
//
// Parameters:
// password - Password to check
//
// Returns:
// Boolean - True if password found in breaches, False if not found or on error
//
// Notes:
// - Uses k-anonymity to avoid sending full password hash
// - Only first 5 characters of hash are sent to API
// - Times out after 4 seconds
// - Returns false on any error (network, timeout, etc)
If password.Trim = "" Then
Return True
End If
// Calculate SHA1 hash of password
Var hash As String = EncodeHex(Crypto.SHA1(password))
hash = hash.Uppercase
// Get first 5 chars of hash for API query
Var prefix As String = hash.Left(5)
Var suffix As String = hash.Mid(6, hash.Length - 5) // Start FROM position 5, not AT position 5
// Set up URL request
Var url As String = "https://api.pwnedpasswords.com/range/" + prefix
Var conn As New URLConnection
conn.RequestHeader("Add-Padding") = "true" // Helps prevent timing attacks
conn.RequestHeader("User-Agent") = "SecurityCheck/1.0"
Var timeout As Integer = 4
Try
// Make request
Var response As String = conn.SendSync("GET", url, timeout)
// Check response code
If conn.HTTPStatusCode <> 200 Then
Return False
End If
// Split response into lines
Var lines() As String = response.ReplaceLineEndings(EndOfLine).Split(EndOfLine)
// Search for our hash suffix in the response
For Each line As String In lines
If line.Trim <> "" Then
Var parts() As String = line.Split(":")
If parts(0).Trim = suffix Then
// Found a match - password has been compromised
Return True
End If
End If
Next
// No match found
Return False
Catch ex As RuntimeException
// Return false on any error
Return False
End Try
End Function