Diversi anni fa ho sviluppato questa classe per calcolare il livello di sicurezza delle password inserite dagli utenti in varie circostanze, come ad esempio durante la registrazione di un nuovo account all'interno di una applicazione web. Poiché a distanza di tempo continuo a utilizzarla con regolarità ho deciso oggi di condividerla all'interno di questo post, sperando che possa essere utile anche a qualcun altro.
La classe può essere utilizzata in due modi:
- Attraverso il metodo generico GetPasswordStrength, che calcola il livello di sicurezza (strength) di una qualsiasi password attraverso una serie di controlli basati sui classici fattori: lunghezza minima, presenza di lettere maiuscole/minuscole, numeri e/o caratteri speciali.
- Attraverso il metodo IsStrongPassword, che risponde a un criterio più specifico e personalizzato.
Il primo metodo di utilizzo è particolarmente indicato in tutti i casi in cui non sono previste delle policy di controllo specifiche, mentre il secondo si rende necessario ogniqualvolta abbiamo dei controlli obbligatori da effettuare. Personalmente io finisco spesso per utilizzare entrambi: il primo per mostrare all'utente la forza della propria password, il secondo - o una sua implementazione leggermente diversa utilizzando gli helper methods inclusi - per controllare i requisiti minimi previsti e/o richiesti dal committente.
Tutti i metodi utilizzati sono spiegati all'interno della classe, quindi non c'è molto altro da dire: se la classe vi è di qualche aiuto, sentitevi liberi di lasciare un feedback nella sezione "commenti" in basso!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
using System; using System.Linq; namespace Ryadel.Components.Security { public enum PasswordStrength { /// <summary> /// Blank Password (empty and/or space chars only) /// </summary> Blank = 0, /// <summary> /// Either too short (less than 5 chars), one-case letters only or digits only /// </summary> VeryWeak = 1, /// <summary> /// At least 5 characters, one strong condition met (>= 8 chars with 1 or more UC letters, LC letters, digits & special chars) /// </summary> Weak = 2, /// <summary> /// At least 5 characters, two strong conditions met (>= 8 chars with 1 or more UC letters, LC letters, digits & special chars) /// </summary> Medium = 3, /// <summary> /// At least 8 characters, three strong conditions met (>= 8 chars with 1 or more UC letters, LC letters, digits & special chars) /// </summary> Strong = 4, /// <summary> /// At least 8 characters, all strong conditions met (>= 8 chars with 1 or more UC letters, LC letters, digits & special chars) /// </summary> VeryStrong = 5 } public static class PasswordCheck { /// <summary> /// Generic method to retrieve password strength: use this for general purpose scenarios, /// i.e. when you don't have a strict policy to follow. /// </summary> /// <param name="password"></param> /// <returns></returns> public static PasswordStrength GetPasswordStrength(string password) { int score = 0; if (String.IsNullOrEmpty(password) || String.IsNullOrEmpty(password.Trim())) return PasswordStrength.Blank; if (!HasMinimumLength(password, 5)) return PasswordStrength.VeryWeak; if (HasMinimumLength(password, 8)) score++; if (HasUpperCaseLetter(password) && HasLowerCaseLetter(password)) score++; if (HasDigit(password)) score++; if (HasSpecialChar(password)) score++; return (PasswordStrength)score; } /// <summary> /// Sample password policy implementation: /// - minimum 8 characters /// - at lease one UC letter /// - at least one LC letter /// - at least one non-letter char (digit OR special char) /// </summary> /// <returns></returns> public static bool IsStrongPassword(string password) { return HasMinimumLength(password, 8) && HasUpperCaseLetter(password) && HasLowerCaseLetter(password) && (HasDigit(password) || HasSpecialChar(password)); } /// <summary> /// Sample password policy implementation following the Microsoft.AspNetCore.Identity.PasswordOptions standard. /// </summary> public static bool IsValidPassword(string password, PasswordOptions opts) { return IsValidPassword( password, opts.RequiredLength, opts.RequiredUniqueChars, opts.RequireNonAlphanumeric, opts.RequireLowercase, opts.RequireUppercase, opts.RequireDigit); } /// <summary> /// Sample password policy implementation following the Microsoft.AspNetCore.Identity.PasswordOptions standard. /// </summary> public static bool IsValidPassword( string password, int requiredLength, int requiredUniqueChars, bool requireNonAlphanumeric, bool requireLowercase, bool requireUppercase, bool requireDigit) { if (!HasMinimumLength(password, requiredLength)) return false; if (!HasMinimumUniqueChars(password, requiredUniqueChars)) return false; if (requireNonAlphanumeric && !HasSpecialChar(password)) return false; if (requireLowercase && !HasLowerCaseLetter(password)) return false; if (requireUppercase && !HasUpperCaseLetter(password)) return false; if (requireDigit && !HasDigit(password)) return false; return true; } #region Helper Methods public static bool HasMinimumLength(string password, int minLength) { return password.Length >= minLength; } public static bool HasMinimumUniqueChars(string password, int minUniqueChars) { return password.Distinct().Count() >= minUniqueChars; } /// <summary> /// Returns TRUE if the password has at least one digit /// </summary> public static bool HasDigit(string password) { return password.Any(c => char.IsDigit(c)); } /// <summary> /// Returns TRUE if the password has at least one special character /// </summary> public static bool HasSpecialChar(string password) { // return password.Any(c => char.IsPunctuation(c)) || password.Any(c => char.IsSeparator(c)) || password.Any(c => char.IsSymbol(c)); return password.IndexOfAny("!@#$%^&*?_~-£().,".ToCharArray()) != -1; } /// <summary> /// Returns TRUE if the password has at least one uppercase letter /// </summary> public static bool HasUpperCaseLetter(string password) { return password.Any(c => char.IsUpper(c)); } /// <summary> /// Returns TRUE if the password has at least one lowercase letter /// </summary> public static bool HasLowerCaseLetter(string password) { return password.Any(c => char.IsLower(c)); } #endregion } } |
Tra i vari metodi presentati nella classe ce n'è uno - quello che si aspetta un oggetto PasswordOptions come parametro - che richiede un riferimento al namespace Microsoft.AspNetCore.Identity: quel metodo è estremamente utile quando si utilizza l'ASP.NET Core Identity Framework per controllare la validità della password rispetto alle impostazioni definite in fase di startup in alcuni casi specifici, come ad esempio quando si deve controllare lato server-side la correttezza di una password inserita dai nuovi utenti in fase di registrazione. Se il nostro progetto non utilizza l'Identity Framework possiamo eliminare o commentare quel metodo e utilizzare l'overload generico che viene richiamato internamente.