#!/usr/bin/env bash # ------------------------------------------------------------------------------ # Copyright (c) 2013-2026 Geoffray Levasseur # 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=] [--length=] [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=, --extracars= # Add the given caracters to the possible caracter list # -L=, --length= # Set length of the password (default is 16) # -o=, --occurences= # 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 \tAdd characters to the pool\n" printf "\t-L, --length \tSet password length (default: 16)\n" printf "\t-o, --occurences \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] pwdscore() { local verbose=0 local read_stdin=0 local password="" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) printf "pwdscore: Score a password from 1 to 100.\n\n" printf "Usage: pwdscore [options] \n" printf " pwdscore [options] --stdin\n" printf " pwdscore [options] # prompt on terminal\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" printf "\t-i, --stdin\t\tRead the password from standard input\n\n" printf "Note:\n" printf " Passwords containing '!' should be quoted, or passed via --stdin.\n" return 0 ;; -v|--verbose) verbose=1 shift ;; -i|--stdin) read_stdin=1 shift ;; --) shift break ;; -*) disp E "Invalid option '$1'. Use \"pwdscore --help\" to display usage." return 1 ;; *) break ;; esac done if (( read_stdin )); then [[ $# -eq 0 ]] || { disp E "Do not pass a positional password when using --stdin." return 1 } IFS= read -r password || true elif [[ $# -eq 0 ]]; then if [[ -t 0 ]]; then read -r -s -p 'Password: ' password < /dev/tty || true printf '\n' > /dev/tty else IFS= read -r password || true fi else [[ $# -eq 1 ]] || { disp E "Please provide exactly one password to score." return 1 } password="$1" fi 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 pool_size=0 local entropy_bits="0.0" local entropy_score=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 if [[ $password =~ [a-z] ]]; then has_lower=1 pool_size=$(( pool_size + 26 )) score=$(( score + 12 )) fi if [[ $password =~ [A-Z] ]]; then has_upper=1 pool_size=$(( pool_size + 26 )) score=$(( score + 12 )) fi if [[ $password =~ [0-9] ]]; then has_digit=1 pool_size=$(( pool_size + 10 )) score=$(( score + 12 )) fi if [[ $password =~ [^[:alnum:]] ]]; then has_symbol=1 pool_size=$(( pool_size + 33 )) score=$(( score + 14 )) fi for (( i=0; i 1 )); then entropy_bits=$(awk -v len="$length" -v pool="$pool_size" \ 'BEGIN { printf "%.1f", len * (log(pool) / log(2)) }') entropy_score=$(awk -v bits="$entropy_bits" 'BEGIN { if (bits < 28) print -35; else if (bits < 36) print -25; else if (bits < 60) print -10; else if (bits < 80) print 0; else if (bits < 100) print 5; else print 10; }') score=$(( score + entropy_score )) fi if [[ $lower =~ (password|admin|root|qwerty|azerty|welcome|letmein|secret|changeme) ]]; then score=$(( score - 25 )) fi if [[ $lower =~ (1234|abcd|qwer|0000|1111|aaaa) ]]; then score=$(( score - 15 )) fi if [[ $password =~ (.)\1\1 ]]; then score=$(( score - 10 )) fi if (( length < 8 )); then score=$(( score - 10 )) fi if (( unique_count * 2 < length )); then score=$(( score - 10 )) fi for (( idx=0; idx 100 )); then score=100 fi 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" printf 'Entropy: ~%s bits\n' "$entropy_bits" printf 'Entropy modifier: %+d\n' "$entropy_score" else printf '%d\n' "$score" fi } export -f pwdscore # ------------------------------------------------------------------------------ # EOF