Cisco type 7 password encryption / decryption
The snippet can be accessed without any authentication.
Authored by
Martin Pépin
Edited
cisco7.py 3.87 KiB
import argparse
import random
import sys
from typing import NamedTuple
# The key used in all cisco's devices is publicly known
KEY = b"dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87"
class Cisco7Error(Exception):
pass
def encrypt(secret: bytes, key: bytes, force_init: int | None = None) -> str:
"""Encrypt a password using Cisco's type 7 algorithm."""
# Start with a random offset if it is not forced
if force_init is not None:
if force_init < 0 or force_init >= len(key):
raise Cisco7Error("Invalid initial offset")
offset = force_init
else:
offset = random.randint(0, min(len(key) - 1, 255))
# Store the initial offset
initial_offset = format(offset, "02d")
# Encrypt
ciphertext = []
for byte in secret:
ciphertext.append(byte ^ key[offset])
offset = (offset + 1) % len(key)
return initial_offset + bytes(ciphertext).hex()
def decrypt(ciphertext: str, key: bytes) -> bytes:
"""Decrypt Cisco type 7 passwords"""
# Get the initial offset from the first two characters
if len(ciphertext) < 2:
raise Cisco7Error("Invalid Cisco type 7 password: too short")
offset = int(ciphertext[:2])
if offset >= len(key):
raise Cisco7Error("Invalid initial offset in Cisco type 7 password")
# Decrypt the password
secret = []
for byte in bytes.fromhex(ciphertext[2:]):
secret.append(byte ^ key[offset])
offset = (offset + 1) % len(key)
return bytes(secret)
Args = NamedTuple(
"Args",
encrypt=str | None,
decrypt=str | None,
key=str | None,
)
def parse_args() -> Args:
parser = argparse.ArgumentParser(
description="Cisco type 7 password encryption/decryption",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""note:
Encryption is non-deterministic.
examples:
%(prog)s -e 'My Sup3r S3cr3t P4ssw0rd'
%(prog)s -d '07362E590E1B1C041B1E124C0A2F2E206832752E1A01134D'""",
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-e",
"--encrypt",
nargs=1,
metavar="SECRET",
action="store",
)
group.add_argument(
"-d",
"--decrypt",
nargs=1,
metavar="CIPHERTEXT",
action="store",
)
parser.add_argument(
"-k",
"--key",
nargs=1,
help="use this encryption/decryption key (in hex format), "
"defaults to the publicly known Cisco key",
)
args = parser.parse_args()
return Args(
encrypt=args.encrypt[0] if args.encrypt else None,
decrypt=args.decrypt[0] if args.decrypt else None,
key=args.key[0] if args.key else None,
)
def warn(msg: str) -> None:
print(f"[WARNING] {msg}", file=sys.stderr)
def error(msg: str, rc: int) -> None:
print(f"[ERROR] {msg}", file=sys.stderr)
exit(rc)
def interpret_argument(arg: str) -> str:
# If arg is -, read from stdin
if arg == "-":
arg = sys.stdin.readline()
# Remove trailing newline if present
if arg and arg[-1] == "\n":
arg = arg[:-1]
return arg
if __name__ == "__main__":
args = parse_args()
key = args.key or KEY
if args.encrypt:
secret = interpret_argument(args.encrypt)
# At the theoretical level, nothing prevents us from accepting
# arbitrary utf-8 strings as passwords.
# But I'm not sure IOS handles non-ascii characters.
if not secret.isascii():
warn("Secret contains non-ascii characters")
try:
print(encrypt(secret.encode("utf-8"), key))
except Cisco7Error as exn:
error(exn.args[0], 1)
else:
ciphertext = interpret_argument(args.decrypt)
try:
print(decrypt(ciphertext, key).decode("utf-8"))
except Cisco7Error as exn:
error(exn.args[0], 1)
Please register or sign in to comment