๐Ÿ“Œ Linux Password Generator

6/1/2015

Secure Linux Password Generator (password_generator.sh)


๐ŸŒŸ Key Features

  • Cryptographically Secure: Draws raw entropy directly from Linux /dev/urandom.
  • Flexible Complexity Modes:
    • plain: Alphabetic characters only (a-z, A-Z).
    • numeric: Digits only (0-9).
    • alpha-numeric: Alphabetic characters + Digits (a-z, A-Z, 0-9).
    • alpha-special-numeric: Letters + Digits + Special Symbols (!@#$%^&*()_+-=[]{}|;:,.<>?).
  • Custom Character Counts: Allows exact specification of special characters and numeric digit counts.
  • Uniform Character Shuffling: Employs shuf or a Fisher-Yates shuffle to prevent predictable character groupings.
  • Entropy Calculation: Computes and reports password strength in bits of entropy ($H = L \times \log_2(N)$).
  • Dual Operation Modes: Interactive step-by-step wizard or non-interactive CLI flags.

๐Ÿ“œ Script Code (password_generator.sh)

#!/usr/bin/env bash
# ==============================================================================
# Script: password_generator.sh
# Purpose: Interactive & CLI Password Generator for Linux Hosts
# Features: Cryptographically secure (/dev/urandom), custom length, special
#           character counts, complexity presets, Fisher-Yates/shuf randomizer,
#           and password entropy calculation.
# ==============================================================================

set -euo pipefail

# Character Set Definitions
ALPHA_LOWER="abcdefghijklmnopqrstuvwxyz"
ALPHA_UPPER="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
NUMERIC="0123456789"
SPECIAL='!@#$%^&*()_+-=[]{}|;:,.<>?'

# Default Values
LENGTH=16
TYPE="alpha-special-numeric"
USE_SPECIAL="y"
SPECIAL_COUNT=4
NUMERIC_COUNT=4
COUNT=1
INTERACTIVE=true

# Helper Functions
print_banner() {
    echo "======================================================================="
    echo "            SECURE LINUX PASSWORD GENERATOR UTILITY                   "
    echo "======================================================================="
}

usage() {
    print_banner
    cat << EOF
Usage: $0 [OPTIONS]

Non-Interactive Flag Mode:
  -l, --length <NUM>          Total length of the password (default: 16)
  -t, --type <TYPE>           Password complexity type:
                              - plain                  (Letters only)
                              - numeric                (Digits only)
                              - alpha-numeric          (Letters + Digits)
                              - alpha-special-numeric  (Letters + Digits + Symbols) [default]
  -s, --special-count <NUM>   Exact number of special characters to include
  -n, --numeric-count <NUM>   Exact number of numeric characters to include
  -c, --count <NUM>           Number of passwords to generate (default: 1)
  -i, --interactive           Force interactive prompt mode
  -h, --help                  Show this help menu

Examples:
  Interactive Mode:
    $0

  Non-Interactive Examples:
    $0 -l 20 -t alpha-special-numeric -s 5 -n 4
    $0 -l 12 -t alpha-numeric -c 5
    $0 --length 24 --special-count 6

EOF
    exit 0
}

# Parse Command Line Arguments
parse_args() {
    if [ "$#" -gt 0 ]; then
        INTERACTIVE=false
    fi

    while [ "$#" -gt 0 ]; do
        case "$1" in
            -l|--length)
                LENGTH="$2"; shift 2 ;;
            -t|--type)
                TYPE="$2"; shift 2 ;;
            -s|--special-count)
                SPECIAL_COUNT="$2"; USE_SPECIAL="y"; shift 2 ;;
            -n|--numeric-count)
                NUMERIC_COUNT="$2"; shift 2 ;;
            -c|--count)
                COUNT="$2"; shift 2 ;;
            -i|--interactive)
                INTERACTIVE=true; shift 1 ;;
            -h|--help)
                usage ;;
            *)
                echo "[-] Unknown option: $1" >&2
                usage ;;
        esac
    done
}

# Interactive Prompt Interface
prompt_user() {
    print_banner
    echo "[+] Running in Interactive Mode..."
    echo ""

    # 1. Total Password Length
    read -rp "1. Enter total password length [default: 16]: " input_len
    if [[ -n "${input_len}" ]]; then
        if [[ "${input_len}" =~ ^[0-9]+$ ]] && [ "${input_len}" -ge 4 ]; then
            LENGTH="${input_len}"
        else
            echo "[!] Invalid length. Defaulting to 16 characters (minimum is 4)."
            LENGTH=16
        fi
    fi

    # 2. Select Password Type / Complexity
    echo ""
    echo "2. Select Password Type / Complexity:"
    echo "   1) Plain Alpha             (Letters only: a-z, A-Z)"
    echo "   2) Alpha-Numeric           (Letters + Digits: a-z, A-Z, 0-9)"
    echo "   3) Alpha-Special-Numeric   (Letters + Digits + Special Symbols) [Default]"
    read -rp "   Choice [1-3, default: 3]: " type_choice

    case "${type_choice}" in
        1)
            TYPE="plain"
            USE_SPECIAL="n"
            SPECIAL_COUNT=0
            NUMERIC_COUNT=0
            ;;
        2)
            TYPE="alpha-numeric"
            USE_SPECIAL="n"
            SPECIAL_COUNT=0
            ;;
        3|"")
            TYPE="alpha-special-numeric"
            USE_SPECIAL="y"
            ;;
        *)
            echo "[!] Invalid choice. Defaulting to Alpha-Special-Numeric."
            TYPE="alpha-special-numeric"
            USE_SPECIAL="y"
            ;;
    esac

    # 3. Special Character Count Prompt (if applicable)
    if [ "${TYPE}" == "alpha-special-numeric" ]; then
        echo ""
        read -rp "3. Include special characters? (y/n) [default: y]: " input_spec_yn
        input_spec_yn=$(echo "${input_spec_yn}" | tr '[:upper:]' '[:lower:]')

        if [[ "${input_spec_yn}" == "n" ]]; then
            USE_SPECIAL="n"
            SPECIAL_COUNT=0
            TYPE="alpha-numeric"
        else
            USE_SPECIAL="y"
            read -rp "   How many special characters? [default: 4, max: $((LENGTH - 2))]: " input_spec_cnt
            if [[ -n "${input_spec_cnt}" ]]; then
                if [[ "${input_spec_cnt}" =~ ^[0-9]+$ ]] && [ "${input_spec_cnt}" -le "$((LENGTH - 2))" ]; then
                    SPECIAL_COUNT="${input_spec_cnt}"
                else
                    echo "[!] Invalid count. Defaulting to 4 special characters."
                    SPECIAL_COUNT=4
                fi
            fi
        fi
    fi

    # 4. Numeric Count Prompt (if applicable)
    if [[ "${TYPE}" == "alpha-numeric" || "${TYPE}" == "alpha-special-numeric" ]]; then
        MAX_NUM=$((LENGTH - SPECIAL_COUNT - 1))
        [ "${MAX_NUM}" -lt 1 ] && MAX_NUM=1
        read -rp "4. How many numeric digits? [default: 4, max: ${MAX_NUM}]: " input_num_cnt
        if [[ -n "${input_num_cnt}" ]]; then
            if [[ "${input_num_cnt}" =~ ^[0-9]+$ ]] && [ "${input_num_cnt}" -le "${MAX_NUM}" ]; then
                NUMERIC_COUNT="${input_num_cnt}"
            else
                echo "[!] Invalid count. Defaulting to 4 numeric digits."
                NUMERIC_COUNT=4
            fi
        fi
    fi

    # 5. Quantity of passwords
    read -rp "5. How many passwords to generate? [default: 1]: " input_count
    if [[ -n "${input_count}" && "${input_count}" =~ ^[0-9]+$ ]] && [ "${input_count}" -ge 1 ]; then
        COUNT="${input_count}"
    fi
}

# Generate Secure Random String from character set
get_random_chars() {
    local charset="$1"
    local count="$2"
    if [ "${count}" -le 0 ]; then
        echo ""
        return
    fi
    LC_ALL=C tr -dc "${charset}" < /dev/urandom | head -c "${count}" || true
}

# Shuffle string characters safely using shuf or Fisher-Yates fallback
shuffle_string() {
    local input="$1"
    if command -v shuf &>/dev/null; then
        echo "${input}" | grep -o . | shuf | tr -d '\n'
    else
        local len=${#input}
        local i j tmp
        declare -a arr
        for (( i=0; i<len; i++ )); do
            arr[i]="${input:$i:1}"
        done
        for (( i=len-1; i>0; i-- )); do
            j=$(( RANDOM % (i + 1) ))
            tmp="${arr[i]}"
            arr[i]="${arr[j]}"
            arr[j]="${tmp}"
        done
        ( IFS=""; echo "${arr[*]}" )
    fi
}

# Calculate Entropy in Bits
calculate_entropy() {
    local len="$1"
    local pool_size="$2"
    if command -v python3 &>/dev/null; then
        python3 -c "import math; print(round(${len} * math.log2(${pool_size}), 1))" 2>/dev/null || echo "N/A"
    else
        echo "N/A"
    fi
}

# Main Password Generation Engine
generate_password() {
    local raw_pass=""
    local charset_pool=""
    local pool_size=0

    case "${TYPE}" in
        plain)
            SPECIAL_COUNT=0
            NUMERIC_COUNT=0
            USE_SPECIAL="n"
            charset_pool="${ALPHA_LOWER}${ALPHA_UPPER}"
            pool_size=52
            raw_pass=$(get_random_chars "${charset_pool}" "${LENGTH}")
            ;;
        numeric)
            SPECIAL_COUNT=0
            NUMERIC_COUNT="${LENGTH}"
            USE_SPECIAL="n"
            charset_pool="${NUMERIC}"
            pool_size=10
            raw_pass=$(get_random_chars "${charset_pool}" "${LENGTH}")
            ;;
        alpha-numeric)
            SPECIAL_COUNT=0
            USE_SPECIAL="n"
            charset_pool="${ALPHA_LOWER}${ALPHA_UPPER}${NUMERIC}"
            pool_size=62
            local alpha_count=$((LENGTH - NUMERIC_COUNT))
            [ "${alpha_count}" -lt 1 ] && alpha_count=1
            local rand_alpha=$(get_random_chars "${ALPHA_LOWER}${ALPHA_UPPER}" "${alpha_count}")
            local rand_num=$(get_random_chars "${NUMERIC}" "${NUMERIC_COUNT}")
            raw_pass="${rand_alpha}${rand_num}"
            ;;
        alpha-special-numeric)
            USE_SPECIAL="y"
            charset_pool="${ALPHA_LOWER}${ALPHA_UPPER}${NUMERIC}${SPECIAL}"
            pool_size=94
            if [ "$((SPECIAL_COUNT + NUMERIC_COUNT))" -ge "${LENGTH}" ]; then
                SPECIAL_COUNT=$((LENGTH / 4))
                NUMERIC_COUNT=$((LENGTH / 4))
            fi
            local alpha_count=$((LENGTH - SPECIAL_COUNT - NUMERIC_COUNT))
            [ "${alpha_count}" -lt 1 ] && alpha_count=1
            local rand_alpha=$(get_random_chars "${ALPHA_LOWER}${ALPHA_UPPER}" "${alpha_count}")
            local rand_num=$(get_random_chars "${NUMERIC}" "${NUMERIC_COUNT}")
            local rand_spec=$(get_random_chars "${SPECIAL}" "${SPECIAL_COUNT}")
            raw_pass="${rand_alpha}${rand_num}${rand_spec}"
            ;;
        *)
            echo "[-] Error: Unsupported password type '${TYPE}'." >&2
            exit 1
            ;;
    esac

    local final_password
    final_password=$(shuffle_string "${raw_pass}")
    local entropy
    entropy=$(calculate_entropy "${LENGTH}" "${pool_size}")

    echo "${final_password}|${entropy}"
}

# Main Execution Flow
main() {
    parse_args "$@"

    if [ "${INTERACTIVE}" = true ]; then
        prompt_user
    fi

    # Pre-sanitize settings for header display
    case "${TYPE}" in
        plain)
            SPECIAL_COUNT=0
            NUMERIC_COUNT=0
            USE_SPECIAL="n"
            ;;
        numeric)
            SPECIAL_COUNT=0
            NUMERIC_COUNT="${LENGTH}"
            USE_SPECIAL="n"
            ;;
        alpha-numeric)
            SPECIAL_COUNT=0
            USE_SPECIAL="n"
            ;;
        alpha-special-numeric)
            USE_SPECIAL="y"
            ;;
    esac

    echo ""
    echo "======================================================================="
    echo " GENERATED PASSWORD(S)"
    echo "======================================================================="
    echo " Configuration:"
    echo "   - Total Length         : ${LENGTH}"
    echo "   - Password Type        : ${TYPE}"
    echo "   - Special Characters   : ${SPECIAL_COUNT} (Enabled: ${USE_SPECIAL})"
    echo "   - Numeric Digits       : ${NUMERIC_COUNT}"
    echo "-----------------------------------------------------------------------"

    for (( i=1; i<=COUNT; i++ )); do
        res=$(generate_password)
        pass=$(echo "${res}" | cut -d'|' -f1)
        ent=$(echo "${res}" | cut -d'|' -f2)

        if [ "${COUNT}" -gt 1 ]; then
            echo -e " [Password #${i}] : ${pass}  (Entropy: ~${ent} bits)"
        else
            echo -e " Generated Password : \033[1;32m${pass}\033[0m"
            echo -e " Estimated Entropy  : ~${ent} bits"
        fi
    done
    echo "======================================================================="
}

main "$@"

๐Ÿš€ Installation & Execution

  1. Save the code into a file named password_generator.sh.
  2. Grant executable permissions:
    chmod +x password_generator.sh
  3. Run interactively:
    ./password_generator.sh

๐Ÿ’ป CLI Flags Reference Table

Flag Long Option Description Default
-l --length Total length of the password 16
-t --type Complexity mode (plain, numeric, alpha-numeric, alpha-special-numeric) alpha-special-numeric
-s --special-count Exact count of special symbols 4
-n --numeric-count Exact count of numeric digits 4
-c --count Number of passwords to generate 1
-i --interactive Force interactive prompt mode true (if no args)
-h --help Display usage menu โ€”

๐Ÿงช Usage Examples

1. Interactive Walkthrough Example

$ ./password_generator.sh
=======================================================================
            SECURE LINUX PASSWORD GENERATOR UTILITY                   
=======================================================================
[+] Running in Interactive Mode...

1. Enter total password length [default: 16]: 20

2. Select Password Type / Complexity:
   1) Plain Alpha             (Letters only: a-z, A-Z)
   2) Alpha-Numeric           (Letters + Digits: a-z, A-Z, 0-9)
   3) Alpha-Special-Numeric   (Letters + Digits + Special Symbols) [Default]
   Choice [1-3, default: 3]: 3

3. Include special characters? (y/n) [default: y]: y
   How many special characters? [default: 4, max: 18]: 5

4. How many numeric digits? [default: 4, max: 14]: 4

5. How many passwords to generate? [default: 1]: 1

=======================================================================
 GENERATED PASSWORD(S)
=======================================================================
 Configuration:
   - Total Length         : 20
   - Password Type        : alpha-special-numeric
   - Special Characters   : 5 (Enabled: y)
   - Numeric Digits       : 4
-----------------------------------------------------------------------
 Generated Password : F0Pb?Bj(fO,]<m8c3Aa3
 Estimated Entropy  : ~131.1 bits
=======================================================================

2. Non-Interactive CLI Automation Examples

  • 20-character password with 5 symbols & 4 numbers:

    ./password_generator.sh -l 20 -t alpha-special-numeric -s 5 -n 4
  • Batch generate 5 alpha-numeric passwords of length 16:

    ./password_generator.sh -l 16 -t alpha-numeric -c 5
  • Generate a 12-character plain letters-only password:

    ./password_generator.sh -l 12 -t plain

๐Ÿ”’ Security & Entropy Metrics

The password strength is rated in bits of entropy calculated via:

$$H = L \times \log_2(N)$$

Where:

  • $L$ = Total Password Length
  • $N$ = Character Set Pool Size
    • Plain Alpha ($N=52$): ~5.7 bits / char
    • Alpha-Numeric ($N=62$): ~5.95 bits / char
    • Alpha-Special-Numeric ($N=94$): ~6.55 bits / char

[!NOTE] A password with > 80 bits of entropy is considered resilient against brute-force attacks, while > 128 bits is considered quantum-safe for general authentication.