This is counterintuitive, but such (popular) password requirements may weaken your password, like:
A password is said to be strong if it satisfies the following criteria: It contains at least 8 characters. It contains at least one digit. It contains at least one lower case alphabet. It contains at least one upper case alphabet. It contains at least one special character which includes !@#$%^&*()-+
( src )
Password length: Minimum of 8 Characters Password complexity: Passwords must contain At least one upper case A-Z At least one lowercase letter (a-z) At least one digit 0-9 At least one punctuation character, such as, !@#$%^&*()-_+|~-=\`{}[]:";'[]?,./
( src )
Let's see. This code enumerates all passwords with a-z0-9 chars:
#!/usr/bin/env python3 alphabet="qwertyuiopasdfghjklzxcvbnm0123456789" for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: print (a+b+c+d)
And this is the same, but a digit must be present:
#!/usr/bin/env python3 alphabet="qwertyuiopasdfghjklzxcvbnm0123456789" def is_digit(c): return c in "0123456789" for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: if is_digit(a) or is_digit(b) or is_digit(c) or is_digit(d): print (a+b+c+d)
There are slightly more passwords in first case:
% ./v1_1.py | wc -l 1679616 % ./v1_2.py | wc -l 1222640
Now let's say, the alphabet consisting of a-z0-9 and punctuation symbols:
#!/usr/bin/env python3 punct=" !\"#$%&'()*+,-./:;<=>?" # ^ note the escape char before double quotes alphabet="qwertyuiopasdfghjklzxcvbnm0123456789"+punct for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: print (a+b+c+d)
The second version (a digit must be present in password):
#!/usr/bin/env python3 punct=" !\"#$%&'()*+,-./:;<=>?" # ^ note the escape char before double quotes alphabet="qwertyuiopasdfghjklzxcvbnm0123456789"+punct def is_digit(c): return c in "0123456789" for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: if is_digit(a) or is_digit(b) or is_digit(c) or is_digit(d): print (a+b+c+d)
The second version almost halved the results of the first version:
% ./v2_1.py | wc -l 11316496 % ./v2_2.py | wc -l 6008080
Now the first version again:
#!/usr/bin/env python3 punct=" !\"#$%&'()*+,-./:;<=>?" # ^ note the escape char before double quotes alphabet="qwertyuiopasdfghjklzxcvbnm"+"0123456789"+punct for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: print (a+b+c+d)
The second version, a digit OR punctuation character must be present in password:
#!/usr/bin/env python3 punct=" !\"#$%&'()*+,-./:;<=>?" # ^ note the escape char before double quotes alphabet="qwertyuiopasdfghjklzxcvbnm"+"0123456789"+punct def is_special(c): return c in "0123456789"+punct for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: if is_special(a) or is_special(b) or is_special(c) or is_special(d): print (a+b+c+d)
Third version: at least one punctuation character must be present AND and least one digit:
#!/usr/bin/env python3 punct=" !\"#$%&'()*+,-./:;<=>?" # ^ note the escape char before double quotes alphabet="qwertyuiopasdfghjklzxcvbnm"+"0123456789"+punct def is_digit(c): return c in "0123456789" def is_punct(c): return c in punct for a in alphabet: for b in alphabet: for c in alphabet: for d in alphabet: has_at_least_one_punct=is_punct(a) or is_punct(b) or is_punct(c) or is_punct(d) has_at_least_one_digit=is_digit(a) or is_digit(b) or is_digit(c) or is_digit(d) if has_at_least_one_punct and has_at_least_one_digit: print (a+b+c+d)
The third version halved the results of first and second:
% ./v3_1.py | wc -l 11316496 % ./v3_2.py | wc -l 10859520 % ./v3_3.py | wc -l 4785440
I simplified Python code for demonstration, but itertools module is to be used instead of nested loops, of course.
TL;DR: --- a randomly generated password (using your favorite password manager or a simple script) is stronger.
In plain English --- knowing password requirements, attacker have more information about password.
Be very careful when devising password requirements.
Now let's compare 4-character password of a-z lowercase character and 3-character a-z password with a random digit inserted somewhere between a-z characters.
Just password: 4-character a-z password: 26**4=456976. 8-character a-z password: 26**8=208827064576.
A digit can be inserted between characters, at the beginning of the password, or at the end, that is, (len+1)*10. 4-character password with 3 a-z characters and one digit somewhere: 26**3*(5*10)=878800 (~2x search space increase). 8-character password with 7 a-z characters and one digit somewhere: 26**7*(9*10)=722862915840 (~3.5x search space increase).
Yes, this is better.
But of course, most users, if coerced into using a digit, would add a random digit at the end of their password (like 'password3'), instead of inserting it somewhere in the middle (like 'pas3sword').
And what is better? Password of n+1 a-z characters? Or n a-z passwords plus one digit?
Keep in mind: adding a random a-z lowercase character at the end multiples search space by 26. But adding a random digit multiples it only by 10. A 8-character a-z password has larger search space than a 7-character a-z password with a random digit added, by factor of ~2.5x.
What does all these 'factors' mean in practice? It mean increase/decrease the time a cracker/hacker/attacker would need to try all passwords (brute-forcing).
All such problems/tasks can be found in many combinatorics textbooks.
Some time ago (before 24-Mar-2025) there was Disqus JS script for comments. I dropped it --- it was so motley, distracting, animated, with too much ads. I never liked it. Also, comments didn't appeared correctly (Disqus was buggy). Also, my blog is too chamberlike --- not many people write comments here. So I decided to switch to the model I once had at least in 2020 --- send me your comments by email to blog at yurichev dot com (don't forget to include URL to this blog post) and I'll copy&paste it here manually
Let's party like it's ~1993-1996, in this ultimate, radical and uncompromisingly primitive pre-web1.0-style blog and website.