Skip to content

Commit 017a551

Browse files
committed
rebrand the Juniper vendor name to Juniper/HPE in docs and website, keeping the code API unchanged
1 parent fa2daad commit 017a551

14 files changed

Lines changed: 52 additions & 46 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project are documented here. This covers both the Python package and the website under `web/`. The website carries no version of its own: it deploys continuously to Cloudflare Pages on every push to `master`, so its changes are recorded under the package release they shipped alongside.
44

5+
## Unreleased
6+
7+
### Changed
8+
9+
- Rebranded the Juniper vendor name to **Juniper/HPE** across the docs and website, following HPE's acquisition of Juniper. The code API (module names, CLI subcommands, URLs, env vars) is unchanged.
10+
511
## v0.1.1 - 2026-07-23
612

713
### Fixed

‎README.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Python versions](https://img.shields.io/pypi/pyversions/network-secret)](https://pypi.org/project/network-secret/)
66
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
77

8-
Encode, decode, and check network device secrets for Juniper JunOS and Nokia SR OS, from the command line or Python. `network-secret` is a unified successor to `juniper8-crypt` and `juniper9-crypt`: it covers all three formats in a single package with a single CLI.
8+
Encode, decode, and check network device secrets for Juniper/HPE JunOS and Nokia SR OS, from the command line or Python. `network-secret` is a unified successor to `juniper8-crypt` and `juniper9-crypt`: it covers all three formats in a single package with a single CLI.
99

1010
> **Prefer a browser?** Encode and decode all three formats at **[network-secret.pages.dev](https://network-secret.pages.dev/)**. It runs the same algorithms fully client-side - nothing you type is ever sent to a server.
1111
@@ -25,8 +25,8 @@ The two implementations share known-answer vectors, so keeping them in one repo
2525

2626
| Format | CLI subcommand | Python module | Description |
2727
|--------|---------------|---------------|-------------|
28-
| `$9$` | `juniper9` | `network_secret.juniper9` | Juniper reversible obfuscation - keyless |
29-
| `$8$` | `juniper8` | `network_secret.juniper8` | Juniper AES-256-GCM - keyed by master password |
28+
| `$9$` | `juniper9` | `network_secret.juniper9` | Juniper/HPE reversible obfuscation - keyless |
29+
| `$8$` | `juniper8` | `network_secret.juniper8` | Juniper/HPE AES-256-GCM - keyed by master password |
3030
| Nokia custom-hash | `nokia-sros-custom-hash` | `network_secret.nokia_sros_custom_hash` | Nokia SR OS AES-ECB shared-key cipher |
3131

3232
## Install
@@ -46,12 +46,12 @@ uv add network-secret
4646
```python
4747
from network_secret import juniper8, juniper9, nokia_sros_custom_hash
4848

49-
# Juniper $9$ (keyless)
49+
# Juniper/HPE $9$ (keyless)
5050
cipher9 = juniper9.encrypt("BGPsecret1")
5151
plain9 = juniper9.decrypt(cipher9)
5252
# 'BGPsecret1'
5353

54-
# Juniper $8$ (master-password keyed)
54+
# Juniper/HPE $8$ (master-password keyed)
5555
master = "MyMasterPassword"
5656
cipher8 = juniper8.encrypt("BGPsecret1", master)
5757
plain8 = juniper8.decrypt(cipher8, master)
@@ -80,15 +80,15 @@ network-secret --list
8080
network-secret --version
8181
```
8282

83-
### Juniper `$9$` (keyless)
83+
### Juniper/HPE `$9$` (keyless)
8484

8585
```bash
8686
network-secret juniper9 --encrypt 'BGPsecret1'
8787
network-secret juniper9 --decrypt '$9$abc...'
8888
network-secret juniper9 --check '$9$abc...' 'BGPsecret1'
8989
```
9090

91-
### Juniper `$8$` (master-password keyed)
91+
### Juniper/HPE `$8$` (master-password keyed)
9292

9393
The master password is resolved in this order: `-m`/`--master` flag, then the `JUNOS_MASTER_PASSWORD` environment variable, then an interactive no-echo prompt.
9494

‎network_secret/__init__.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""network-secret: encode, decode, and check network device secrets.
22
33
Supported ciphers (one module each):
4-
juniper9 - Juniper $9$ reversible obfuscation (keyless)
5-
juniper8 - Juniper $8$ AES-256-GCM (master-password keyed)
4+
juniper9 - Juniper/HPE $9$ reversible obfuscation (keyless)
5+
juniper8 - Juniper/HPE $8$ AES-256-GCM (master-password keyed)
66
nokia_sros_custom_hash - Nokia SR OS custom-hash AES-ECB (shared-key)
77
88
>>> from network_secret import juniper8, juniper9, nokia_sros_custom_hash

‎network_secret/juniper8.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"""
2-
Encrypt and decrypt Juniper $8$ (type 8) passwords.
2+
Encrypt and decrypt Juniper/HPE $8$ (type 8) passwords.
33
44
Unlike the reversible, keyless $9$ substitution cipher, the $8$ format is
55
genuine authenticated encryption keyed by the device master password
66
(``set system master-password``). The same master password is required to
77
both encrypt and decrypt: without it, $8$ secrets cannot be recovered.
88
9-
Juniper documents the format, but the documentation is incomplete: it omits
9+
Juniper/HPE documents the format, but the documentation is incomplete: it omits
1010
that only the first 12 bytes of the 16-byte iv field are used as the GCM
1111
nonce, so a by-the-book implementation fails authentication. That missing
1212
detail was reverse-engineered and verified against a real JUNOS 23.2 device
@@ -79,7 +79,7 @@ def _derive_key(master_password: str, salt: bytes, iterations: int) -> bytes:
7979

8080

8181
def encrypt(plaintext: str, master_password: str) -> str:
82-
"""Encrypt plaintext into a Juniper $8$ value under master_password.
82+
"""Encrypt plaintext into a Juniper/HPE $8$ value under master_password.
8383
8484
Output is non-deterministic: a fresh random salt and IV are generated on
8585
every call, so the same plaintext produces a different $8$ string each
@@ -109,13 +109,13 @@ def encrypt(plaintext: str, master_password: str) -> str:
109109

110110

111111
def decrypt(ciphertext: str, master_password: str) -> str:
112-
"""Decrypt a Juniper $8$ value with master_password.
112+
"""Decrypt a Juniper/HPE $8$ value with master_password.
113113
114114
Raises ValueError on a malformed $8$ string or on authentication failure
115115
(wrong master password, or a value not produced by this scheme).
116116
"""
117117
if not ciphertext.startswith(MAGIC):
118-
raise ValueError(f"Not a Juniper $8$ string: must start with {MAGIC}")
118+
raise ValueError(f"Not a Juniper/HPE $8$ string: must start with {MAGIC}")
119119

120120
# Leading '$' yields an empty first element; '8' is the type tag.
121121
parts = ciphertext.split("$")

‎network_secret/juniper9.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
2-
Encrypt and decrypt Juniper $9$ reversible passwords.
2+
Encrypt and decrypt Juniper/HPE $9$ reversible passwords.
33
4-
The $9$ algorithm is a proprietary Juniper substitution cipher that is
4+
The $9$ algorithm is a proprietary Juniper/HPE substitution cipher that is
55
deterministic and device-independent: the same plaintext can be encrypted
6-
on any Juniper device and decrypted on any other, with no node-specific
6+
on any Juniper/HPE device and decrypted on any other, with no node-specific
77
secret involved.
88
99
This is a Python implementation of the algorithm. It is based on
@@ -35,9 +35,9 @@ def _gap(c1: str, c2: str) -> int:
3535

3636

3737
def decrypt(ciphertext: str) -> str:
38-
"""Decrypt a Juniper $9$ ciphertext back to plaintext."""
38+
"""Decrypt a Juniper/HPE $9$ ciphertext back to plaintext."""
3939
if not ciphertext.startswith(MAGIC):
40-
raise ValueError(f"Not a Juniper $9$ string: must start with {MAGIC}")
40+
raise ValueError(f"Not a Juniper/HPE $9$ string: must start with {MAGIC}")
4141
chars = ciphertext[len(MAGIC):]
4242
if not chars:
4343
raise ValueError("Empty $9$ payload")
@@ -64,7 +64,7 @@ def decrypt(ciphertext: str) -> str:
6464

6565

6666
def encrypt(plaintext: str) -> str:
67-
"""Encrypt plaintext to a Juniper $9$ ciphertext.
67+
"""Encrypt plaintext to a Juniper/HPE $9$ ciphertext.
6868
6969
Output is non-deterministic: random filler characters mean each call
7070
returns a different ciphertext for the same plaintext. All of them

‎network_secret/registry.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
REGISTRY: list[Cipher] = [
1414
Cipher(
1515
id="juniper9",
16-
vendor="Juniper",
17-
name="Juniper $9$",
16+
vendor="Juniper/HPE",
17+
name="Juniper/HPE $9$",
1818
key_kind=KeyKind.NONE,
1919
encrypt=juniper9.encrypt,
2020
decrypt=juniper9.decrypt,
2121
check=juniper9.check,
2222
),
2323
Cipher(
2424
id="juniper8",
25-
vendor="Juniper",
26-
name="Juniper $8$",
25+
vendor="Juniper/HPE",
26+
name="Juniper/HPE $8$",
2727
key_kind=KeyKind.MASTER_PASSWORD,
2828
encrypt=juniper8.encrypt,
2929
decrypt=juniper8.decrypt,

‎pyproject.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[project]
22
name = "network-secret"
33
version = "0.1.1"
4-
description = "Encode, decode, and check network device secrets: Juniper $9$, Juniper $8$, and Nokia SR OS custom-hash"
4+
description = "Encode, decode, and check network device secrets: Juniper/HPE $9$, Juniper/HPE $8$, and Nokia SR OS custom-hash"
55
readme = "README.md"
66
license = { file = "LICENSE" }
77
authors = [
88
{ name = "Antoine Keranflec'h", email = "antoine.keranflech@gmail.com" },
99
]
1010
requires-python = ">=3.11"
11-
keywords = ["juniper", "junos", "nokia", "sros", "password", "crypt", "decrypt", "network", "$9$", "$8$", "custom-hash"]
11+
keywords = ["juniper", "hpe", "junos", "nokia", "sros", "password", "crypt", "decrypt", "network", "$9$", "$8$", "custom-hash"]
1212
classifiers = [
1313
"Development Status :: 4 - Beta",
1414
"Environment :: Console",

‎tests/test_registry.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def test_registry_ids_and_order():
1313

1414

1515
def test_find_returns_cipher_or_none():
16-
assert find("juniper8").vendor == "Juniper"
16+
assert find("juniper8").vendor == "Juniper/HPE"
1717
assert find("does-not-exist") is None
1818

1919

‎web/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Formats:
66

77
| # | Format | Vendor | Kind | Key |
88
|----|---------------|-------------|--------------------------------------|-----------------|
9-
| 01 | `$9$` | Juniper | reversible obfuscation | none |
10-
| 02 | `$8$` | Juniper | AES-256-GCM authenticated encryption | master password |
9+
| 01 | `$9$` | Juniper/HPE | reversible obfuscation | none |
10+
| 02 | `$8$` | Juniper/HPE | AES-256-GCM authenticated encryption | master password |
1111
| 03 | `custom-hash` | Nokia SR OS | AES-ECB + PKCS#7 (deterministic) | shared AES key |
1212

1313
One page per format. Adding a converter is one module (see [Adding a converter](#adding-a-converter)).

‎web/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<meta
88
name="description"
9-
content="Decode and encode network device secrets entirely in your browser: Juniper type 9 ($9$), Juniper type 8 ($8$), and Nokia SR OS custom-hash. Nothing is sent to a server."
9+
content="Decode and encode network device secrets entirely in your browser: Juniper/HPE type 9 ($9$), Juniper/HPE type 8 ($8$), and Nokia SR OS custom-hash. Nothing is sent to a server."
1010
/>
11-
<title>Network Secret: Juniper $9$ / $8$ &amp; Nokia secret toolkit</title>
11+
<title>Network Secret: Juniper/HPE $9$ / $8$ &amp; Nokia secret toolkit</title>
1212
<script>
1313
(function () {
1414
try {

0 commit comments

Comments
 (0)