409 lines
13 KiB
Bash
409 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|
# ------------------------------------------------------------------------------
|
|
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
|
# Protected by the BSD3 license. Please read bellow for details.
|
|
#
|
|
# * Redistribution and use in source and binary forms,
|
|
# * with or without modification, are permitted provided
|
|
# * that the following conditions are met:
|
|
# *
|
|
# * Redistributions of source code must retain the above
|
|
# * copyright notice, this list of conditions and the
|
|
# * following disclaimer.
|
|
# *
|
|
# * Redistributions in binary form must reproduce the above
|
|
# * copyright notice, this list of conditions and the following
|
|
# * disclaimer in the documentation and/or other materials
|
|
# * provided with the distribution.
|
|
# *
|
|
# * Neither the name of the copyright holder nor the names
|
|
# * of any other contributors may be used to endorse or
|
|
# * promote products derived from this software without
|
|
# * specific prior written permission.
|
|
# *
|
|
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
# * OF SUCH DAMAGE.
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# genpwd : generate a password with different criteria
|
|
# Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]
|
|
# Options:
|
|
# -h, --help Display that help screen
|
|
# -s, --nosymbols Exclude symbols
|
|
# -n, --nonumbers Exclude numbers
|
|
# -u, --noup Exclude uppercase letters
|
|
# -l, --nolow Exclude lowercase letters
|
|
# -e=<c>, --extracars=<c>
|
|
# Add the given caracters to the possible caracter list
|
|
# -L=<n>, --length=<n>
|
|
# Set length of the password (default is 16)
|
|
# -o=<n>, --occurences=<n>
|
|
# Set the maximum occurences of a same caracter (default is 2)
|
|
# The function is very slow on Windows
|
|
genpwd()
|
|
{
|
|
local length=16
|
|
local occurs=2
|
|
local symb=1 maj=1 min=1 numb=1
|
|
local nbpwd=1
|
|
local extcar=""
|
|
|
|
local PARSED
|
|
PARSED=$(getopt -o hsnule:L:o: --long \
|
|
help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences:,occurrences: \
|
|
-n 'genpwd' -- "$@")
|
|
if [[ $? -ne 0 ]]; then return 1; fi
|
|
eval set -- "$PARSED"
|
|
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "genpwd: Generate random password(s).\n\n"
|
|
printf "Usage: genpwd [options] [nb_passwd]\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
printf "\t-s, --nosymbols\t\tExclude symbols\n"
|
|
printf "\t-n, --nonumbers\t\tExclude numbers\n"
|
|
printf "\t-u, --noup\t\tExclude uppercase letters\n"
|
|
printf "\t-l, --nolow\t\tExclude lowercase letters\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-o, --occurences <n>\tMax occurrences per character (default: 2)\n"
|
|
return 0
|
|
;;
|
|
-s|--nosymbols)
|
|
symb=0
|
|
shift
|
|
;;
|
|
-n|--nonumbers)
|
|
numb=0
|
|
shift
|
|
;;
|
|
-u|--noup)
|
|
maj=0
|
|
shift
|
|
;;
|
|
-l|--nolow)
|
|
min=0
|
|
shift
|
|
;;
|
|
-e|--extracars)
|
|
extcar="$2"
|
|
shift 2
|
|
;;
|
|
-L|--length)
|
|
length="$2"
|
|
if ! [[ $length =~ ^[1-9][0-9]*$ ]]; then
|
|
disp E "The --length parameter requires a positive integer."
|
|
return 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
-o|--occurences|--occurrences)
|
|
occurs="$2"
|
|
if ! [[ $occurs =~ ^[1-9][0-9]*$ ]]; then
|
|
disp E "The --occurences parameter requires a positive integer."
|
|
return 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"genpwd --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ $# -gt 1 ]]; then
|
|
disp E "Too many positional arguments. Use only [nb_passwd]."
|
|
return 1
|
|
fi
|
|
|
|
if [[ $# -eq 1 ]]; then
|
|
nbpwd="$1"
|
|
if ! [[ $nbpwd =~ ^[1-9][0-9]*$ ]]; then
|
|
disp E "The number of passwords to generate must be a positive integer."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local carset=""
|
|
local unique_carset=""
|
|
local ch=""
|
|
local i=0
|
|
local n=0
|
|
local idx=0
|
|
local attempts=0
|
|
local count=0
|
|
local max_attempts=0
|
|
local set=""
|
|
local 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
|
|
|
|
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
|
|
local -a password_chars=()
|
|
local -A char_count=()
|
|
max_attempts=$(( ${#carset} * (occurs + 1) + 32 ))
|
|
|
|
for set in "${required_sets[@]}"; 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 % ${#set} ))
|
|
char=${set:idx:1}
|
|
count=${char_count["$char"]:-0}
|
|
|
|
if (( count < occurs )); then
|
|
char_count["$char"]=$(( count + 1 ))
|
|
password_chars+=("$char")
|
|
break
|
|
fi
|
|
|
|
((attempts++))
|
|
done
|
|
done
|
|
|
|
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
|
|
}
|
|
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
|