Regex text returning true or false

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 ...
1 Like

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 :roll_eyes:

If one of the RegEx answers has solved this for you, please mark one of them as the solution :slight_smile: 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

1 Like

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:

2 Likes

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)

2 Likes

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

1 Like