Using Python crypt for Shadow-Compliant Password Hashes
My open-source work includes DenyHosts and Scratchy, practical tools for administering and understanding Linux systems. Password hashing offers another script-friendly solution when a service must produce credentials accepted by an existing shadow and PAM stack.
Python’s crypt module wraps the traditional Unix crypt interface and can emit SHA-512 hashes used by /etc/shadow. It is legacy: crypt was deprecated in Python 3.11 and removed in Python 3.13. Module availability and method support must be checked on each target.
A shadow record combines an account name, encrypted password and fields governing password age, warnings and expiry. For SHA-512 crypt, the encrypted field carries the algorithm, work factor, salt and derived value. Shadow-compliant here means compatible with Unix password tooling, not regulatory certification.
The steps below cover preparing a password, creating a salted hash, verifying it and installing it without damaging account metadata. They also show where this approach fits across Australian cloud regions, data centres and regional offices.
Shadow formats and crypt methods
A typical /etc/shadow entry uses colon-separated fields: username, password hash, last-change day, minimum and maximum lifetime, warning period, inactivity period and expiry day. Password-age settings belong to the account, so replacing the hash should preserve them. Access is normally restricted to root, often through the shadow group.
Supported outputs include SHA-256 ($5$), SHA-512 ($6$), Blowfish/bcrypt ($2b$ on contemporary libcrypt) and other schemes. Confirm destination support with crypt.methods. A SHA-512 setting is written as $6$rounds=10000$salt$hash: rounds controls work, while salt makes each hash unique.
Preparing passwords and salts
Treat the password as secret input, not a command-line argument. Read it through getpass or a protected channel, encode it consistently as UTF-8 and reject embedded NUL bytes. SHA-crypt considers at most 72 password bytes; reject longer input rather than allow silent truncation. Apply the organisation’s length policy first.
Use crypt.mksalt with the intended method and rounds value. It draws salt characters using secure platform randomness. Check /etc/login.defs for local policy and benchmark on representative hardware before fixing the work factor.
import crypt
import getpass
def make_shadow_hash():
password = getpass.getpass("Password: ").encode("utf-8")
if len(password) > 72 or b"\0" in password:
raise ValueError("Unsupported password bytes")
salt = crypt.mksalt(crypt.METHOD_SHA512, rounds=10000)
result = crypt.crypt(password, salt)
if not result.startswith("$6$"):
raise RuntimeError("SHA-512 crypt unavailable")
return result
This returns a shadow-compatible string, not plaintext. Pass it to a privileged installation step through a protected pipe or file; keep raw passwords out of argv, logs and shell history.
Generating and verifying a hash
Verification reuses the stored algorithm, rounds and salt. Derive a candidate with those same parameters, then compare complete strings. hmac.compare_digest avoids exposing ordinary comparison timing differences.
import crypt
import hmac
def verify_shadow_hash(stored, candidate):
parts = stored.split("$")
if len(parts) not in (4, 5) or parts[1] != "6":
raise ValueError("Not a SHA-512 crypt hash")
salt = "$".join(parts[:4]) if len(parts) == 5 else "$".join(parts[:3])
candidate_bytes = candidate.encode("utf-8")
if len(candidate_bytes) > 72:
return False
candidate_hash = crypt.crypt(candidate_bytes, salt)
return hmac.compare_digest(candidate_hash, stored)
The salt must include its rounds prefix when present. A failed crypt call may return a short marker instead of a hash, so validate the stored prefix before comparing and keep password-byte rules identical during creation and verification.
Updating shadow entries safely
Do not rebuild /etc/shadow through text substitution. Preserve ownership, permissions and account ageing settings, and record existing requirements with chage -l. Use chpasswd -e to install an already encrypted password rather than asking it to encrypt plaintext.
sudo chpasswd -e < /root/alice-shadow-hash
Place the one-line account-and-hash record in that root-only file, then remove it after the change. A hash is not plaintext, but it remains an offline-cracking target. Test a non-production account, retain an existing privileged session and confirm local and SSH logins before closing the change window.
Australian infrastructure considerations
Australian estates often mix Debian or Ubuntu systems with RHEL-family servers, whether hosted in a Sydney or Melbourne data centre or managed for a Brisbane business. An MSP may support several Linux families at once. Verify Python and libcrypt support on every image, not just the developer laptop.
The surrounding service may handle personal information under the Privacy Act 1988 and the Australian Privacy Principles. Hashing one credential does not make wider data handling compliant. Australian Cyber Security Centre guidance also favours unique credentials, MFA and prompt patching alongside strong local password storage.
Regional sites with limited links may need an approved offline procedure: carry a verified hash and installation instructions rather than transmit plaintext. During an arvo change window, a Brisbane team may say it is checking the box before sign-off. The practical test is a real login through the expected PAM path.
Verification and password rotation
Check ENCRYPT_METHOD, SHA_CRYPT_MIN_ROUNDS and SHA_CRYPT_MAX_ROUNDS in /etc/login.defs, since distributions differ. Because chpasswd -e trusts the supplied hash, validate its prefix and parameters yourself. A script should fail closed when the method is unavailable.
When policy changes, rehash after successful login using a fresh salt and current parameters; retain the old hash until authentication succeeds. Audit administrative changes with account, host and timestamp, while keeping passwords, salts and complete hashes out of logs, tickets and chat.
Deployment checks and modern alternatives
crypt remains appropriate for narrow compatibility work, particularly an existing shadow-backed service that expects SHA-crypt. New applications should generally use a maintained password-hashing library and Argon2id, configured with measured memory, iteration and parallelism costs.
Checks before deployment
- Confirm the target’s crypt methods and approved rounds.
- Test a disposable account through the real PAM path.
- Preserve account metadata and existing privileged access.
- Document the Python 3.13 dependency boundary.
When a different format makes sense
- Use Argon2id for new services with suitable client support.
- Use bcrypt or yescrypt when policy requires them.
- Keep SHA-crypt for established shadow interoperability.
- Use maintained libraries for new password-storage formats.
Argon2id, bcrypt and yescrypt all need current client-library support and careful parameter selection. SHA-512 crypt is the relevant outcome when Unix shadow compatibility is mandatory, while Argon2id provides the stronger default for a new authentication system.
