If anyone spices this up some more, please share it with all of us.
Here's my contribution. Should work on POSIX systems and maybe under Cygwin/MSYS. Don't be scared by the size of the script; most of it are comments and breakups of lines to fit within 80 characters.
#!/bin/sh
#
# passwmaker - version 1.0
# A sh script for PasswordMaker-compatible hash generation.
# Author: Pedro Gimeno <http://www.formauri.es/personal/pgimeno/>
# License: Public domain. NO WARRANTIES OF ANY KIND.
#
# Usage: passwmaker <data> <charset> <alg> <length> [<pfx> [<sfx>]]
# where:
# data is the concatenation of MPW + URL + user + counter.
# charset is a string of characters allowed in the key.
# alg is the hash algorithm to use (see Limitations below).
# length is the password length.
# pfx is the prefix (optional). May be "".
# sfx is the suffix (optional). May be "" but makes more sense to
# omit it in that case.
# Example:
# $ passwmaker PWdomain.orguser1 aBcDeFg0123456789 md5 30 x y
# should output the same password as
# <https://passwordmaker.org/passwordmaker.html> with the following
# settings:
# Master password = PW
# Use l33t = not at all
# Hash algorithm = MD5
# Domain = domain.org
# Length of generated password = 30
# User = user
# Counter = 1
# Characters = aBcDeFg0123456789
# Prefix = x
# Suffix = y
# namely:
# xB27gBB7527g8D6e8730B75c71F1Dy
#
# Limitations:
# - Does not suport l33t modes nor HMAC. Maybe l33t modes can be
# implemented using tr; I just haven't tried.
# - Hash support depends upon installed utilities. My version of
# OpenSSL does not currently support SHA256, but shash might work:
# <http://mcrypt.hellug.gr/shash/>
# - Max charset size is 113 characters.
# - UTF-8 support depends on the involved utilities.
# - The escaping of special characters in the shell's command line
# is, naturally, up to the user.
# - The concatenation of MPW+URL+user+counter is up to the user.
# - While the author has made an effort to make it as POSIX-compliant
# as possible, it has not been tried on other than his Debian Linux
# box.
# - Error checking is scarce. Be careful with improper arguments.
# - Because of being a shell script, the master password is not asked
# in a secure way. It may remain in your shell's history.
data="$1"
charset="$2"
method="$3"
length="$4"
prefix="$5"
suffix="$6"
# Abort if charset is not at least 2 chars long.
# Note: ${#var} is the length of the value of var (POSIX sh).
test "${#charset}" -gt 1 || exit 1
# If $suffix is greater than or equal to $length, truncate it to
# $length and output it, since the password is just the suffix
# in that case.
test "${#suffix}" -lt $length || {
echo "$suffix" | cut -c 1-$length
exit 0
}
# Calculate the hash.
hash=$(echo -n "$data" | openssl dgst -$method)
# If openssl is not available, use this instead
# (works for md5 only if you have md5sum and for sha1 if you have
# sha1sum):
#hash=$(echo -n "$data" | ${method}sum)
# Extract the hash itself converting it to uppercase.
hash=$(echo "$hash" | cut -d " " -f 1 | tr abcdef ABCDEF)
# Use a bc mini-script to calculate the successive remainders.
# Use hexadecimal input to enter the hash, then switch again
# to decimal to accept the lengths. Output in octal for \nnn to
# work. The output is in little endian order and needs to be
# reversed.
result="obase=8; ibase=16; n=$hash; ibase=A; m=16^${#hash};"
result="$result while (n != 0)"
# Print "\nnn" and a newline (POSIX bc can't avoid the newline).
# We offset nnn by 14 (0Eh, 016o) so that \n and \r are skipped.
result="$result {\"\\\"; n % ${#charset} + 14; n /= ${#charset};}"
# Run the script in bc.
result=$(echo "$result" | bc)
# Translate the output to the charset; input range: 14-126.
result=$(printf "$result" | tr "\016-~" "$charset")
result=$(echo "$result" | nl | sort -n -r | cut -f 2 | tr -d "\r\n")
# 'rev' makes life much easier but may be unavailable:
#result=$(echo "$result" | tr -d "\r\n" | rev)
# Finally, process prefix/suffix.
result=$(echo "$prefix$result" | cut -c 1-$(($length-${#suffix})))
echo "$result$suffix"
HTH. Enhancements are welcome.
-- Pedro Gimeno