I need to implement some code to test if a password is complex enough, and I have a regex expression for this. How can I simply test if a supplied string matches the regex patter (is complex enough) or not (not complex enough)?
Many thanks
Steve
I need to implement some code to test if a password is complex enough, and I have a regex expression for this. How can I simply test if a supplied string matches the regex patter (is complex enough) or not (not complex enough)?
Many thanks
Steve
If your Pattern does not match Return a Boolean (False) from your Method.
Var match As RegExMatch = rx.Search( myString )
If match <> Nil Then
Return True
End If
Return False
// Or simply "Return (match <> Nil)"
The NIST says specifically NOT to do this.
3.1.1.2 Password Verifiers
- Verifiers and CSPs SHALL NOT impose other composition rules (e.g., requiring mixtures of different character types) for passwords.
Or simply:
if rx.Search( myString ) is nil then ...
Indeed - but client says they want it and they are paying…
I know that situation all too well. Just sharing the latest information, which has changed (for the better) recently. My bank is now violation of the standards, forcing password resets
If one of the RegEx answers has solved this for you, please mark one of them as the solution That flags the thread as “solved” which helps users searching for answers.
I have this method I found here on this forum IIRC …
Public Function AnalyzePasswordStrength(pw As String, analysis As Dictionary = nil) As Integer
// Returns a score loosely based on the formula presented at http://passwordmeter.com
// If a dictionary is provided, will fill it with statistics.
// The requirement bonus is for a minimum of kMinLength characters and 3 of 4 of
// - Uppercase
// - Lowercase
// - Numbers
// - Symbols
// One difference of note: UTF-8 multi-byte characters are given a bonus. The more bytes, the higher then bonus.
const kMinLength = 8
const kMultLetter = 2
const kMultNumber = 4
const kMultSymbol = 6
const kMultLength = 4
const kMultMiddle = 2
const kMultConsecutive = 2
const kMultSequential = 3
const kMultMultiByte = 4 // Every additional byte is almost like another symbol
if pw = "" then return 0
pw = pw.ConvertEncoding( Encodings.UTF8 )
dim pwLen as integer = pw.Len
dim pwLenB as integer = pw.LenB
dim lowerCnt, upperCnt, numberCnt, letterCnt, symbolCnt, middleCnt as integer
dim consecutiveCnt, sequentialCnt as integer
dim requirementScore, upperScore, lowerScore, numberScore, symbolScore, lengthScore, middleScore, multiByteScore as integer
dim allLetterDeduction, allNumberDeduction, consecutiveDeduction, sequentialDeduction as integer
dim requirementsMet as integer
dim repeatCnt as integer
dim repeatDeduction as double
dim score as integer
dim thisChar, prevChar, prevPrevChar as string
dim thisCharCode as integer
dim thisCharType, prevCharType, prevPrevCharType as CharacterType
dim isUppercase, prevIsUppercase as boolean
thisCharType = CharacterType.Unknown
prevCharType = CharacterType.Unknown
prevPrevCharType = CharacterType.Unknown
dim chars() as string = pw.Split( "" )
dim lastCharIndex as integer = chars.Ubound
for charIndex as integer = 0 to lastCharIndex
thisChar = chars( charIndex )
thisCharCode = thisChar.Asc
if thisChar >= "0" and thisChar <= "9" then
thisCharType = CharacterType.Number
numberCnt = numberCnt + 1
elseif StrComp( thisChar.Uppercase, thisChar.Lowercase, 0 ) <> 0 then
thisCharType = CharacterType.Letter
letterCnt = letterCnt + 1
isUpperCase = ( StrComp( thisChar, thisChar.Uppercase, 0 ) = 0 )
if isUpperCase then
upperCnt = upperCnt + 1
else
lowerCnt = lowerCnt + 1
end if
else
thisCharType = CharacterType.Symbol
symbolCnt = symbolCnt + 1
end if
// Consecutive or sequentiial?
if thisCharType = prevCharType then
if thisCharType = CharacterType.Number then
consecutiveCnt = consecutiveCnt + 1 // Consecutive number
elseif thisCharType = CharacterType.Letter and isUppercase = prevIsUppercase then
consecutiveCnt = consecutiveCnt + 1
end if
if prevPrevCharType = thisCharType then // Can't do this with a control-A or control-B
dim actual as string = prevPrevChar + prevChar + thisChar
dim proposed as string
if thisCharCode > 2 then
proposed = Encodings.UTF8.Chr( thisCharCode - 2 ) + Encodings.UTF8.Chr( thisCharCode - 1 ) + thisChar
end if
if actual = proposed then
sequentialCnt = sequentialCnt + 1
elseif thisCharCode < 1112063 then // Max code point for UTF-8 is 1,112,064
proposed = Encodings.UTF8.Chr( thisCharCode + 2 ) + Encodings.UTF8.Chr( thisCharCode + 1 ) + thisChar
if actual = proposed then
sequentialCnt = sequentialCnt + 1
end if
end if
end if
end if
// Middle number or symbol?
if charIndex > 0 and charIndex < lastCharIndex and thisCharType <> CharacterType.Letter then
middleCnt = middleCnt + 1
end if
// See if this is a repeating character
dim repeatFound as boolean
for repeatIndex as integer = 0 to lastCharIndex
if repeatIndex <> charIndex and StrComp( thisChar, chars( repeatIndex ), 0 ) = 0 then
repeatFound = true
repeatDeduction = repeatDeduction + Abs( pwLen / ( repeatIndex - charIndex ) )
end if
next
if repeatFound then
repeatCnt = repeatCnt + 1
dim uniqueCnt as integer = pwLen - repeatCnt
if uniqueCnt <> 0 then
repeatDeduction = Ceil( repeatDeduction / uniqueCnt )
else
repeatDeduction = Ceil( repeatDeduction )
end if
end if
// Rotate for the next round
prevPrevChar = prevChar
prevChar = thisChar
prevPrevCharType = prevCharType
prevCharType = thisCharType
prevIsUppercase = isUppercase
next
// Calculate the score
lengthScore = pwLen * kMultLength
if upperCnt <> 0 then upperScore = ( pwLen - upperCnt ) * kMultLetter
if lowerCnt <> 0 then lowerScore = ( pwLen - lowerCnt ) * kMultLetter
numberScore = numberCnt * kMultNumber
symbolScore = symbolCnt * kMultSymbol
middleScore = middleCnt * kMultMiddle
if pwLen <> pwLenB then
multiByteScore = ( pwLenB - pwLen ) * kMultMultiByte
end
// Check requirements
if pwLen >= kMinLength then requirementsMet = requirementsMet + 1
if lowerCnt <> 0 then requirementsMet = requirementsMet + 1
if upperCnt <> 0 then requirementsMet = requirementsMet + 1
if symbolCnt <> 0 then requirementsMet = requirementsMet + 1
if numberCnt <> 0 then requirementsMet = requirementsMet + 1
if requirementsMet >= 4 then
requirementScore = requirementsMet * 2
end if
score = _
lengthScore + _
upperScore + _
lowerScore + _
numberScore + _
symbolScore + _
middleScore + _
multiByteScore + _
requirementScore
// Deduct for all letters or numbers
if numberCnt = pwLen then
allNumberDeduction = 0 - pwLen
elseif letterCnt = pwLen then
allLetterDeduction = 0 - pwLen
end if
// Deduct for consecutive characters
if consecutiveCnt <> 0 then
consecutiveDeduction = 0 - ( consecutiveCnt * kMultConsecutive )
end if
// Deduct for sequential characters
if sequentialCnt <> 0 then
sequentialDeduction = 0 - ( sequentialCnt * kMultSequential)
end if
repeatDeduction = 0 - repeatDeduction // Make negative
score = score + _
allLetterDeduction + _
allNumberDeduction + _
consecutiveDeduction + _
sequentialDeduction + _
repeatDeduction
if score > 100 then
score = 100
elseif score < 0 then
score = 0
end if
if analysis <> nil then
analysis.Value( "Score" ) = score
analysis.Value( "Length" ) = pwLen
analysis.Value( "Binary Length" ) = pwLenB
analysis.Value( "Letter Count" ) = letterCnt
analysis.Value( "Number Count" ) = numberCnt
analysis.Value( "Symbol Count" ) = symbolCnt
analysis.Value( "Uppercase Count" ) = upperCnt
analysis.Value( "Lowercase Count" ) = lowerCnt
analysis.Value( "Middle Number/Symbol Count" ) = middleCnt
analysis.Value( "Consecutive Character Count" ) = consecutiveCnt
analysis.Value( "Sequential Character Count" ) = sequentialCnt
analysis.Value( "Repeated Character Count" ) = repeatCnt
analysis.Value( "Requirements Met" ) = requirementsMet
analysis.Value( "Length Score" ) = lengthScore
analysis.Value( "Uppercase Score" ) = upperScore
analysis.Value( "Lowercase Score" ) = lowerScore
analysis.Value( "Number Score" ) = numberScore
analysis.Value( "Symbol Score" ) = symbolScore
analysis.Value( "Middle Number/Symbol Score" ) = middleScore
analysis.Value( "Multi-Byte Score" ) = multiByteScore
analysis.Value( "Requirement Score" ) = requirementScore
analysis.Value( "All Letter Deduction" ) = allLetterDeduction
analysis.Value( "All Number Deduction" ) = allNumberDeduction
analysis.Value( "Consecutive Character Deduction" ) = consecutiveDeduction
analysis.Value( "Sequential Character Deduction" ) = sequentialDeduction
analysis.Value( "Repeat Character Deduction" ) = CType( repeatDeduction, Integer )
End If
Return score
End Function
From that standard:
“This guideline focuses on the authentication of subjects who interact with government information systems over networks to establish that a given claimant is a subscriber who has been previously authenticated.”
I would suggest taking this document as a “best practices” because password “strength” and forcing password resets are both known to force users to choose less secure passwords. (both of these items are in the updated guidelines as DO NOT DO)
Relevant XKCD:
Hi @Steve_Johnson
I recently posted the same question, here’s the thread on it. HOWEVER after more testing the regex expression I thought worked doesn’t. I will go back to that thread to append and reopen it (so the body of knowledge is there for posterity.
Excited to see what the final solution really is!
BTW I’d like to thank @Kem_Tekinay again for all his help. I think this snippet works very well, if you stipulate “must also contain a lower-case letter”:
rg.SearchPattern = "^(?=.*\p{Lu})(?=.*\p{Ll})(?=.*\d).*"
“ALLCAPS+123” (fails)
“ALLCAPs+123” (passes)
“aLLCAPS+123” (passes)
I shared some code here to check a password against the list of known breached passwords via the haveibeenpwned API. This will require an internet connection but after you verify password requirements it’s a good final-check before allowing the user to select a particular password.
Thanks all who contributed.
I so agree with Tim Parnell, but have to bow down to my client’s wishes. I’ve tried pointing him to the doc Tim referenced, so maybe he’ll change his mind over the weekend.
S