Module network_hash_gen.juniper_junos.type_9
Expand source code
from typing import Dict, List, Optional, Tuple
from network_hash_gen.utils import _generate_salt
class Type9:
"""
The JunOS Type 9 password obfuscation algorithm.
It is used to store passwords in the configuration that need to be
available in plaintext, but should not be easily read by humans.
For example passwords for BGP sessions.
This is not a secure hash function!
This implementation is based on code from
https://github.com/msuksong/junosdecode/blob/master/junosdecode.py
"""
__MAGIC = "$9$"
__FAMILY: List[str] = [
"QzF3n6/9CAtpu0O",
"B1IREhcSyrleKvMW8LXx",
"7N-dVbwsY2g4oaJZGUDj",
"iHkq.mPf5T",
]
# maps single characters to numbers from 0 to 3 (incl.)
__EXTRA: Dict[str, int] = dict()
for x, item in enumerate(__FAMILY):
for c in item:
__EXTRA[c] = 3 - x
# List of 65 character, in sorted order they are:
# -./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
__NUM_ALPHA: str = "".join(__FAMILY)
# reverse mapping from __NUM_ALPHA
__ALPHA_NUM: Dict[str, int] = {x: i for (i, x) in enumerate("".join(__FAMILY))}
# encoding moduli by position
__ENCODING = [
[1, 4, 32],
[1, 16, 32],
[1, 8, 32],
[1, 64],
[1, 32],
[1, 4, 16, 128],
[1, 32, 64],
]
@staticmethod
def encode(plaintext: str) -> str:
"""
Encoded the given plaintext.
Generates a random salt.
"""
salt = Type9._generate_salt(length=1)
return Type9.encode_salted(plaintext, salt)
@staticmethod
def encode_seeded(plaintext: str, seed: str) -> str:
salt = Type9._generate_salt(length=1, seed=seed)
return Type9.encode_salted(plaintext, salt)
@staticmethod
def encode_salted(plaintext: str, salt: str) -> str:
"""
Encodes the given plaintext.
"""
assert len(salt) == 1
assert salt in Type9.__NUM_ALPHA
# an additional, random string of length 0 to 3 (incl.)
rand: str = Type9._generate_salt(Type9.__EXTRA[salt], seed=salt)
position: int = 0
prev: str = salt
crypt = Type9.__MAGIC + salt + rand
for x in plaintext:
moduli = Type9.__ENCODING[position % len(Type9.__ENCODING)]
crypt += Type9._gap_encode(x, prev, moduli)
prev = crypt[-1]
position += 1
return crypt
@staticmethod
def decode(secret: str) -> str:
"""
Decodes the given secret.
"""
splits: List[str] = secret.split("$")
assert len(splits) == 3
magic: str = splits[1]
assert magic == "9"
encoding: str = splits[2]
salt: str = encoding[0]
rand_length: int = Type9.__EXTRA[salt]
crypt: str = encoding[1 + rand_length :]
input_position: int = 0
prev: str = salt
output: str = ""
while input_position < len(crypt):
moduli: List[int] = Type9.__ENCODING[len(output) % len(Type9.__ENCODING)]
assert input_position + len(moduli) <= len(crypt)
# the 2 to 4 characters that define the next output character
enc: str = crypt[input_position : input_position + len(moduli)]
gaps: List[int] = list()
for character in enc:
gap = Type9.__ALPHA_NUM[character] - Type9.__ALPHA_NUM[prev] - 1
gap %= len(Type9.__NUM_ALPHA)
gaps.append(gap)
prev = character
result = 0
for i, gap in enumerate(gaps):
result += gap * moduli[i]
output += chr(result)
input_position += len(moduli)
return output
@staticmethod
def _gap_encode(pc: str, prev: str, moduli: List[int]):
"""
pc is the plaintext byte,
prev is the previous encoding,
moduli is a list of moduli.
"""
assert len(pc) == 1
assert len(prev) == 1
_ord = ord(pc) # Unicode value of that character
crypt: str = ""
gaps: List = []
# builds a encoding based on the given module.
# with moduli [1, 2, 4, 8, ...] this would be binary encoding.
# with moduli [1, 16, 256] it would be hexadecimal
# but in this case each position can have a different value
for mod in reversed(moduli):
gaps.insert(0, int(_ord / mod))
_ord %= mod
for gap in gaps:
gap += Type9.__ALPHA_NUM[prev] + 1
prev = Type9.__NUM_ALPHA[gap % len(Type9.__NUM_ALPHA)]
crypt += prev
assert len(crypt) == len(moduli)
return crypt
@staticmethod
def _parse_encoding(secret: str) -> Tuple[str, str, str]:
"""
Returns a tuple containing the salt, rand and the encoded part
of the given secret
"""
splits: List[str] = secret.split("$")
assert len(splits) == 3
magic: str = splits[1]
assert magic == "9"
encoding: str = splits[2]
salt: str = encoding[0]
rand_length: int = Type9.__EXTRA[salt]
rand: str = encoding[1 : 1 + rand_length]
crypt: str = encoding[1 + rand_length :]
return salt, rand, crypt
@staticmethod
def _generate_salt(length: int, seed: Optional[str] = None) -> str:
return _generate_salt("".join(Type9.__NUM_ALPHA), length, seed)
Classes
class Type9
-
The JunOS Type 9 password obfuscation algorithm.
It is used to store passwords in the configuration that need to be available in plaintext, but should not be easily read by humans. For example passwords for BGP sessions.
This is not a secure hash function!
This implementation is based on code from https://github.com/msuksong/junosdecode/blob/master/junosdecode.py
Expand source code
class Type9: """ The JunOS Type 9 password obfuscation algorithm. It is used to store passwords in the configuration that need to be available in plaintext, but should not be easily read by humans. For example passwords for BGP sessions. This is not a secure hash function! This implementation is based on code from https://github.com/msuksong/junosdecode/blob/master/junosdecode.py """ __MAGIC = "$9$" __FAMILY: List[str] = [ "QzF3n6/9CAtpu0O", "B1IREhcSyrleKvMW8LXx", "7N-dVbwsY2g4oaJZGUDj", "iHkq.mPf5T", ] # maps single characters to numbers from 0 to 3 (incl.) __EXTRA: Dict[str, int] = dict() for x, item in enumerate(__FAMILY): for c in item: __EXTRA[c] = 3 - x # List of 65 character, in sorted order they are: # -./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz __NUM_ALPHA: str = "".join(__FAMILY) # reverse mapping from __NUM_ALPHA __ALPHA_NUM: Dict[str, int] = {x: i for (i, x) in enumerate("".join(__FAMILY))} # encoding moduli by position __ENCODING = [ [1, 4, 32], [1, 16, 32], [1, 8, 32], [1, 64], [1, 32], [1, 4, 16, 128], [1, 32, 64], ] @staticmethod def encode(plaintext: str) -> str: """ Encoded the given plaintext. Generates a random salt. """ salt = Type9._generate_salt(length=1) return Type9.encode_salted(plaintext, salt) @staticmethod def encode_seeded(plaintext: str, seed: str) -> str: salt = Type9._generate_salt(length=1, seed=seed) return Type9.encode_salted(plaintext, salt) @staticmethod def encode_salted(plaintext: str, salt: str) -> str: """ Encodes the given plaintext. """ assert len(salt) == 1 assert salt in Type9.__NUM_ALPHA # an additional, random string of length 0 to 3 (incl.) rand: str = Type9._generate_salt(Type9.__EXTRA[salt], seed=salt) position: int = 0 prev: str = salt crypt = Type9.__MAGIC + salt + rand for x in plaintext: moduli = Type9.__ENCODING[position % len(Type9.__ENCODING)] crypt += Type9._gap_encode(x, prev, moduli) prev = crypt[-1] position += 1 return crypt @staticmethod def decode(secret: str) -> str: """ Decodes the given secret. """ splits: List[str] = secret.split("$") assert len(splits) == 3 magic: str = splits[1] assert magic == "9" encoding: str = splits[2] salt: str = encoding[0] rand_length: int = Type9.__EXTRA[salt] crypt: str = encoding[1 + rand_length :] input_position: int = 0 prev: str = salt output: str = "" while input_position < len(crypt): moduli: List[int] = Type9.__ENCODING[len(output) % len(Type9.__ENCODING)] assert input_position + len(moduli) <= len(crypt) # the 2 to 4 characters that define the next output character enc: str = crypt[input_position : input_position + len(moduli)] gaps: List[int] = list() for character in enc: gap = Type9.__ALPHA_NUM[character] - Type9.__ALPHA_NUM[prev] - 1 gap %= len(Type9.__NUM_ALPHA) gaps.append(gap) prev = character result = 0 for i, gap in enumerate(gaps): result += gap * moduli[i] output += chr(result) input_position += len(moduli) return output @staticmethod def _gap_encode(pc: str, prev: str, moduli: List[int]): """ pc is the plaintext byte, prev is the previous encoding, moduli is a list of moduli. """ assert len(pc) == 1 assert len(prev) == 1 _ord = ord(pc) # Unicode value of that character crypt: str = "" gaps: List = [] # builds a encoding based on the given module. # with moduli [1, 2, 4, 8, ...] this would be binary encoding. # with moduli [1, 16, 256] it would be hexadecimal # but in this case each position can have a different value for mod in reversed(moduli): gaps.insert(0, int(_ord / mod)) _ord %= mod for gap in gaps: gap += Type9.__ALPHA_NUM[prev] + 1 prev = Type9.__NUM_ALPHA[gap % len(Type9.__NUM_ALPHA)] crypt += prev assert len(crypt) == len(moduli) return crypt @staticmethod def _parse_encoding(secret: str) -> Tuple[str, str, str]: """ Returns a tuple containing the salt, rand and the encoded part of the given secret """ splits: List[str] = secret.split("$") assert len(splits) == 3 magic: str = splits[1] assert magic == "9" encoding: str = splits[2] salt: str = encoding[0] rand_length: int = Type9.__EXTRA[salt] rand: str = encoding[1 : 1 + rand_length] crypt: str = encoding[1 + rand_length :] return salt, rand, crypt @staticmethod def _generate_salt(length: int, seed: Optional[str] = None) -> str: return _generate_salt("".join(Type9.__NUM_ALPHA), length, seed)
Class variables
var c
var item
var x
Static methods
def decode(secret: str) ‑> str
-
Decodes the given secret.
Expand source code
@staticmethod def decode(secret: str) -> str: """ Decodes the given secret. """ splits: List[str] = secret.split("$") assert len(splits) == 3 magic: str = splits[1] assert magic == "9" encoding: str = splits[2] salt: str = encoding[0] rand_length: int = Type9.__EXTRA[salt] crypt: str = encoding[1 + rand_length :] input_position: int = 0 prev: str = salt output: str = "" while input_position < len(crypt): moduli: List[int] = Type9.__ENCODING[len(output) % len(Type9.__ENCODING)] assert input_position + len(moduli) <= len(crypt) # the 2 to 4 characters that define the next output character enc: str = crypt[input_position : input_position + len(moduli)] gaps: List[int] = list() for character in enc: gap = Type9.__ALPHA_NUM[character] - Type9.__ALPHA_NUM[prev] - 1 gap %= len(Type9.__NUM_ALPHA) gaps.append(gap) prev = character result = 0 for i, gap in enumerate(gaps): result += gap * moduli[i] output += chr(result) input_position += len(moduli) return output
def encode(plaintext: str) ‑> str
-
Encoded the given plaintext. Generates a random salt.
Expand source code
@staticmethod def encode(plaintext: str) -> str: """ Encoded the given plaintext. Generates a random salt. """ salt = Type9._generate_salt(length=1) return Type9.encode_salted(plaintext, salt)
def encode_salted(plaintext: str, salt: str) ‑> str
-
Encodes the given plaintext.
Expand source code
@staticmethod def encode_salted(plaintext: str, salt: str) -> str: """ Encodes the given plaintext. """ assert len(salt) == 1 assert salt in Type9.__NUM_ALPHA # an additional, random string of length 0 to 3 (incl.) rand: str = Type9._generate_salt(Type9.__EXTRA[salt], seed=salt) position: int = 0 prev: str = salt crypt = Type9.__MAGIC + salt + rand for x in plaintext: moduli = Type9.__ENCODING[position % len(Type9.__ENCODING)] crypt += Type9._gap_encode(x, prev, moduli) prev = crypt[-1] position += 1 return crypt
def encode_seeded(plaintext: str, seed: str) ‑> str
-
Expand source code
@staticmethod def encode_seeded(plaintext: str, seed: str) -> str: salt = Type9._generate_salt(length=1, seed=seed) return Type9.encode_salted(plaintext, salt)