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.