Skip to content

Instantly share code, notes, and snippets.

@dadatuputi
Last active April 23, 2026 15:43
Show Gist options
  • Select an option

  • Save dadatuputi/1f19e434514fe5278fea60890642dc39 to your computer and use it in GitHub Desktop.

Select an option

Save dadatuputi/1f19e434514fe5278fea60890642dc39 to your computer and use it in GitHub Desktop.
Bash Script (Linux, macOS) to generate new DS Login password
#!/bin/bash
#
# Generates DS Logon compliant passwords.
# Based on https://gist.github.com/dadatuputi/d2ead8ee219da5922bbcbf6b7325857f
set -e
LENGTH=128
LOWERCASE='abcdefghijklmnopqrstuvwxyz'
UPPERCASE='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
NUMBERS='0123456789'
# @ _ # / , ; ~ ` % & = ' : ! $ * + ( ) . { } | ? > < ^ [ ] - " \
SPECIAL=$'@_#/,;~`%&=\':!$*+().{}|?><^[]-"\\'
# See https://www.reddit.com/r/VeteransBenefits/comments/1avm2wf/satisfying_ds_logon_password_requirements/
NEW_PW_CHARS_DIFFERENT=8
show_help() {
cat <<'EOF'
Generates DS Logon compliant passwords
PARAMETERS
-l, --length <int>
Length of password (15-128 chars)
Default: 128
-h, --help
Shows this help message
BEHAVIOR
Always prompts for current password. If empty password entered,
generates completely new password. If current password provided,
ensures new password meets '8 different characters' requirement.
EXAMPLES
Generate password (will prompt for current):
./new-dslogon-password.sh --length 30
Show help:
./new-dslogon-password.sh --help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-l|--length)
LENGTH="$2"
shift 2
;;
-h|--help|-\?)
show_help
exit 0
;;
*)
echo "Unknown argument: $1" >&2
show_help >&2
exit 1
;;
esac
done
# Random integer in [0, max) using /dev/urandom.
rand_int() {
local max=$1
local n
n=$(od -An -N4 -tu4 /dev/urandom | tr -d '[:space:]')
echo $(( n % max ))
}
# True when haystack contains needle (needle is matched literally).
contains_char() {
local haystack=$1 needle=$2
[[ "$haystack" == *"$needle"* ]]
}
pick_random() {
local str=$1
local len=${#str}
local idx
idx=$(rand_int "$len")
printf '%s' "${str:$idx:1}"
}
# Fisher-Yates shuffle of a string.
shuffle_string() {
local str=$1
local n=${#str}
if (( n == 0 )); then
return
fi
local -a chars
local i
for (( i=0; i<n; i++ )); do
chars[$i]=${str:$i:1}
done
local j tmp
for (( i=n-1; i>0; i-- )); do
j=$(rand_int $(( i + 1 )))
tmp=${chars[i]}
chars[i]=${chars[j]}
chars[j]=$tmp
done
local result=""
for (( i=0; i<n; i++ )); do
result+="${chars[i]}"
done
printf '%s' "$result"
}
# Characters of $1 not present in $2. Avoids ${var//pat/} so glob metachars
# (* ? [ ] { }) in the remove-set aren't interpreted as patterns.
remove_chars() {
local source=$1 to_remove=$2
local result=""
local i ch
for (( i=0; i<${#source}; i++ )); do
ch=${source:$i:1}
if ! contains_char "$to_remove" "$ch"; then
result+="$ch"
fi
done
printf '%s' "$result"
}
sort_string() {
printf '%s' "$1" | fold -w1 | sort | tr -d '\n'
}
generate_password() {
local length=$1
local current_pass=$2
local lowercase=$LOWERCASE
local uppercase=$UPPERCASE
local numbers=$NUMBERS
local special=$SPECIAL
local all_chars="$lowercase$uppercase$numbers$special"
local guaranteed_different=""
if [[ -n "$current_pass" ]]; then
local chars_not_in_current
chars_not_in_current=$(remove_chars "$all_chars" "$current_pass")
if (( ${#chars_not_in_current} < NEW_PW_CHARS_DIFFERENT )); then
echo "Not enough unique characters available different from current password. You may have to reset your password through the 'Forgot Password' process." >&2
exit 1
fi
guaranteed_different=$(shuffle_string "$chars_not_in_current")
guaranteed_different=${guaranteed_different:0:NEW_PW_CHARS_DIFFERENT}
fi
local upper_limit=${#all_chars}
local lower_limit=$(( upper_limit - NEW_PW_CHARS_DIFFERENT ))
local reserve_count
if (( length >= upper_limit )); then
reserve_count=$NEW_PW_CHARS_DIFFERENT
elif (( length < lower_limit )); then
reserve_count=0
else
reserve_count=$(( length - lower_limit ))
fi
if (( reserve_count > 0 )); then
local reserved_chars
reserved_chars=$(shuffle_string "$all_chars")
reserved_chars=${reserved_chars:0:reserve_count}
lowercase=$(remove_chars "$lowercase" "$reserved_chars")
uppercase=$(remove_chars "$uppercase" "$reserved_chars")
numbers=$(remove_chars "$numbers" "$reserved_chars")
special=$(remove_chars "$special" "$reserved_chars")
echo "Reserved $reserve_count characters for future use: $(sort_string "$reserved_chars")" >&2
fi
local password=""
password+=$(pick_random "$lowercase")
password+=$(pick_random "$uppercase")
password+=$(pick_random "$numbers")
password+=$(pick_random "$special")
password+="$guaranteed_different"
local pool="$lowercase$uppercase$numbers$special"
while (( ${#password} < length )); do
password+=$(pick_random "$pool")
done
shuffle_string "$password"
}
test_password() {
local current_pass=$1 new_pass=$2
local different=""
local i ch
for (( i=0; i<${#new_pass}; i++ )); do
ch=${new_pass:$i:1}
if ! contains_char "$current_pass" "$ch" \
&& ! contains_char "$different" "$ch"; then
different+="$ch"
fi
done
echo "Number of unique characters in new password not present in current password: ${#different}" >&2
echo "Different characters: $(sort_string "$different")" >&2
}
read -r -s -p "Enter current password (press Enter for none): " CURRENT_PASS
echo
if [[ -z "$CURRENT_PASS" ]]; then
echo "No current password provided - generating completely new password" >&2
NEW_PASS=$(generate_password "$LENGTH" "")
else
echo "Current password provided - ensuring new password is sufficiently different" >&2
NEW_PASS=$(generate_password "$LENGTH" "$CURRENT_PASS")
test_password "$CURRENT_PASS" "$NEW_PASS"
fi
# Scrub the current-password variable from the environment.
CURRENT_PASS=""
unset CURRENT_PASS
echo "New password: $NEW_PASS"
copy_to_clipboard() {
if command -v pbcopy >/dev/null 2>&1; then
pbcopy
elif [[ -n "${WAYLAND_DISPLAY:-}" ]] && command -v wl-copy >/dev/null 2>&1; then
wl-copy
elif command -v xclip >/dev/null 2>&1; then
xclip -selection clipboard
elif command -v xsel >/dev/null 2>&1; then
xsel --clipboard --input
else
return 1
fi
}
if printf '%s' "$NEW_PASS" | copy_to_clipboard; then
echo "Password copied to clipboard" >&2
else
echo "No clipboard utility found (install xclip, xsel, or wl-clipboard)" >&2
fi
@dadatuputi

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment