Skip to content
Snippets Groups Projects

Cisco type 7 password encryption / decryption

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    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)
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please register or to comment