Regex, aby sprawdzić siłę hasła


142

Moje kryteria siły hasła są następujące:

  • Długość 8 znaków
  • 2 wielkie litery
  • 1 znak specjalny (!@#$&*)
  • 2 cyfry (0-9)
  • 3 małe litery

Czy ktoś może mi podać wyrażenie regularne na to samo. Wszystkie warunki muszą być spełnione przez hasło.


2
Czy naprawdę chcesz zaufać środkom bezpieczeństwa swojego hasła w całym Internecie?
Borealid

12
@Borealid: publikowanie zasad haseł zazwyczaj nie powinno znacząco wpływać na Twoje bezpieczeństwo. Jeśli tak, oznacza to, że Twoje zasady są złe („Tylko passwordi hello123są prawidłowe hasła!”).
Joachim Sauer

3
@Joachim Sauer: Nie to miałem na myśli. Chodziło mi o to, że plakat prawdopodobnie po prostu zaufa dowolnemu wyrażeniu regularnemu, które otrzyma. Nie taki dobry pomysł.
Borealid

3
Właściwie to wyrażenie regularne będzie w kodzie serwisowym, będę testować przypadki różnic, a nie ślepo mu ufać :)
Ajay Kelkar

9
Złożone zasady dotyczące haseł zwykle nie prowadzą do bezpieczniejszych haseł, ważna jest tylko minimalna długość. Ludzie nie pamiętają ton silnych haseł, a takie zasady mogą kolidować z dobrymi schematami haseł. Ludzie mogą być bardzo pomysłowi omijając takie zasady, np. Używając słabych haseł, takich jak „Hasło-2014”. Często kończy się to ze słabszymi hasłami zamiast silniejszych.
martinstoeckli

Odpowiedzi:


427

Możesz przeprowadzić te sprawdzenia, używając twierdzeń pozytywnych przewidujących:

^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$

Link rubularny

Wyjaśnienie:

^                         Start anchor
(?=.*[A-Z].*[A-Z])        Ensure string has two uppercase letters.
(?=.*[!@#$&*])            Ensure string has one special case letter.
(?=.*[0-9].*[0-9])        Ensure string has two digits.
(?=.*[a-z].*[a-z].*[a-z]) Ensure string has three lowercase letters.
.{8}                      Ensure string is of length 8.
$                         End anchor.

92
Dla każdego, kto chce długość co najmniej nwymienić .{8}z.{n,}
NullUserException

14
+1 za pełne wyjaśnienie. Moje zasady dotyczące hasła są inne, ale na podstawie Twojej odpowiedzi mogę dostosować wyrażenie regularne.
Morvael

14
Dziękuję za opisanie, co się dzieje w wyrażeniu regularnym. To świetny przykład do nauki dla tych z nas, którzy nigdy nie radzili sobie ze składnią.

4
Doceniam również wyjaśnienie wyrażenia regularnego. Zbyt wiele razy używam złożonych wyrażeń regularnych, które znalazłem, nie rozumiejąc, co się dzieje.
Nicholas Smith

4
Świetny wzór, zastanawiam się, dlaczego nie użyć kwantyfikatorów? Co najmniej 1 specjalny, 1 cyfra, 1 znak specjalny, 8 znaków: ^ (? =. * ([AZ]) {1,}) (? =. * [! @ # $ & *] {1,}) ( ? =. * [0-9] {1,}) (? =. * [Az] {1,}). {8,100} $
RockOnGom

11

Możesz użyć dodatnich wyprzedzeń o zerowej długości, aby osobno określić każde z ograniczeń:

(?=.{8,})(?=.*\p{Lu}.*\p{Lu})(?=.*[!@#$&*])(?=.*[0-9])(?=.*\p{Ll}.*\p{Ll})

Jeśli regex silnik nie obsługuje \pzapis i czystego ASCII wystarczy, można wymienić \p{Lu}z [A-Z]i \p{Ll}z [a-z].


8

Odpowiedzi podane powyżej są doskonałe, ale sugeruję użycie kilku mniejszych wyrażeń regularnych zamiast dużego.
Dzielenie długiego wyrażenia regularnego ma kilka zalet:

  • łatwość pisania i czytania
  • łatwość debugowania
  • łatwość dodawania / usuwania części wyrażenia regularnego

Generalnie takie podejście zapewnia łatwość utrzymania kodu .

Powiedziawszy to, udostępniam fragment kodu, który piszę w języku Swift jako przykład:

struct RegExp {

    /**
     Check password complexity

     - parameter password:         password to test
     - parameter length:           password min length
     - parameter patternsToEscape: patterns that password must not contains
     - parameter caseSensitivty:   specify if password must conforms case sensitivity or not
     - parameter numericDigits:    specify if password must conforms contains numeric digits or not

     - returns: boolean that describes if password is valid or not
     */
    static func checkPasswordComplexity(password password: String, length: Int, patternsToEscape: [String], caseSensitivty: Bool, numericDigits: Bool) -> Bool {
        if (password.length < length) {
            return false
        }
        if caseSensitivty {
            let hasUpperCase = RegExp.matchesForRegexInText("[A-Z]", text: password).count > 0
            if !hasUpperCase {
                return false
            }
            let hasLowerCase = RegExp.matchesForRegexInText("[a-z]", text: password).count > 0
            if !hasLowerCase {
                return false
            }
        }
        if numericDigits {
            let hasNumbers = RegExp.matchesForRegexInText("\\d", text: password).count > 0
            if !hasNumbers {
                return false
            }
        }
        if patternsToEscape.count > 0 {
            let passwordLowerCase = password.lowercaseString
            for pattern in patternsToEscape {
                let hasMatchesWithPattern = RegExp.matchesForRegexInText(pattern, text: passwordLowerCase).count > 0
                if hasMatchesWithPattern {
                    return false
                }
            }
        }
        return true
    }

    static func matchesForRegexInText(regex: String, text: String) -> [String] {
        do {
            let regex = try NSRegularExpression(pattern: regex, options: [])
            let nsString = text as NSString
            let results = regex.matchesInString(text,
                options: [], range: NSMakeRange(0, nsString.length))
            return results.map { nsString.substringWithRange($0.range)}
        } catch let error as NSError {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
}

Ponadto, używając złożonego wyrażenia regularnego, takiego jak powyżej, bardzo łatwo jest otworzyć się na katastroficzne cofanie ( regular-expressions.info/catastrophic.html ). Może to pozostać niezauważone, aż pewnego dnia serwer zawiesza się ze 100% procesorem, ponieważ użytkownik użył „dziwnego” hasła. Przykład: ^ ([a-z0-9] +) {8,} $ (czy widzisz błąd?)
aKzenT

5

Sugerowałbym dodanie

(?!.*pass|.*word|.*1234|.*qwer|.*asdf) exclude common passwords

1

Rozwiązanie codaddict działa dobrze, ale to jest nieco bardziej wydajne: (składnia Pythona)

password = re.compile(r"""(?#!py password Rev:20160831_2100)
    # Validate password: 2 upper, 1 special, 2 digit, 1 lower, 8 chars.
    ^                        # Anchor to start of string.
    (?=(?:[^A-Z]*[A-Z]){2})  # At least two uppercase.
    (?=[^!@#$&*]*[!@#$&*])   # At least one "special".
    (?=(?:[^0-9]*[0-9]){2})  # At least two digit.
    .{8,}                    # Password length is 8 or more.
    $                        # Anchor to end of string.
    """, re.VERBOSE)

Klasy znaków zanegowanych pochłaniają wszystko do pożądanego znaku w jednym kroku, nie wymagając żadnego cofania. (Rozwiązanie z gwiazdą kropkową działa dobrze, ale wymaga pewnego cofania). Oczywiście w przypadku krótkich ciągów znaków docelowych, takich jak hasła, ta poprawa wydajności będzie nieistotna.


Czy mógłbyś sprawdzić, czy to prawda? Mam wątpliwości z powodu otwierającego nawias okrągły w pierwszej linii między potrójnym cudzysłowem a znakiem zapytania. Widzę, że komentarz Pythona (skrót) jest później. Nie widzę korespondenta zamykającego okrągłego nawiasu blisko końca kotwicy (znak dolara). Powinienem wspomnieć, że nie jestem profy regex.
lospejos

@lospejos - # nie jest początkiem zwykłego komentarza w jednej linii. Ten skrót jest częścią grupy komentarzy, która zaczyna się od (?#a kończy się a ). W tym wyrażeniu regularnym nie ma niezrównoważonych par.
ridgerunner

1
import re

RegexLength=re.compile(r'^\S{8,}$')
RegexDigit=re.compile(r'\d')
RegexLower=re.compile(r'[a-z]')
RegexUpper=re.compile(r'[A-Z]')


def IsStrongPW(password):
    if RegexLength.search(password) == None or RegexDigit.search(password) == None or RegexUpper.search(password) == None or RegexLower.search(password) == None:
        return False
    else:
        return True

while True:
    userpw=input("please input your passord to check: \n")
    if userpw == "exit":
        break
    else:
        print(IsStrongPW(userpw))

1

Rozwiązanie @ codaddict będzie działać.

Powinieneś także rozważyć zmianę niektórych zasad, aby:

  1. Dodaj więcej znaków specjalnych, np.%, ^, (,), -, _, + i kropka. Dodaję wszystkie pominięte znaki specjalne nad znakami liczbowymi na klawiaturach w USA. Ucieknij z tych, których używa regex.
  2. Wprowadź hasło co najmniej 8 znaków. Nie tylko statyczna liczba 8.

Dzięki powyższym ulepszeniom oraz dla większej elastyczności i czytelności zmodyfikowałbym wyrażenie regularne do.

^(?=.*[a-z]){3,}(?=.*[A-Z]){2,}(?=.*[0-9]){2,}(?=.*[!@#$%^&*()--__+.]){1,}.{8,}$

Podstawowe wyjaśnienie

(?=.*RULE){MIN_OCCURANCES,}     Each rule block is shown by (){}. The rule and number of occurrences can then be easily specified and tested separately, before getting combined

Szczegółowe wyjaśnienie

^                             start anchor
(?=.*[a-z]){3,}               lowercase letters. {3,} indicates that you want 3 of this group
(?=.*[A-Z]){2,}               uppercase letters. {2,} indicates that you want 2 of this group
(?=.*[0-9]){2,}               numbers. {2,} indicates that you want 2 of this group
(?=.*[!@#$%^&*()--__+.]){1,}   all the special characters in the [] fields. The ones used by regex are escaped by using the \ or the character itself. {1,} is redundant, but good practice, in case you change that to more than 1 in the future. Also keeps all the groups consistent
{8,}                          indicates that you want 8 or more
$                             end anchor

I na koniec, do celów testowych, jest tutaj robulink z powyższym wyrażeniem regularnym


Dzięki @AFract. Używam go w moim kodzie. Podoba mi się czytelność i powtarzalność, gdy trzeba wrócić i zmienić to w przyszłości, np. W przypadku zmiany polityki haseł :)
lsu_guy

0

W przypadku PHP to działa dobrze!

 if(preg_match("/^(?=(?:[^A-Z]*[A-Z]){2})(?=(?:[^0-9]*[0-9]){2}).{8,}$/", 
 'CaSu4Li8')){
    return true;
 }else{
    return fasle;
 }

w tym przypadku wynik jest prawdziwy

Podziękowania dla @ridgerunner


dlaczego nie return preg_match("/^(?=(?:[^A-Z]*[A-Z]){2})(?=(?:[^0-9]*[0-9]){2}).{8,}$/", 'CaSu4Li8')?
aloisdg przenosi się do codidact.com

0

Inne rozwiązanie:

import re

passwordRegex = re.compile(r'''(
    ^(?=.*[A-Z].*[A-Z])                # at least two capital letters
    (?=.*[!@#$&*])                     # at least one of these special c-er
    (?=.*[0-9].*[0-9])                 # at least two numeric digits
    (?=.*[a-z].*[a-z].*[a-z])          # at least three lower case letters
    .{8,}                              # at least 8 total digits
    $
    )''', re.VERBOSE)

def userInputPasswordCheck():
    print('Enter a potential password:')
    while True:
        m = input()
        mo = passwordRegex.search(m) 
        if (not mo):
           print('''
Your password should have at least one special charachter,
two digits, two uppercase and three lowercase charachter. Length: 8+ ch-ers.

Enter another password:''')          
        else:
           print('Password is strong')
           return
userInputPasswordCheck()

0

Hasło musi spełniać co najmniej 3 z poniższych 4 zasad złożoności,

[co najmniej 1 duża litera (AZ) co najmniej 1 mała litera (az) co najmniej 1 cyfra (0-9) co najmniej 1 znak specjalny - nie zapomnij również traktować spacji jako znaków specjalnych]

co najmniej 10 znaków

maksymalnie 128 znaków

nie więcej niż 2 identyczne znaki w rzędzie (np. 111 niedozwolonych)

'^ (?!. (.) \ 1 {2}) ((? =. [Az]) (? =. [AZ]) (? =. [0-9]) | (? =. [Az] ) (? =. [AZ]) (? =. [^ A-zA-Z0-9]) | (? =. [AZ]) (? =. [0-9]) (? =. [^ A -zA-Z0-9]) | (? =. [az]) (? =. [0-9]) (? =. * [^ a-zA-Z0-9])). {10,127} $ '

(?!. * (.) \ 1 {2})

(? =. [az]) (? =. [AZ]) (? =. * [0-9])

(? =. [az]) (? =. [AZ]) (? =. * [^ a-zA-Z0-9])

(? =. [AZ]) (? =. [0-9]) (? =. * [^ A-zA-Z0-9])

(? =. [az]) (? =. [0-9]) (? =. * [^ a-zA-Z0-9])

. {10.127}


0

Wszystkie powyższe wyrażenia regularne niestety nie działały dla mnie. Podstawowe zasady silnego hasła to

  • Powinien zawierać co najmniej dużą literę
  • Powinien zawierać przynajmniej małą literę
  • Powinien zawierać co najmniej liczbę
  • Powinien zawierać co najmniej znak specjalny
  • I minimalna długość

Więc najlepszy byłby Regex

^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*]).{8,}$

Powyższe wyrażenie regularne ma minimalną długość 8. Możesz je zmienić z {8,} na { any_number ,}

Modyfikacja zasad?

powiedzmy, że chcesz mieć minimum x znaków małych liter, y znaków wielkich liter, z znaków liczb, Całkowita minimalna długość w . Następnie spróbuj poniżej wyrażenia regularnego

^(?=.*[a-z]{x,})(?=.*[A-Z]{y,})(?=.*[0-9]{z,})(?=.*[!@#\$%\^&\*]).{w,}$

Uwaga: zmień x , y , z , w w wyrażeniu regularnym

Edycja: zaktualizowana odpowiedź wyrażenia regularnego

Edit2: Dodano modyfikację


Twoje wyrażenie regularne pasuje 12345678, czy na pewno jest to silne hasło? Przed wysłaniem wypróbuj swoje wyrażenie regularne.
Toto

To lepiej, ale nie odpowiada na pytanie, chcą 1) długości 8 znaków. 2) 2 wielkie litery. 3) 1 znak specjalny (! @ # $ & *). 4) 2 cyfry (0-9). 5) 3 małe litery.
Toto

@Toto Czy możesz teraz podzielić się swoimi przemyśleniami?
Juned Khatri

Twoje wyrażenie regularne nie bierze pod uwagę, że 2 obowiązkowe wielkie litery mogą być oddzielone innymi znakami, ta sama uwaga dla małych liter i cyfr. Prawidłowa odpowiedź to ta, która została zaakceptowana.
Toto
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.