add subcmds/new/password

This commit is contained in:
do butterflies cry? 2026-01-21 14:15:31 +10:00
parent 4daab330cb
commit 97c9dfb986
2 changed files with 261 additions and 0 deletions

View file

@ -23,6 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET}
${BOLD}${UNDERLINE}${RED}Subcommands${RESET}
${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair
${BOLD}${CYAN}password${RESET} Generate a new hashed Unix user password
${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH keypair
${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair"

260
ceru/subcmds/new/password Executable file
View file

@ -0,0 +1,260 @@
#!/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 key derivation function rounds 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
re='^[0-9]+$'
if ! [[ "$ROUNDS" =~ ^[0-9]+$ ]] ; then
throw-badval 1 "$ROUNDS" '-r|--rounds'
fi
unset -v re
# 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