269 lines
8.4 KiB
Python
269 lines
8.4 KiB
Python
"""
|
|
██████╗██╗██████╗ ██╗ ██╗███████╗██╗ ██╗
|
|
██╔════╝██║██╔══██╗██║ ██║██╔════╝╚██╗ ██╔╝
|
|
██║ ██║██████╔╝███████║█████╗ ╚████╔╝
|
|
██║ ██║██╔═══╝ ██╔══██║██╔══╝ ╚██╔╝
|
|
╚██████╗██║██║ ██║ ██║███████╗ ██║
|
|
https://github.com/Ciphey
|
|
https://github.com/Ciphey/Ciphey/wiki
|
|
|
|
The cycle goes:
|
|
main -> argparsing (if needed) -> call_encryption -> new Ciphey object -> decrypt() -> produceProbTable ->
|
|
one_level_of_decryption -> decrypt_normal
|
|
"""
|
|
import os
|
|
import warnings
|
|
from typing import Any, Optional, Union
|
|
|
|
import click
|
|
from appdirs import AppDirs
|
|
import logging
|
|
from rich.logging import RichHandler
|
|
from rich.console import Console
|
|
|
|
from . import iface
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
console = Console()
|
|
|
|
|
|
def decrypt(config: iface.Config, ctext: Any) -> Union[str, bytes]:
|
|
"""A simple alias for searching a ctext and makes the answer pretty"""
|
|
res: Optional[iface.SearchResult] = config.objs["searcher"].search(ctext)
|
|
if res is None:
|
|
return "Failed to crack"
|
|
if config.verbosity < 0:
|
|
return res.path[-1].result.value
|
|
else:
|
|
return iface.pretty_search_results(res)
|
|
|
|
|
|
def get_name(ctx, param, value):
|
|
# reads from stdin if value was not supplied
|
|
if not value and not click.get_text_stream("stdin").isatty():
|
|
click.get_text_stream("stdin").read().strip()
|
|
return click.get_text_stream("stdin").read().strip()
|
|
else:
|
|
return value
|
|
|
|
|
|
def print_help(ctx):
|
|
# prints help menu
|
|
# if no arguments are passed
|
|
click.echo(ctx.get_help())
|
|
ctx.exit()
|
|
|
|
|
|
@click.command()
|
|
@click.option(
|
|
"-t",
|
|
"--text",
|
|
help="The ciphertext you want to decrypt.",
|
|
type=str,
|
|
)
|
|
@click.option(
|
|
"-q", "--quiet", help="Decrease verbosity", type=int, count=True, default=None
|
|
)
|
|
@click.option(
|
|
"-g",
|
|
"--greppable",
|
|
help="Only print the answer (useful for grep)",
|
|
type=bool,
|
|
is_flag=True,
|
|
default=None,
|
|
)
|
|
@click.option("-v", "--verbose", count=True, type=int)
|
|
@click.option("-C", "--checker", help="Use the given checker", default=None)
|
|
@click.option(
|
|
"-c",
|
|
"--config",
|
|
help="Uses the given config file. Defaults to appdirs.user_config_dir('ciphey', 'ciphey')/'config.yml'",
|
|
)
|
|
@click.option("-w", "--wordlist", help="Uses the given wordlist")
|
|
@click.option(
|
|
"-p",
|
|
"--param",
|
|
help="Passes a parameter to the language checker",
|
|
multiple=True,
|
|
)
|
|
@click.option(
|
|
"-l",
|
|
"--list-params",
|
|
help="List the parameters of the selected module",
|
|
type=bool,
|
|
)
|
|
@click.option(
|
|
"--searcher",
|
|
help="Select the searching algorithm to use",
|
|
)
|
|
# HARLAN TODO XXX
|
|
# I switched this to a boolean flag system
|
|
# https://click.palletsprojects.com/en/7.x/options/#boolean-flags
|
|
# True for bytes input, False for str
|
|
@click.option(
|
|
"-b",
|
|
"--bytes",
|
|
help="Forces ciphey to use binary mode for the input",
|
|
is_flag=True,
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"--default-dist",
|
|
help="Sets the default character/byte distribution",
|
|
type=str,
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"-m",
|
|
"--module",
|
|
help="Adds a module from the given path",
|
|
type=click.Path(),
|
|
multiple=True,
|
|
)
|
|
@click.option(
|
|
"-A",
|
|
"--appdirs",
|
|
help="Print the location of where Ciphey wants the settings file to be",
|
|
type=bool,
|
|
is_flag=True,
|
|
)
|
|
@click.option("-f", "--file", type=click.File("rb"), required=False)
|
|
@click.argument("text_stdin", callback=get_name, required=False)
|
|
def main(**kwargs):
|
|
"""Ciphey - Automated Decryption Tool
|
|
|
|
Documentation:
|
|
https://github.com/Ciphey/Ciphey/wiki\n
|
|
Discord (support here, we're online most of the day):
|
|
http://discord.skerritt.blog\n
|
|
GitHub:
|
|
https://github.com/ciphey/ciphey\n
|
|
|
|
Ciphey is an automated decryption tool using smart artificial intelligence and natural language processing. Input encrypted text, get the decrypted text back.
|
|
|
|
Examples:\n
|
|
Basic Usage: ciphey -t "aGVsbG8gbXkgbmFtZSBpcyBiZWU="
|
|
|
|
"""
|
|
|
|
"""Function to deal with arguments. Either calls with args or not. Makes Pytest work.
|
|
|
|
It gets the arguments in the function definition using locals()
|
|
if withArgs is True, that means this is being called with command line args
|
|
so go to arg_parsing() to get those args
|
|
we then update locals() with the new command line args and remove "withArgs"
|
|
This function then calls call_encryption(**result) which passes our dict of args
|
|
to the function as its own arguments using dict unpacking.
|
|
Returns:
|
|
The output of the decryption.
|
|
"""
|
|
|
|
# if user wants to know where appdirs is
|
|
# print and exit
|
|
if "appdirs" in kwargs and kwargs["appdirs"]:
|
|
dirs = AppDirs("Ciphey", "Ciphey")
|
|
path_to_config = dirs.user_config_dir
|
|
print(
|
|
f"The settings.yml file should be at {os.path.join(path_to_config, 'settings.yml')}"
|
|
)
|
|
return None
|
|
|
|
# Now we create the config object
|
|
config = iface.Config()
|
|
|
|
# Load the settings file into the config
|
|
load_msg: str
|
|
cfg_arg = kwargs["config"]
|
|
if cfg_arg is None:
|
|
# Make sure that the config dir actually exists
|
|
os.makedirs(iface.Config.get_default_dir(), exist_ok=True)
|
|
config.load_file(create=True)
|
|
load_msg = f"Opened config file at {os.path.join(iface.Config.get_default_dir(), 'config.yml')}"
|
|
else:
|
|
config.load_file(cfg_arg)
|
|
load_msg = f"Opened config file at {cfg_arg}"
|
|
|
|
# Load the verbosity, so that we can start logging
|
|
verbosity = kwargs["verbose"]
|
|
quiet = kwargs["quiet"]
|
|
if verbosity is None:
|
|
if quiet is not None:
|
|
verbosity = -quiet
|
|
elif quiet is not None:
|
|
verbosity -= quiet
|
|
if kwargs["greppable"] is not None:
|
|
verbosity -= 999
|
|
# Use the existing value as a base
|
|
config.verbosity += verbosity
|
|
config.update_log_level(config.verbosity)
|
|
logging.info(load_msg)
|
|
logging.debug(f"Got cmdline args {kwargs}")
|
|
|
|
# Now we load the modules
|
|
module_arg = kwargs["module"]
|
|
if module_arg is not None:
|
|
config.modules += list(module_arg)
|
|
|
|
# We need to load formats BEFORE we instantiate objects
|
|
if kwargs["bytes"] is not None:
|
|
config.update_format("bytes")
|
|
|
|
# Next, load the objects
|
|
params = kwargs["param"]
|
|
if params is not None:
|
|
for i in params:
|
|
key, value = i.split("=", 1)
|
|
parent, name = key.split(".", 1)
|
|
config.update_param(parent, name, value)
|
|
config.update("checker", kwargs["checker"])
|
|
config.update("searcher", kwargs["searcher"])
|
|
config.update("default_dist", kwargs["default_dist"])
|
|
|
|
config.complete_config()
|
|
|
|
logging.debug(f"Command line opts: {kwargs}")
|
|
logging.debug(f"Config finalised: {config}")
|
|
|
|
# Finally, we load the plaintext
|
|
if kwargs["text"] is None:
|
|
if kwargs["file"] is not None:
|
|
kwargs["text"] = kwargs["file"].read()
|
|
elif kwargs["text_stdin"] is not None:
|
|
kwargs["text"] = kwargs["text_stdin"]
|
|
else:
|
|
# else print help menu
|
|
print("[bold red]Error. No inputs were given to Ciphey. [bold red]")
|
|
|
|
@click.pass_context
|
|
def all_procedure(ctx):
|
|
print_help(ctx)
|
|
|
|
all_procedure()
|
|
|
|
return None
|
|
|
|
if issubclass(config.objs["format"], type(kwargs["text"])):
|
|
pass
|
|
elif config.objs["format"] == str and isinstance(kwargs["text"], bytes):
|
|
kwargs["text"] = kwargs["text"].decode("utf-8")
|
|
elif config.objs["format"] == bytes and isinstance(kwargs["text"], str):
|
|
kwargs["text"] = kwargs["text"].encode("utf-8")
|
|
else:
|
|
raise TypeError(f"Cannot load type {config.format} from {type(kwargs['text'])}")
|
|
|
|
result: Optional[str]
|
|
|
|
# if debug or quiet mode is on, run without spinner
|
|
if config.verbosity != 0:
|
|
result = decrypt(config, kwargs["text"])
|
|
else:
|
|
# else, run with spinner if verbosity is 0
|
|
with console.status("[bold green]Thinking...", spinner="moon") as status:
|
|
config.set_spinner(status)
|
|
result = decrypt(config, kwargs["text"])
|
|
if result is None:
|
|
result = "Could not find any solutions."
|
|
|
|
console.print(result)
|