258 lines
7.9 KiB
Bash
Executable file
258 lines
7.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Copyright 2026 Emile Clark-Boman
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
set -euo pipefail
|
|
|
|
USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET}
|
|
${BOLD}${GREEN}$THIS new password [option...]${RESET}
|
|
|
|
${BOLD}${UNDERLINE}${RED}Description${RESET}
|
|
Generates a new password hash in libxcrypt format with secure defaults.
|
|
For more advanced usage run the ${BOLD}${MAGENTA}\`mkpasswd\`${RESET} utility directly.
|
|
|
|
${BOLD}${UNDERLINE}${RED}Options${RESET}
|
|
${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^)
|
|
${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${CYAN}.pub${RESET})
|
|
${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format
|
|
${BOLD}${MAGENTA}-i, --stdin${RESET} Read the password to hash from stdin ${BOLD}$CYAN(single line only)${RESET}
|
|
${BOLD}${MAGENTA}-t, --type${RESET} The hash algorithm to use: ${BOLD}${MAGENTA}yescrypt, scrypt, bcrypt ${CYAN}(default: yescrypt)${RESET}
|
|
${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET}
|
|
${BOLD}${MAGENTA}-s, --salt${RESET} Specify the hash's salt directly ${BOLD}${RED}(not recommended)${RESET} ${BOLD}${CYAN}(default: libxcrypt secure default)${RESET}"
|
|
|
|
# ==== Argument Values ====
|
|
TYPE='yescrypt'
|
|
ROUNDS=''
|
|
SALTED=false
|
|
OUT=''
|
|
JSON=false
|
|
STDIN=false
|
|
EXTRA=''
|
|
# ==== Argument Values ====
|
|
|
|
# parse all args
|
|
while [[ $# -gt 0 ]]; do
|
|
ARG="$1"
|
|
case "$ARG" in
|
|
-h|--help)
|
|
throw-usage 0
|
|
;;
|
|
-o|--out)
|
|
shift
|
|
OUT="$1"; shift
|
|
;;
|
|
-j|--json)
|
|
shift
|
|
JSON=true
|
|
;;
|
|
-i|--stdin)
|
|
shift
|
|
STDIN=true
|
|
;;
|
|
-t|--type)
|
|
shift
|
|
TYPE="$1"; shift
|
|
;;
|
|
-r|--rounds)
|
|
shift
|
|
ROUNDS="$1"; shift
|
|
;;
|
|
-s|--salt)
|
|
shift
|
|
if [[ "$SALTED" == false ]]; then
|
|
SALTED=true
|
|
EXTRA="$EXTRA --salt=\'$1\'"
|
|
fi
|
|
shift
|
|
;;
|
|
-*)
|
|
throw-badflag 1 "$ARG"
|
|
;;
|
|
*)
|
|
throw-badarg 1 "$ARG"
|
|
;;
|
|
esac
|
|
done; unset -v ARG
|
|
|
|
# NOTE: Available password hashing methods for /etc/passwd & /etc/shadow
|
|
# NOTE: Read the manual pages via `man 3 crypt` and `man 5 crypt` (if available)
|
|
# NOTE: Available online via https://man.archlinux.org/man/crypt.5
|
|
# WARNING: Due to modern developments in cryptography most of these methods
|
|
# WARNING: are no longer recommended however some distrobutions still use them.
|
|
# WARNING: Cerulean intentionally restricts access to only secure algorithms.
|
|
# $ mkpasswd -m help
|
|
## Available methods:
|
|
## yescrypt Yescrypt
|
|
## gost-yescrypt GOST Yescrypt
|
|
## scrypt scrypt
|
|
## bcrypt bcrypt
|
|
## bcrypt-a bcrypt (obsolete $2a$ version)
|
|
## sha512crypt SHA-512
|
|
## sha256crypt SHA-256
|
|
## sunmd5 SunMD5
|
|
## md5crypt MD5
|
|
## bsdicrypt BSDI extended DES-based crypt(3)
|
|
## descrypt standard 56 bit DES-based crypt(3)
|
|
## nt NT-Hash
|
|
function perr-unsupportedhash {
|
|
local ALGO="$1"
|
|
echo -e "${BOLD}${CYAN}$THIS${RED} does not support the ${MAGENTA}$ALGO${RED} hashing algorithm${RESET}" >&2
|
|
}
|
|
function perr-forbiddenhash {
|
|
local ALGO="$1"
|
|
echo -e "${BOLD}${CYAN}Cerulean${RED} intentionally forbids ${MAGENTA}$ALGO-based${RED} hashes.${RESET}" >&2
|
|
}
|
|
function perr-recommendhash {
|
|
local ALGO="$1"
|
|
echo -e "${BOLD}${CYAN}Cerulean${WHITE} recommends the ${MAGENTA}$ALGO${WHITE} algorithm ${GREEN}(embrace modernity loser)${RESET}" >&2
|
|
}
|
|
|
|
# ensure $TYPE is a valid hash algorithm
|
|
case "$TYPE" in
|
|
# ========= PERMITTED HASH ALGORITHMS =========
|
|
yescrypt)
|
|
if [[ -z "$ROUNDS" ]]; then
|
|
ROUNDS='11'
|
|
fi
|
|
;;
|
|
scrypt)
|
|
if [[ -z "$ROUNDS" ]]; then
|
|
ROUNDS='10'
|
|
fi
|
|
;;
|
|
bcrypt)
|
|
if [[ -z "$ROUNDS" ]]; then
|
|
ROUNDS='14'
|
|
fi
|
|
;;
|
|
|
|
# ========= FORBIDDEN HASH ALGORITHMS =========
|
|
gost-yescrypt)
|
|
perr-unsupportedhash "$TYPE"
|
|
perr-recommendhash 'yescrypt'
|
|
echo -e "┏
|
|
┃ Dear Comrade,
|
|
┃ It is with a heavy heart I must inform you that \"GOST Algorithms
|
|
┃ Considered Harmful\" - Edsger Wybe Dijkstra (probably). Alas
|
|
┃ GOST is considered broken... It is no longer 1970, please grow up :(
|
|
┃ Слава Родине! - Glory to the Motherland!
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
bcrypt-a)
|
|
perr-unsupportedhash "$TYPE"
|
|
perr-recommendhash 'bcrypt'
|
|
echo -e "┏
|
|
┃ The alternative prefix \"\$2y$\" is equivalent to \"\$2b$\".
|
|
┃ It exists for historical reasons only. The alternative prefixes
|
|
┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with
|
|
┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed
|
|
┃ characters with the 8th bit set.
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
sha512crypt|sha256crypt)
|
|
perr-unsupportedhash "$TYPE"
|
|
perr-forbiddenhash 'SHA'
|
|
echo -e "┏
|
|
┃ SHA-based hashes are considered outdated and generally insecure
|
|
┃ due to their vulnerabilit to brute-force and collision attacks.
|
|
┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended.
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
sunmd5|md5crypt)
|
|
perr-unsupportedhash "$TYPE"
|
|
perr-forbiddenhash 'MD5'
|
|
echo -e "┏
|
|
┃ Not as weak as the DES-based hashes, but MD5 is so cheap
|
|
┃ on modern hardware that it should not be used for new hashes.
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
bsdicrypt|descrypt)
|
|
perr-unsupportedhash "$TYPE"
|
|
perr-forbiddenhash 'DES'
|
|
echo -e "┏
|
|
┃ The DES block cipher is cheap on modern hardware. Because there are only
|
|
┃ 4096 possible salts and 2**56 distinct passphrases, which it
|
|
┃ truncates to 8 characters, it is feasible to discover any passphrase
|
|
┃ hashed with this method. It should only be used if you absolutely have to
|
|
┃ generate hashes that will work on an old operating system that supports nothing else.
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
nt)
|
|
perr-unsupportedhash "$TYPE"
|
|
echo -e "${BOLD}Please ${RED}repent${WHITE} for your filthy sins ${RED}you disgusting human...${RESET}" >&2
|
|
echo -e "┏
|
|
┃ Available for cross-compatibility's sake on FreeBSD. Based on MD4.
|
|
┃ Has no salt or tunable cost parameter. It is so weak that
|
|
┃ almost any human-chosen passphrase hashed with this method is guessable.
|
|
┃ It should only be used if you absolutely have to generate hashes that
|
|
┃ will work on an old operating system that supports nothing else.
|
|
┗" >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
echo -e "${BOLD}${RED}Unrecognised hash algorithm ${MAGENTA}\"$TYPE\"${RESET}" >&2
|
|
echo -e "${BOLD}${GREEN}Supported algorithms: ${MAGENTA}yescrypt, scrypt, bcrypt${RESET}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
unset -f perr-unsupportedhash perr-forbiddenhash perr-recommendhash
|
|
|
|
# ensure $ROUNDS is a valid numeric
|
|
if ! isnumeric "$ROUNDS"; then
|
|
throw-badval 1 "$ROUNDS" '-r|--rounds'
|
|
fi
|
|
|
|
# Acquire password from stdin
|
|
if [[ "$STDIN" == true ]]; then
|
|
read -s PASS
|
|
else
|
|
read -sp "$(echo -e "${BOLD}${GREEN}Password:${RESET} ")" PASS
|
|
echo # \n
|
|
read -sp "$(echo -e "${BOLD}${GREEN}Retype Password:${RESET} ")" PASS2
|
|
echo # \n
|
|
if [[ "$PASS" != "$PASS2" ]]; then
|
|
echo -e "${BOLD}${RED}Sorry, passwords do not match${RESET}" >&2
|
|
exit 1
|
|
fi
|
|
unset -v PASS2
|
|
fi
|
|
|
|
# Compute hash of password
|
|
RESULT=$(mkpasswd -sm "$TYPE" -R "$ROUNDS" $EXTRA <<<"$PASS")
|
|
unset -v PASS
|
|
# Format as JSON if necessary
|
|
if [[ "$JSON" == true ]]; then
|
|
RESULT="{
|
|
\"type\": \"${TYPE}\",
|
|
\"rounds\": ${ROUNDS},
|
|
\"hash\": \"${RESULT}\"
|
|
}"
|
|
fi
|
|
|
|
# Display hash result
|
|
if [[ -n "$OUT" ]]; then
|
|
echo "$RESULT" > "$OUT"
|
|
elif [[ "$JSON" == true ]]; then
|
|
echo "$RESULT"
|
|
else
|
|
echo -e "${BOLD}${GREEN}Hash:${WHITE} $RESULT${RESET}"
|
|
fi
|
|
unset -v RESULT
|
|
|
|
unset -v TYPE ROUNDS SALTED OUT JSON STDIN EXTRA
|