Module network_hash_gen.juniper_junos

This module provides hash functions for Juniper Junos

Supported are:

  • Type 1 (md5-based)
  • Type 6 (sha512-based)
  • Type 9 (Vigenère-based (not a secure hash, must a obfuscation))
Expand source code
"""
This module provides hash functions for Juniper Junos


Supported are:

  - Type 1 (md5-based)
  - Type 6 (sha512-based)
  - Type 9 (Vigenère-based (not a secure hash, must a obfuscation))
"""
from .type_1 import Type1
from .type_6 import Type6
from .type_9 import Type9

__pdoc__ = {}

__all__ = ["Type1", "Type6", "Type9"]

Sub-modules

network_hash_gen.juniper_junos.type_1
network_hash_gen.juniper_junos.type_6
network_hash_gen.juniper_junos.type_9

Classes

class Type1

Calculates the md5-crypt based type 1 hashes for Juniper Junos.

Expand source code
class Type1(CiscoType5):
    """
    Calculates the md5-crypt based type 1 hashes for Juniper Junos.
    """

    salt_length = 8

Ancestors

Class variables

var salt_chars : str

Inherited from: BaseHash.salt_chars

A string containing all characters that can be used in a salt.

var salt_length : int

Inherited from: BaseHash.salt_length

The length of the salt.

Static methods

def hash(password: str) ‑> str

Inherited from: Type5.hash

Calculates the hash …

def hash_salted(password: str, salt: str) ‑> str

Inherited from: Type5.hash_salted

Calculates a Cisco IOS/IOS-XE Type 5 hash with the given password and salt …

def hash_seeded(password: str, seed: str) ‑> str

Inherited from: Type5.hash_seeded

Calculates a hash with the given seed used for generating a appropriate salt …

class Type6

SHA2 512 bit based hash for Juniper JunOS.

Specification: https://akkadia.org/drepper/SHA-crypt.txt Details: - 5000 rounds are used - the round specifier is omitted.

Expand source code
class Type6(BaseHash):
    """
    SHA2 512 bit based hash for Juniper JunOS.

    Specification: https://akkadia.org/drepper/SHA-crypt.txt
    Details:
    - 5000 rounds are used
    - the round specifier is omitted.
    """

    salt_length = 8
    salt_chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

    @staticmethod
    def hash_salted(password: str, salt: str) -> str:
        return sha512_crypt.using(salt=salt, rounds=5000).hash(password)

Ancestors

Class variables

var salt_chars : str

Inherited from: BaseHash.salt_chars

A string containing all characters that can be used in a salt.

var salt_length : int

Inherited from: BaseHash.salt_length

The length of the salt.

Static methods

def hash(password: str) ‑> str

Inherited from: BaseHash.hash

Calculates the hash …

def hash_salted(password: str, salt: str) ‑> str

Inherited from: BaseHash.hash_salted

calculates a hash from a fiven password and salt. If the salt is invalid for the hash, the behaviour is undefined. Prefer the hash and hash_seeded

Expand source code
@staticmethod
def hash_salted(password: str, salt: str) -> str:
    return sha512_crypt.using(salt=salt, rounds=5000).hash(password)
def hash_seeded(password: str, seed: str) ‑> str

Inherited from: BaseHash.hash_seeded

Calculates a hash with the given seed used for generating a appropriate 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

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)