reworked genpwd / introduce pwdscore

This commit is contained in:
fatalerrors
2026-04-01 17:52:09 +02:00
parent d72fa1a712
commit 0737d0c647
2 changed files with 278 additions and 72 deletions

View File

@@ -63,6 +63,7 @@ help()
printf "ppn\t\tDisplay process matching the exact process name given in parameter\n" printf "ppn\t\tDisplay process matching the exact process name given in parameter\n"
printf "ppu\t\tDisplay processes owned by the given user\n" printf "ppu\t\tDisplay processes owned by the given user\n"
printf "profile_upgrade\tUpgrade profile to the latest version\n" printf "profile_upgrade\tUpgrade profile to the latest version\n"
printf "pwdscore\tCalculate password strength score\n"
printf "rain\t\tLet the rain fall\n" printf "rain\t\tLet the rain fall\n"
printf "rmhost\t\tRemove host (IP and/or DNS name) from current known_hosts\n" printf "rmhost\t\tRemove host (IP and/or DNS name) from current known_hosts\n"
printf "rmspc\t\tRemove spaces from file and directory names\n" printf "rmspc\t\tRemove spaces from file and directory names\n"

View File

@@ -53,21 +53,22 @@
genpwd() genpwd()
{ {
local length=16 local length=16
local occurs=2 # Bug, if set to 1, seems to be ignored local occurs=2
local symb=1 maj=1 min=1 numb=1 local symb=1 maj=1 min=1 numb=1
local nbpwd=1 local nbpwd=1
local extcar local extcar=""
local PARSED local PARSED
PARSED=$(getopt -o hsnu l e:L:o: --long \ PARSED=$(getopt -o hsnule:L:o: --long \
help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences: -n 'genpwd' -- "$@") help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences:,occurrences: \
-n 'genpwd' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED" eval set -- "$PARSED"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
printf "genpwd: Generate secure random password(s).\n\n" printf "genpwd: Generate random password(s).\n\n"
printf "Usage: genpwd [options] [nb_passwd]\n\n" printf "Usage: genpwd [options] [nb_passwd]\n\n"
printf "Options:\n" printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n" printf "\t-h, --help\t\tDisplay this help screen\n"
@@ -75,9 +76,9 @@ local PARSED
printf "\t-n, --nonumbers\t\tExclude numbers\n" printf "\t-n, --nonumbers\t\tExclude numbers\n"
printf "\t-u, --noup\t\tExclude uppercase letters\n" printf "\t-u, --noup\t\tExclude uppercase letters\n"
printf "\t-l, --nolow\t\tExclude lowercase letters\n" printf "\t-l, --nolow\t\tExclude lowercase letters\n"
printf "\t-e, --extracars <c>\tAdd characters to list\n" printf "\t-e, --extracars <c>\tAdd characters to the pool\n"
printf "\t-L, --length <n>\tSet password length (default: 16)\n" printf "\t-L, --length <n>\tSet password length (default: 16)\n"
printf "\t-o, --occurences <n>\tMax occurences per character (default: 2)\n" printf "\t-o, --occurences <n>\tMax occurrences per character (default: 2)\n"
return 0 return 0
;; ;;
-s|--nosymbols) -s|--nosymbols)
@@ -102,102 +103,306 @@ local PARSED
;; ;;
-L|--length) -L|--length)
length="$2" length="$2"
if ! [[ $length =~ ^[0-9]+$ ]]; then if ! [[ $length =~ ^[1-9][0-9]*$ ]]; then
disp E "The --length parameter requires a number." disp E "The --length parameter requires a positive integer."
return 1 return 1
fi fi
shift 2 shift 2
;; ;;
-o|--occurences) -o|--occurences|--occurrences)
occurs="$2" occurs="$2"
if ! [[ $occurs =~ ^[1-9]+$ ]]; then if ! [[ $occurs =~ ^[1-9][0-9]*$ ]]; then
disp E "The --occurs parameter requires a number from 1 to 9." disp E "The --occurences parameter requires a positive integer."
return 1 return 1
fi fi
shift 2 shift 2
;; ;;
--) --)
shift; break shift
break
;; ;;
*) *)
break disp E "Invalid options, use \"genpwd --help\" to display usage."
return 1
;; ;;
esac esac
done done
if [[ -n "$1" ]]; then if [[ $# -gt 1 ]]; then
disp E "Too many positional arguments. Use only [nb_passwd]."
return 1
fi
if [[ $# -eq 1 ]]; then
nbpwd="$1" nbpwd="$1"
if ! [[ $nbpwd =~ ^[0-9]+$ ]]; then if ! [[ $nbpwd =~ ^[1-9][0-9]*$ ]]; then
disp E "The number of password to generate must be a number." disp E "The number of passwords to generate must be a positive integer."
return 1 return 1
fi fi
fi fi
# Function selecting a random caracter from the list in parameter local carset=""
pickcar() { local unique_carset=""
# When a character is picked we check if it's not appearing already twice local ch=""
# elsewhere, we choose an other char, to compensate weak bash randomizer local i=0
while [[ -z $char ]]; do local n=0
local char="${1:RANDOM%${#1}:1} $RANDOM" local idx=0
if [[ $(awk -F"$char" '{print NF-1}' <<<"$picked") -gt $occurs ]]; then local attempts=0
unset char local count=0
fi local max_attempts=0
done local set=""
picked+="$char" local char=""
echo "$char" local -a required_sets=()
declare -A seen_chars=()
(( symb )) && {
required_sets+=('!.@#&%/^-_')
carset+='!.@#&%/^-_'
} }
(( numb )) && {
required_sets+=('0123456789')
carset+='0123456789'
}
(( maj )) && {
required_sets+=('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
(( min )) && {
required_sets+=('abcdefghijklmnopqrstuvwxyz')
carset+='abcdefghijklmnopqrstuvwxyz'
}
if [[ -n $extcar ]]; then
required_sets+=("$extcar")
carset+="$extcar"
fi
disp I "Generating $nbpwd passwords, please wait..." if [[ -z $carset ]]; then
disp E "No characters are available. Re-enable at least one character class."
return 1
fi
for (( i=0; i<${#carset}; i++ )); do
ch=${carset:i:1}
if [[ -z ${seen_chars["$ch"]+x} ]]; then
seen_chars["$ch"]=1
unique_carset+="$ch"
fi
done
unset seen_chars
carset="$unique_carset"
if (( ${#required_sets[@]} > length )); then
disp E "The selected character classes require a longer password."
return 1
fi
if (( length > ${#carset} * occurs )); then
disp E "The occurrence limit is too strict for the selected length."
disp E "Please allow more characters or increase --occurences."
return 1
fi
disp I "Generating $nbpwd password(s), please wait..."
for (( n=1; n<=nbpwd; n++ )); do for (( n=1; n<=nbpwd; n++ )); do
{ local -a password_chars=()
local carset='' # store final caracter set to use local -A char_count=()
local picked='' # store already used caracter max_attempts=$(( ${#carset} * (occurs + 1) + 32 ))
local rlength=0 # store already assigned length of caracters
# ?, *, $ and \ impossible to use to my knowledge as it would be interpreted for set in "${required_sets[@]}"; do
if [[ $symb == 1 ]]; then attempts=0
pickcar '!.@#&%/^-_' while :; do
carset+='!.@#&%/^-_' if (( attempts >= max_attempts )); then
((rlength++)) disp E "Unable to satisfy the occurrence limit with the current settings."
fi return 1
if [[ $numb == 1 ]]; then fi
pickcar '0123456789'
carset+='0123456789'
((rlength++))
fi
if [[ $maj == 1 ]]; then
pickcar 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
((rlength++))
fi
if [[ $min == 1 ]]; then
pickcar 'abcdefghijklmnopqrstuvwxyz'
carset+='abcdefghijklmnopqrstuvwxyz'
((rlength++))
fi
if [[ -n $extcar ]]; then
pickcar "$extcar"
carset+=$extcar
((rlength++))
fi
# Check if we have enough car to have something viable idx=$(( RANDOM % ${#set} ))
if [[ ${#carset} -lt $length ]]; then char=${set:idx:1}
disp E 'Not enought caracters are authorised for the password length.' count=${char_count["$char"]:-0}
disp E 'Please allow more caracter (preferably) or reduce password lentgh.'
return 1
fi
for i in $(seq 1 $(($length - $rlength))); do if (( count < occurs )); then
pickcar "$carset" char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done done
} | sort -R | awk '{printf "%s", $1}' done
unset picked carset rlength
echo while (( ${#password_chars[@]} < length )); do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#carset} ))
char=${carset:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
for (( i=${#password_chars[@]} - 1; i>0; i-- )); do
idx=$(( RANDOM % (i + 1) ))
char=${password_chars[i]}
password_chars[i]=${password_chars[idx]}
password_chars[idx]=$char
done
printf '%s' "${password_chars[@]}"
printf '\n'
done done
} }
export -f genpwd export -f genpwd
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# pwdscore : score a password quality from 1 to 100
# Usage: pwdscore [options] <password>
pwdscore()
{
local verbose=0
local PARSED
PARSED=$(getopt -o hv --long help,verbose -n 'pwdscore' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "pwdscore: Score a password from 1 to 100.\n\n"
printf "Usage: pwdscore [options] <password>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-v, --verbose\t\tShow details about the computed score\n"
return 0
;;
-v|--verbose)
verbose=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"pwdscore --help\" to display usage."
return 1
;;
esac
done
[[ $# -ne 1 ]] && {
disp E "Please provide exactly one password to score."
return 1
}
local password="$1"
local lower=${password,,}
local length=${#password}
local score=0
local rating="very weak"
local unique_count=0
local i=0 idx=0
local c1=0 c2=0 c3=0
local ch=""
local has_lower=0 has_upper=0 has_digit=0 has_symbol=0
local -A seen=()
if [[ -z $password ]]; then
printf '1\n'
return 0
fi
if (( length >= 20 )); then
score=40
elif (( length >= 16 )); then
score=34
elif (( length >= 12 )); then
score=28
elif (( length >= 8 )); then
score=18
else
score=$(( length * 2 ))
fi
[[ $password =~ [a-z] ]] && { has_lower=1; ((score += 12)); }
[[ $password =~ [A-Z] ]] && { has_upper=1; ((score += 12)); }
[[ $password =~ [0-9] ]] && { has_digit=1; ((score += 12)); }
[[ $password =~ [^[:alnum:]] ]] && { has_symbol=1; ((score += 14)); }
for (( i=0; i<length; i++ )); do
ch=${password:i:1}
if [[ -z ${seen["$ch"]+x} ]]; then
seen["$ch"]=1
((unique_count++))
fi
done
(( score += (unique_count * 10) / length ))
if [[ $lower =~ (password|admin|root|qwerty|azerty|welcome|letmein|secret|changeme) ]]; then
((score -= 25))
fi
if [[ $lower =~ (1234|abcd|qwer|0000|1111|aaaa) ]]; then
((score -= 15))
fi
[[ $password =~ (.)\1\1 ]] && ((score -= 10))
(( length < 8 )) && ((score -= 10))
(( unique_count * 2 < length )) && ((score -= 10))
for (( idx=0; idx<length-2; idx++ )); do
printf -v c1 '%d' "'${lower:idx:1}"
printf -v c2 '%d' "'${lower:idx+1:1}"
printf -v c3 '%d' "'${lower:idx+2:1}"
if (( (c2 == c1 + 1 && c3 == c2 + 1) || \
(c2 == c1 - 1 && c3 == c2 - 1) )); then
((score -= 10))
break
fi
done
(( score < 1 )) && score=1
(( score > 100 )) && score=100
if (( score >= 90 )); then
rating='excellent'
elif (( score >= 75 )); then
rating='strong'
elif (( score >= 60 )); then
rating='good'
elif (( score >= 40 )); then
rating='fair'
elif (( score >= 20 )); then
rating='weak'
fi
if (( verbose )); then
printf 'Score: %d/100\n' "$score"
printf 'Rating: %s\n' "$rating"
printf 'Length: %d\n' "$length"
printf 'Lowercase: %s\n' "$([[ $has_lower -eq 1 ]] && echo yes || echo no)"
printf 'Uppercase: %s\n' "$([[ $has_upper -eq 1 ]] && echo yes || echo no)"
printf 'Digits: %s\n' "$([[ $has_digit -eq 1 ]] && echo yes || echo no)"
printf 'Symbols: %s\n' "$([[ $has_symbol -eq 1 ]] && echo yes || echo no)"
printf 'Unique chars: %d\n' "$unique_count"
else
printf '%d\n' "$score"
fi
}
export -f pwdscore
# ------------------------------------------------------------------------------
# EOF # EOF