Repository navigation
Add JUNOS encrypted-password ($5$ and $6$ SHA-crypt) support - #8
Conversation
…gling report reference
|
Added the variant option, so the branch is now 15 commits. What changedJUNOS writes
The default is unchanged. Kept generic rather than special-cased
This follows how Worth being clear aboutThis is a convenience, not a correctness fix. A Verification
261 Python tests on 3.11, 3.12, 3.13 and 3.14 ( The selector's rendering is the one thing untested here, since this environment has no browser and adding a component-testing library would have meant a new dependency. |
Adds JUNOS
encrypted-passwordas a ninth format, in the Python package, the CLI and the website.JUNOS stores local user passwords as standard Unix SHA-crypt, written in config as:
Two variants are covered:
$5$(sha256-crypt) and$6$(sha512-crypt), both Ulrich Drepper's SHA-crypt specification.$1$(md5crypt), which older JUNOS wrote, is deliberately out of scope and is rejected by name rather than failing as a generic parse error.It is one-way, following the contract Cisco type 8/9 and the Nokia
$2y$format established:encrypthashes,checkverifies a candidate password,decryptraises. On the site it gets Hash and Verify tabs and a One-way badge, and joins the existing Juniper/HPE menu.No new dependency, on either side
Python 3.13 removed the stdlib
cryptmodule, andpasslibimports it so it breaks there too. Web Crypto has no crypt primitive. Both implementations are therefore written from the specification, using onlyhashliband Web Crypto for the raw hashes.That is a different risk profile from previous formats, so the verification was correspondingly heavier. See below.
Verification against independent implementations
The algorithms are checked against two other implementations of the same spec, not only against each other:
openssl passwd: 22 boundary cases matched byte for byte, covering password lengths at 31, 32 (the sha256 digest length), 33, 63, 64 (the block size), 65, 127 and 128 bytes, plus multi-byte UTF-8, CJK and emoji passwords, for both variants.cryptbuild: used for the empty-password case, whichopenssl passwdrefuses to generate at all. It matches.Rounds are bounded, deliberately
$5$and$6$acceptrounds=Ninside the salt, and the spec permits N up to 999999999, read from the value being checked. Unbounded, a pasted hash could force minutes to hours of hashing and freeze a browser tab. Rounds are clamped to 1000-100000 before any hashing, mirroring howjuniper8bounds its iteration count and the Nokia$2y$format bounds its bcrypt cost. Measured: the JUNOS default of 5000 rounds is 0.003s, and the 100000 ceiling is 0.055s for$5$and 0.07s for$6$.Two deliberate divergences, both documented in the code
A non-canonical
rounds=007000is accepted but never emitted.checkechoes back the value you gave it with its digits unchanged, whileencryptalways writes the canonical form. This matchesopenssl, which likewise tolerates leading zeros on input and normalises its own output.An over-long salt is rejected rather than truncated. The spec silently truncates a salt beyond 16 characters. This module refuses it, because a real device can never emit one, and silently hashing against an untruncated salt would diverge from what the device computed. The reasoning is recorded in
_validate_salt's docstring.Also in here
--listcolumn widths are now derived from the registry rather than hardcoded. The new id is 26 characters and overflowed the 24-character column, breaking alignment. An identical defect was fixed once before by widening the number; deriving the widths means it cannot recur.Verification summary
245 Python tests, 220 web tests,
npm run checkclean at zero warnings,npm run buildsucceeds. Every known-answer vector passes through the CLI, including a value pasted with its surrounding quotes, trailing semicolon and## SECRET-DATAmarker intact.Note that the leading
encrypted-passwordkeyword is not stripped; only quotes, a trailing semicolon and the marker are. The README and both modules' docstrings describe that accurately.