From efbe668c53504ed19730b1faa1a7202203ad58c7 Mon Sep 17 00:00:00 2001 From: Johan van den Dorpe Date: Thu, 2 Mar 2023 11:13:09 +0000 Subject: [PATCH 1/2] Add functions for encrypting and decrypting junipter $9$ type passwords --- docs/user/include_jinja_list.md | 2 + netutils/password.py | 110 ++++++++++++++++++++++++++++++++ netutils/utils.py | 2 + tests/unit/test_password.py | 28 ++++++++ 4 files changed, 142 insertions(+) diff --git a/docs/user/include_jinja_list.md b/docs/user/include_jinja_list.md index 219e04db..d4627da2 100644 --- a/docs/user/include_jinja_list.md +++ b/docs/user/include_jinja_list.md @@ -53,7 +53,9 @@ | mac_type | netutils.mac.mac_type | | compare_type5 | netutils.password.compare_type5 | | compare_type7 | netutils.password.compare_type7 | +| decrypt_juniper | netutils.password.decrypt_juniper | | decrypt_type7 | netutils.password.decrypt_type7 | +| encrypt_juniper | netutils.password.encrypt_juniper | | encrypt_type5 | netutils.password.encrypt_type5 | | encrypt_type7 | netutils.password.encrypt_type7 | | get_hash_salt | netutils.password.get_hash_salt | diff --git a/netutils/password.py b/netutils/password.py index 282b215f..3f66f407 100644 --- a/netutils/password.py +++ b/netutils/password.py @@ -71,6 +71,24 @@ "0x37", ] +JUNIPER_ENCODING = [ + [1, 4, 32], + [1, 16, 32], + [1, 8, 32], + [1, 64], + [1, 32], + [1, 4, 16, 128], + [1, 32, 64], +] + +JUNIPER_KEYS = ["QzF3n6/9CAtpu0O", "B1IREhcSyrleKvMW8LXx", "7N-dVbwsY2g4oaJZGUDj", "iHkq.mPf5T"] +JUNIPER_KEYS_STRING = "".join(JUNIPER_KEYS) +JUNIPER_KEYS_LENGTH = len(JUNIPER_KEYS_STRING) +JUNIPER_CHARACTER_KEYS = dict() +for idx, key in enumerate(JUNIPER_KEYS): + for character in key: + JUNIPER_CHARACTER_KEYS[character] = 3 - idx + def _fail_on_mac(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: """There is an issue with Macintosh for encryption.""" @@ -252,3 +270,95 @@ def get_hash_salt(encrypted_password: str) -> str: if len(split_password) != 4: raise ValueError(f"Could not parse salt out password correctly from {encrypted_password}") return split_password[2] + + +def decrypt_juniper(encrypted_password: str) -> str: + """Given an encrypted Junos $9$ type password, decrypt it. + + Args: + encrypted_password: A password that has been encrypted, and will be decrypted. + + Returns: + The unencrypted_password password. + + Examples: + >>> from netutils.password import decrypt_juniper + >>> decrypt_juniper("$9$7YdwgGDkTz6oJz69A1INdb") + 'juniper' + >>> + """ + # Strip $9$ from start of string + password_characters = encrypted_password.split("$9$", 1)[1] + + # Get first character and toss extra characters + first_character = password_characters[0] + stripped_password_characters = password_characters[JUNIPER_CHARACTER_KEYS[first_character] + 1 :] + + previous_char = first_character + decrypted_password = "" # nosec + while stripped_password_characters: + # Get encoding modulus + decode = JUNIPER_ENCODING[len(decrypted_password) % len(JUNIPER_ENCODING)] + + # Get nibble we will decode + nibble = stripped_password_characters[0 : len(decode)] + stripped_password_characters = stripped_password_characters[len(decode) :] + + # Decode value for nibble and convert to character, append to decryped password + value = 0 + for idx, char in enumerate(nibble): + gap = ( + (JUNIPER_KEYS_STRING.index(char) - JUNIPER_KEYS_STRING.index(previous_char)) % JUNIPER_KEYS_LENGTH + ) - 1 + value += gap * decode[idx] + previous_char = char + decrypted_password += chr(value) + + return decrypted_password + + +def encrypt_juniper(unencrypted_password: str, salt: t.Optional[int] = None) -> str: + """Given an unencrypted password, encrypt to Juniper $9$ type password. + + Args: + unencrypted_password: A password that has not been encrypted, and will be compared against. + salt: A integer that can be set by the operator. Defaults to random generated one. + + Returns: + The encrypted password. + + Examples: + >>> from netutils.password import encrypt_juniper + >>> encrypt_juniper("juniper", 35) # doctest: +SKIP + '$9$7YdwgGDkTz6oJz69A1INdb' + >>> + """ + if not salt: + salt = random.randint(0, JUNIPER_KEYS_LENGTH) - 1 # nosec + + # Use salt to generate start of encrypted password + first_character = JUNIPER_KEYS_STRING[salt] + random_chars = "".join( + [ + JUNIPER_KEYS_STRING[random.randint(0, JUNIPER_KEYS_LENGTH) - 1] # nosec + for x in range(0, JUNIPER_CHARACTER_KEYS[first_character]) + ] + ) + encrypted_password = "$9$" + first_character + random_chars + + previous_character = first_character + for idx, char in enumerate(unencrypted_password): + encode = JUNIPER_ENCODING[idx % len(JUNIPER_ENCODING)][::-1] # Get encoding modulus in reverse order + char_ord = ord(char) + gaps: t.List[int] = [] + for modulus in encode: + gaps = [int(char_ord / modulus)] + gaps + char_ord %= modulus + + for gap in gaps: + gap += JUNIPER_KEYS_STRING.index(previous_character) + 1 + new_character = JUNIPER_KEYS_STRING[gap % JUNIPER_KEYS_LENGTH] + previous_character = new_character + encrypted_password += new_character + + return encrypted_password diff --git a/netutils/utils.py b/netutils/utils.py index ff52ac7a..aa28155f 100644 --- a/netutils/utils.py +++ b/netutils/utils.py @@ -58,6 +58,8 @@ "encrypt_type5": "password.encrypt_type5", "encrypt_type7": "password.encrypt_type7", "get_hash_salt": "password.get_hash_salt", + "encrypt_juniper": "password.encrypt_juniper", + "decrypt_juniper": "password.decrypt_juniper", "tcp_ping": "ping.tcp_ping", "longest_prefix_match": "route.longest_prefix_match", "vlanlist_to_config": "vlan.vlanlist_to_config", diff --git a/tests/unit/test_password.py b/tests/unit/test_password.py index 6ed43750..320c7f20 100644 --- a/tests/unit/test_password.py +++ b/tests/unit/test_password.py @@ -69,6 +69,21 @@ }, ] +ENCRYPT_JUNIPER = [ + { + "sent": {"unencrypted_password": "juniper", "salt": 35}, + "received_one": "$9$7", + "received_two": "gGDkTz6oJz69A1INdb", + }, +] + +DECRYPT_JUNIPER = [ + { + "sent": {"encrypted_password": "$9$7YdwgGDkTz6oJz69A1INdb"}, + "received": "juniper", + } +] + @pytest.mark.parametrize("data", COMPARE_TYPE5) def test_compare_type5(data): @@ -98,3 +113,16 @@ def test_encrypt_type7(data): @pytest.mark.parametrize("data", GET_HASH_SALT) def test_get_hash_salt(data): assert password.get_hash_salt(**data["sent"]) == data["received"] + + +@pytest.mark.parametrize("data", ENCRYPT_JUNIPER) +def test_encrypt_juniper(data): + # Passwords include random padding, check only the non random sections + decrypted_password = password.encrypt_juniper(**data["sent"]) + assert decrypted_password[0:4] == data["received_one"] + assert decrypted_password[7:] == data["received_two"] + + +@pytest.mark.parametrize("data", DECRYPT_JUNIPER) +def test_decrypt_juniper(data): + assert password.decrypt_juniper(**data["sent"]) == data["received"] From ba01affb450c84a88e7fee00a5c2725f033e1f05 Mon Sep 17 00:00:00 2001 From: flat-data Date: Thu, 9 Mar 2023 08:02:30 +0000 Subject: [PATCH 2/2] Flat: latest data (2023-03-09T08:02:30.143Z) { "date": "2023-03-09T08:02:30.143Z", "files": [ { "name": "netutils/data_files/protocol_mappings.py", "deltaBytes": 190, "source": "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv" } ] } --- netutils/data_files/protocol_mappings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/netutils/data_files/protocol_mappings.py b/netutils/data_files/protocol_mappings.py index f8d27f65..13f990e8 100644 --- a/netutils/data_files/protocol_mappings.py +++ b/netutils/data_files/protocol_mappings.py @@ -719,6 +719,8 @@ "xact-backup": {"port_number": 911, "protocols": ["tcp", "udp"]}, "apex-mesh": {"port_number": 912, "protocols": ["tcp", "udp"]}, "apex-edge": {"port_number": 913, "protocols": ["tcp", "udp"]}, + "rift-lies": {"port_number": 914, "protocols": ["udp"]}, + "rift-ties": {"port_number": 915, "protocols": ["udp"]}, "rndc": {"port_number": 953, "protocols": ["tcp"]}, "ftps-data": {"port_number": 989, "protocols": ["tcp", "udp"]}, "ftps": {"port_number": 990, "protocols": ["tcp", "udp"]}, @@ -2005,6 +2007,7 @@ "pcc-mfp": {"port_number": 2256, "protocols": ["tcp", "udp"]}, "simple-tx-rx": {"port_number": 2257, "protocols": ["tcp", "udp"]}, "rcts": {"port_number": 2258, "protocols": ["tcp", "udp"]}, + "bid-serv": {"port_number": 2259, "protocols": ["tcp", "udp"]}, "apc-2260": {"port_number": 2260, "protocols": ["tcp", "udp"]}, "comotionmaster": {"port_number": 2261, "protocols": ["tcp", "udp"]}, "comotionback": {"port_number": 2262, "protocols": ["tcp", "udp"]},