From 73b12b4f6be76335e51c450f8857e98fc53c5173 Mon Sep 17 00:00:00 2001 From: urbanware-org Date: Fri, 16 Mar 2018 20:04:21 +0100 Subject: [PATCH] Initial upload --- LICENSE.md | 27 ++++ README.md | 67 ++++++++ python2/changelog.txt | 57 +++++++ python2/core/__init__.py | 0 python2/core/clap.py | 216 ++++++++++++++++++++++++++ python2/core/common.py | 39 +++++ python2/core/cracker.py | 191 +++++++++++++++++++++++ python2/core/paval.py | 203 ++++++++++++++++++++++++ python2/core/rot128.py | 143 +++++++++++++++++ python2/core/rot13.py | 163 +++++++++++++++++++ python2/core/rot47.py | 149 ++++++++++++++++++ python2/docs/usage_rotate-cracker.txt | 83 ++++++++++ python2/docs/usage_rotate-rot128.txt | 56 +++++++ python2/docs/usage_rotate-rot13.txt | 74 +++++++++ python2/docs/usage_rotate-rot47.txt | 56 +++++++ python2/license.txt | 27 ++++ python2/readme.txt | 49 ++++++ python2/requirements.txt | 17 ++ python2/rotate-cracker.py | 77 +++++++++ python2/rotate-rot128.py | 85 ++++++++++ python2/rotate-rot13.py | 85 ++++++++++ python2/rotate-rot47.py | 85 ++++++++++ python3/changelog.txt | 57 +++++++ python3/core/__init__.py | 0 python3/core/clap.py | 216 ++++++++++++++++++++++++++ python3/core/common.py | 39 +++++ python3/core/cracker.py | 191 +++++++++++++++++++++++ python3/core/paval.py | 203 ++++++++++++++++++++++++ python3/core/rot128.py | 143 +++++++++++++++++ python3/core/rot13.py | 163 +++++++++++++++++++ python3/core/rot47.py | 149 ++++++++++++++++++ python3/docs/usage_rotate-cracker.txt | 83 ++++++++++ python3/docs/usage_rotate-rot128.txt | 56 +++++++ python3/docs/usage_rotate-rot13.txt | 74 +++++++++ python3/docs/usage_rotate-rot47.txt | 56 +++++++ python3/license.txt | 33 ++++ python3/readme.txt | 49 ++++++ python3/requirements.txt | 17 ++ python3/rotate-cracker.py | 77 +++++++++ python3/rotate-rot128.py | 85 ++++++++++ python3/rotate-rot13.py | 85 ++++++++++ python3/rotate-rot47.py | 85 ++++++++++ rotate.png | Bin 0 -> 26077 bytes 43 files changed, 3810 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 python2/changelog.txt create mode 100755 python2/core/__init__.py create mode 100755 python2/core/clap.py create mode 100755 python2/core/common.py create mode 100755 python2/core/cracker.py create mode 100755 python2/core/paval.py create mode 100755 python2/core/rot128.py create mode 100755 python2/core/rot13.py create mode 100755 python2/core/rot47.py create mode 100644 python2/docs/usage_rotate-cracker.txt create mode 100644 python2/docs/usage_rotate-rot128.txt create mode 100644 python2/docs/usage_rotate-rot13.txt create mode 100644 python2/docs/usage_rotate-rot47.txt create mode 100644 python2/license.txt create mode 100644 python2/readme.txt create mode 100644 python2/requirements.txt create mode 100755 python2/rotate-cracker.py create mode 100755 python2/rotate-rot128.py create mode 100755 python2/rotate-rot13.py create mode 100755 python2/rotate-rot47.py create mode 100644 python3/changelog.txt create mode 100755 python3/core/__init__.py create mode 100755 python3/core/clap.py create mode 100755 python3/core/common.py create mode 100755 python3/core/cracker.py create mode 100755 python3/core/paval.py create mode 100755 python3/core/rot128.py create mode 100755 python3/core/rot13.py create mode 100755 python3/core/rot47.py create mode 100644 python3/docs/usage_rotate-cracker.txt create mode 100644 python3/docs/usage_rotate-rot128.txt create mode 100644 python3/docs/usage_rotate-rot13.txt create mode 100644 python3/docs/usage_rotate-rot47.txt create mode 100644 python3/license.txt create mode 100644 python3/readme.txt create mode 100644 python3/requirements.txt create mode 100755 python3/rotate-cracker.py create mode 100755 python3/rotate-rot128.py create mode 100755 python3/rotate-rot13.py create mode 100755 python3/rotate-rot47.py create mode 100644 rotate.png diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f830b12 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,27 @@ +# License + +***ROTate*** - Encryption tool based on the ROT cipher + +Copyright © 2018 by Ralf Kilian + +Distributed under the *MIT License*: + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/README.md b/README.md new file mode 100644 index 0000000..cee5e83 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# *ROTate* ROTate logo + +**Table of contents** + +* [Definition](#definition) +* [Details](#details) +* [Components](#components) +* [Requirements](#requirements) +* [Documentation](#documentation) +* [Useless facts](#useless-facts) + +---- + +## Definition + +The *ROTate* project is a collection of scripts to encrypt and decrypt files using various *ROT* cipher methods. + +[Top](#) + +## Details + +The project allows to encrypt and decrypt files using various ROT cipher methods such as *ROT13*, *ROT47*, *ROT128* as well as with enhanced variants based on each character set of these methods. + +It also comes with a tool to find out which variant and value has been used to encrypt a file. + +Due to the fact, that data encrypted with *ROT* methods can be cracked quite easily, they are **not** suitable for encrypting sensible data. + +[Top](#) + +## Components + +### *ROTate* variants + +There are three components to encrypt and decrypt files using the *ROT13*, *ROT47* and *ROT128* cipher method. + +They also allow using a user-defined rotation value (based on the character set of that cipher method) instead of the default rotation value. + +### *ROTate Cracker* + +As already mentioned above, data encrypted with *ROT* methods can be cracked quite easily. This brute force cracker helps to determine which *ROT* variant and rotation value has been used to encrypt a file or string by simply trying all supported variants with all rotation values available. + +[Top](#) + +## Requirements + +In order to use *ROTate*, the *Python* framework must be installed on the system. + +Depending on which version of the framework you are using: + +* *Python* 2.x (version 2.7 or higher is recommended, may also work with earlier versions) +* *Python* 3.x (version 3.2 or higher is recommended, may also work with earlier versions) + +[Top](#) + +## Documentation + +In the corresponding `docs` sub-directories, there are plain text files containing a detailed documentation for each component with further information and usage examples. + +[Top](#) + +## Useless facts + +* The name *ROTate* stands for ***ROT*** *with* ***A**dditional* ***T**ools* *and* ***E**nhancements*. +* The first version uploaded on *GitHub* was *ROTate* 3.0.6 built on March 13th, 2018. +* Before uploading, the project has neither been changed nor even touched for more than three years. + +[Top](#) diff --git a/python2/changelog.txt b/python2/changelog.txt new file mode 100644 index 0000000..c5739f6 --- /dev/null +++ b/python2/changelog.txt @@ -0,0 +1,57 @@ + +CHANGELOG (ROTate) + + Version 3.0.6 (2018-03-13) + + + Added new versions of the Clap and PaVal core modules (replaced the + existing ones). + + * Revised (refurbished) all components of the project in general + (neglibible changes). + + # Fixed the wildcard bug (certain characters inside the strings to + encrypt and decrypt and will no longer be interpreted as wildcards). + + Version 3.0.5 (2015-01-03) + + * Revised some code inside the ROTate Cracker core module (negligible + changes). + + - Removed unnecessary module imports from the core modules. + + Version 3.0.4 (2014-04-03) + + + Added an optional command-line argument to the ROTate Cracker script + to write the integer ordinals of the decrypted string into the + output file. + + Version 3.0.3 (2014-03-21) + + * Revised (reduced) some code inside the ROTate Cracker core module. + * Revised the ROTate Cracker output file (non-printable characters are + now replaced either with whitespaces or a corresponding notice). + + # Fixed the attribute error inside the ROTate Cracker core module when + reading out the major version of the Python framework using Python + version 2.6 or below. + + Version 3.0.2 (2014-03-14) + + + Added an error handler to the ROTate scripts in case no command-line + argument parser can be initialized. + + * Revised (reduced) some code inside the ROTate Cracker core module. + * Revised the transform methods inside the ROT13, ROT47 and ROT128 + core module (reduced some code for increased readability). + + Version 3.0.1 (2014-03-07) + + * Revised the description of some command-line arguments inside all + ROTate scripts. + * Revised the header of the ROTate Cracker output file (negligible + text changes). + + Version 3.0.0 (2014-02-11) + + * First official release of this major version. + diff --git a/python2/core/__init__.py b/python2/core/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/python2/core/clap.py b/python2/core/clap.py new file mode 100755 index 0000000..03305c0 --- /dev/null +++ b/python2/core/clap.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# Clap - Command-line argument parser module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/clap +# ============================================================================ + +__version__ = "1.1.10" + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +class Parser(object): + """ + Project independent command-line argument parser class. + """ + __arg_grp_opt = None + __arg_grp_req = None + __arg_parser = None + __is_argparser = False + __conflict_handler = "resolve" # used by OptionParser, only + + def __init__(self): + try: + from argparse import ArgumentParser + self.__arg_parser = ArgumentParser(add_help=False) + self.__arg_grp_req = \ + self.__arg_parser.add_argument_group("required arguments") + self.__arg_grp_opt = \ + self.__arg_parser.add_argument_group("optional arguments") + self.__is_argparser = True + return + except ImportError: + # Ignore the exception and proceed with the fallback + pass + + try: + from optparse import OptionParser + self.__arg_parser = \ + OptionParser(conflict_handler=self.__conflict_handler) + self.__arg_grp_req = \ + self.__arg_parser.add_option_group("Required arguments") + self.__arg_grp_opt = \ + self.__arg_parser.add_option_group("Optional arguments") + return + except ImportError: + # This should never happen + raise ImportError("Failed to initialize an argument parser.") + + def add_avalue(self, arg_short, arg_long, arg_help, arg_dest, arg_default, + arg_required): + """ + Add an argument that expects a single user-defined value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if arg_default is not None: + # Enclose the value with quotes in case it is not an integer + quotes = "'" + try: + arg_default = int(arg_default) + quotes = "" + except ValueError: + pass + + if arg_help.strip().endswith(")"): + arg_help = arg_help.rstrip(")") + arg_help += ", default is %s%s%s)" % \ + (quotes, str(arg_default), quotes) + else: + arg_help += " (default is %s%s%s)" % \ + (quotes, str(arg_default), quotes) + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + default=arg_default, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, default=arg_default, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + default=arg_default) + else: + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest, default=arg_default) + + def add_predef(self, arg_short, arg_long, arg_help, arg_dest, arg_choices, + arg_required): + """ + Add an argument that expects a certain predefined value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + choices=arg_choices, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, choices=arg_choices, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + choices=arg_choices) + else: + # The OptionParser does not print the values to choose from, + # so these have to be added manually to the description of + # the argument first + arg_help += " (choose from " + for item in arg_choices: + arg_help += "'%s', " % item + arg_help = arg_help.rstrip(", ") + ")" + + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest) + + def add_switch(self, arg_short, arg_long, arg_help, arg_dest, arg_store, + arg_required): + """ + Add an argument that does not expect anything, but returns a + boolean value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if arg_store: + arg_store = "store_true" + else: + arg_store = "store_false" + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + action=arg_store, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, action=arg_store, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + action=arg_store) + else: + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest, action=arg_store) + + def dependency(self, arg_name, arg_value, dependency): + """ + Check the dependency of a command-line argument. + """ + if dependency is not None: + if arg_value is None or str(arg_value) == "": + raise Exception("The '%s' argument depends on %s'." % + (arg_name, dependency)) + + def error(self, obj): + """ + Raise an error and cause the argument parser to print the error + message. + """ + if type(obj) == str: + obj = obj.strip() + + self.__arg_parser.error(obj) + + def parse_args(self): + """ + Parse and return the command-line arguments. + """ + if self.__is_argparser: + args = self.__arg_parser.parse_args() + else: + (args, values) = self.__arg_parser.parse_args() + return args + + def print_help(self): + """ + Print the usage, description, argument details and epilog. + """ + self.__arg_parser.print_help() + + def set_description(self, string): + """ + Set the description text. + """ + self.__arg_parser.description = string.strip() + + def set_epilog(self, string): + """ + Set the epilog text. + """ + self.__arg_parser.epilog = string.strip() + +# EOF diff --git a/python2/core/common.py b/python2/core/common.py new file mode 100755 index 0000000..4342ab4 --- /dev/null +++ b/python2/core/common.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Common core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import paval as pv + + +def get_file_size(file_path): + """ + Get the size of a file in bytes. + """ + pv.path(file_path, "", True, True) + + f = open(file_path, "rb") + f.seek(0, 2) + file_size = f.tell() + f.close() + + return int(file_size) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + +# EOF diff --git a/python2/core/cracker.py b/python2/core/cracker.py new file mode 100755 index 0000000..6fd937f --- /dev/null +++ b/python2/core/cracker.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Cracker core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import os +import paval as pv +import rot13 +import rot47 +import rot128 +import string as st +import sys + +from datetime import datetime as dt + + +def brute_force(string, file_output, method=None, int_ordinals=False, + non_printable=False, limit=True): + """ + Brute-force the given string to find out which ROT variant and + rotation value was used to encrypt. + """ + pv.path(file_output, "output", True, False) + pv.string(string, "string to decrypt", True) + file_output = os.path.abspath(file_output) + + if method is None: + method = "All" + else: + method = method.upper() + pv.compstr(method, "method", ["ROT13", "ROT47", "ROT128"]) + + list_values_rot13 = [] + list_values_rot47 = [] + list_values_rot128 = [] + timestamp = str(dt.now()) + output = "\r\n" + "=" * 78 + \ + "\r\nFile type: ROTate Cracker output file" \ + "\r\n" + "-" * 78 + \ + "\r\nOutput file name: " + file_output + \ + "\r\nString to decrypt: " + string + \ + "\r\nMethods used: " + method + \ + "\r\n" + "-" * 78 + \ + "\r\nTimestamp: " + timestamp[:-7] + \ + "\r\nROTate version: " + get_version() + \ + "\r\n" + "=" * 78 + "\r\n\r\n" + + if method == "All" or method == "ROT13": + list_values_rot13 = \ + __get_values_rot13(string, int_ordinals, non_printable, limit) + if method == "All" or method == "ROT47": + list_values_rot47 = \ + __get_values_rot47(string, int_ordinals, non_printable, limit) + if method == "All" or method == "ROT128": + list_values_rot128 = \ + __get_values_rot128(string, int_ordinals, non_printable, limit) + + if len(list_values_rot13) > 0: + output += "\r\n [ROT13]\r\n" + for value in list_values_rot13: + output += " - %s\r\n" % value + output += "\r\n" + if len(list_values_rot47) > 0: + output += "\r\n [ROT47]\r\n" + for value in list_values_rot47: + output += " - %s\r\n" % value + output += "\r\n" + if len(list_values_rot128) > 0: + output += "\r\n [ROT128]\r\n" + for value in list_values_rot128: + output += " - %s\r\n" % value + output += "\r\n" + fh_output = open(file_output, "wb") + + # Run the appropriate code for the Python framework used + if sys.version_info[0] == 2: + fh_output.write(output) + elif sys.version_info[0] > 2: + fh_output.write(output.encode(sys.getdefaultencoding())) + + fh_output.close() + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __get_int_ordinals(string): + """ + Return the integer ordinals of a string. + """ + output = "" + + for char in string: + output += str(ord(char)).rjust(3, " ") + ", " + output = output.rstrip(", ") + + return output + + +def __get_printable(string): + """ + Replace non-printable characters inside a string with whitespaces + """ + output = "" + + chars_remove = "\t\n\r\x0b\x0c" + for char in chars_remove: + string = string.replace(char, " ") + + for char in string: + if char not in st.printable: + output += " " + else: + output += char + + if output.strip() == "": + output = "(only non-printable characters or spaces)" + + return output + + +def __get_values_rot13(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT13 values for the given string. + """ + list_values = [] + for i in range(1, 26): + output = __prepare_line(rot13.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __get_values_rot47(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT47 values for the given string. + """ + list_values = [] + for i in range(1, 94): + output = __prepare_line(rot47.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __get_values_rot128(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT128 values for the given string. + """ + list_values = [] + for i in range(1, 256): + output = __prepare_line(rot128.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __prepare_line(line, int_ordinals, non_printable, limit): + """ + Prepare the line before it gets written into the file. + """ + if int_ordinals: + line = __get_int_ordinals(line) + else: + if not non_printable: + line = __get_printable(line) + + if limit: + if len(line) > 58: + line = line[:55] + "..." + + return line + +# EOF diff --git a/python2/core/paval.py b/python2/core/paval.py new file mode 100755 index 0000000..a2ed54a --- /dev/null +++ b/python2/core/paval.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# PaVal - Parameter validation module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/paval +# ============================================================================ + +__version__ = "1.2.7" + +import filecmp +import os + + +def compfile(path, name="", list_files=[]): + """ + Compare files to avoid that the same file is given multiple times or + in different ways (e. g. different name but same content). + """ + __string(path, "%s path" % name, True) + + if list_files is None: + list_files = [] + elif len(list_files) == 0: + __ex("File list is empty (no files to compare with).", True) + else: + for item in list_files: + if not type(item) == list: + __ex("Every list item must be a sub-list.", True) + if not len(item) == 2: + __ex("Every sub-list must contain two items.", True) + + path = os.path.abspath(path) + for item in list_files: + path_compare = os.path.abspath(str(item[0])) + name_compare = str(item[1]) + if path == path_compare: + __ex("The %s and the %s file path must not be identical." % + (name, name_compare), False) + if os.path.exists(path) and os.path.exists(path_compare): + if filecmp.cmp(path, path_compare, 0): + __ex("The %s and %s file content must not be identical." % + (name, name_compare), False) + + +def compstr(string, name="", list_strings=[]): + """ + Compare a string with a list of strings and check if it is an item of + that list. + """ + __string(string, name, False) + if len(list_strings) == 0: + __ex("No %s strings to compare with." % name, True) + if string not in list_strings: + __ex("The %s '%s' does not exist." % (name, string), False) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def intrange(value, name="", value_min=None, value_max=None, zero=False): + """ + Validate an integer range. + """ + value = __integer(value, "%s value" % name, False) + if value_min is not None: + value_min = __integer(value_min, "minimal %s value" % name, True) + intvalue(value_min, name, True, True, True) + if value_max is not None: + value_max = __integer(value_max, "maximal %s value" % name, True) + intvalue(value_max, name, True, True, True) + if not zero: + if value == 0: + __ex("The %s value must not be zero." % name, False) + if (value_min is not None) and (value_max is not None): + if value_min > value_max: + __ex("The maximal %s value must be greater than the minimal " + "value." % name, False) + if (value_min == value_max) and (not value == value_min): + __ex("The %s value can only be %s (depending on further range " + "further range arguments)." % (name, value_min), False) + if (value < value_min) or (value > value_max): + __ex("The %s value must be between %s and %s (depending on " + "further range arguments)." % (name, value_min, value_max), + False) + elif value_min is not None: + if value < value_min: + __ex("The %s value must not be less than %s." % (name, value_min), + False) + elif value_max is not None: + if value > value_max: + __ex("The %s value must not be greater than %s." % + (name, value_max), False) + + +def intvalue(value, name="", positive=True, zero=False, negative=False): + """ + Validate a single integer value. + """ + value = __integer(value, "%s value" % name, False) + if not positive: + if value > 0: + __ex("The %s value must not be positive." % name, False) + if not zero: + if value == 0: + __ex("The %s value must not be zero." % name, False) + if not negative: + if value < 0: + __ex("The %s value must not be negative." % name, False) + + +def path(path, name="", is_file=False, exists=False): + """ + Validate a path of a file or directory. + """ + string(path, "%s path" % name, False, None) + path = os.path.abspath(path) + + if is_file: + path_type = "file" + else: + path_type = "directory" + if exists: + if not os.path.exists(path): + __ex("The given %s %s does not exist." % (name, path_type), False) + if (is_file and not os.path.isfile(path)) or \ + (not is_file and not os.path.isdir(path)): + __ex("The given %s %s path is not a %s." % (name, path_type, + path_type), False) + else: + if os.path.exists(path): + __ex("The given %s %s path already exists." % (name, path_type), + False) + + +def string(string, name="", wildcards=False, invalid_chars=None): + """ + Validate a string. + """ + __string(string, name, False) + if invalid_chars is None: + invalid_chars = "" + if not wildcards: + if ("*" in string) or ("?" in string): + __ex("The %s must not contain wildcards." % name, False) + if len(invalid_chars) > 0: + for char in invalid_chars: + if char in string: + # Use single quotes by default or double quotes in case the + # single quotes are the invalid character + quotes = "'" + if char == quotes: + quotes = "\"" + + __ex("The %s contains at least one invalid character " + "(%s%s%s)." % (name, quotes, char, quotes), False) + + +def __ex(string, internal=False): + """ + Internal method to raise an exception. + """ + string = str(string).strip() + while (" " * 2) in string: + string = string.replace((" " * 2), " ") + if internal: + string = "PaVal: " + string + raise Exception(string) + + +def __integer(value, name="", internal=False): + """ + Internal method for basic integer validation. + """ + if value is None: + __ex("The %s is missing." % name, internal) + if value == "": + __ex("The %s must not be empty." % name, internal) + try: + value = int(value) + except ValueError: + __ex("The %s must be an integer." % name, internal) + return int(value) + + +def __string(string, name="", internal=False): + """ + Internal method for basic string validation. + """ + if string is None: + __ex("The %s is missing." % name, internal) + if string == "": + __ex("The %s must not be empty." % name, internal) + +# EOF diff --git a/python2/core/rot128.py b/python2/core/rot128.py new file mode 100755 index 0000000..b3a5265 --- /dev/null +++ b/python2/core/rot128.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT128 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import common +import os +import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=128): + """ + Encrypt a file using ROT128. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value): + """ + Encrypt a string using ROT128. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=128): + """ + Decrypt a file using ROT128. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value): + """ + Decrypt a string using ROT128. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if encrypt: + data_output.append(((byte + rot_value) % 256)) + else: + data_output.append(((byte - rot_value) % 256)) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if encrypt: + output += chr(((ord(char) + rot_value) % 256)) + else: + output += chr(((ord(char) + rot_value) % 256)) + + return output + +# EOF diff --git a/python2/core/rot13.py b/python2/core/rot13.py new file mode 100755 index 0000000..ac0120b --- /dev/null +++ b/python2/core/rot13.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT13 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import common +import os +import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=13): + """ + Encrypt a file using ROT13. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value=13): + """ + Encrypt a string using ROT13. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=13): + """ + Decrypt a file using ROT13. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value=13): + """ + Decrypt a string using ROT13. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if byte in range(65, 91): + # Uppercase letters + if encrypt: + data_output.append((((byte - 65) + rot_value) % 26) + 65) + else: + data_output.append((((byte - 65) - rot_value) % 26) + 65) + elif byte in range(97, 123): + # Lowercase letters + if encrypt: + data_output.append((((byte - 97) + rot_value) % 26) + 97) + else: + data_output.append((((byte - 97) - rot_value) % 26) + 97) + else: + data_output.append(byte) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if ord(char) in range(65, 91): + # Uppercase letters + if encrypt: + output += chr((((ord(char) - 65) + rot_value) % 26) + 65) + else: + output += chr((((ord(char) - 65) - rot_value) % 26) + 65) + elif ord(char) in range(97, 123): + # Lowercase letters + if encrypt: + output += chr((((ord(char) - 97) + rot_value) % 26) + 97) + else: + output += chr((((ord(char) - 97) - rot_value) % 26) + 97) + else: + output += char + + return output + +# EOF diff --git a/python2/core/rot47.py b/python2/core/rot47.py new file mode 100755 index 0000000..ee71aef --- /dev/null +++ b/python2/core/rot47.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT47 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import common +import os +import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=47): + """ + Encrypt a file using ROT47. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value=47): + """ + Encrypt a string using ROT47. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=47): + """ + Decrypt a file using ROT47. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value=47): + """ + Decrypt a string using ROT47. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if byte in range(32, 126): + if encrypt: + data_output.append((((byte - 32) + rot_value) % 94) + 32) + else: + data_output.append((((byte - 32) - rot_value) % 94) + 32) + else: + data_output.append(byte) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if ord(char) in range(32, 126): + if encrypt: + output += chr((((ord(char) - 32) + rot_value) % 94) + 32) + else: + output += chr((((ord(char) - 32) - rot_value) % 94) + 32) + else: + output += char + + return output + +# EOF diff --git a/python2/docs/usage_rotate-cracker.txt b/python2/docs/usage_rotate-cracker.txt new file mode 100644 index 0000000..60c548f --- /dev/null +++ b/python2/docs/usage_rotate-cracker.txt @@ -0,0 +1,83 @@ + +USAGE (rotate-cracker.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Crack a ROT encrypted file + + 1. Definition + + The ROTate Cracker script helps to determine which ROT variant and + rotation value was used to encrypt a file or string by trying all + supported ROT variants with all rotation values available (brute + force). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-cracker.py --help + + 3. Crack a ROT encrypted file + + For example, you have the ROT encrypted file "secret.txt" containing + the line + + Uryyb, Jbeyq! + + and want to decrypt it. So, to find out which ROT method and rotation + value was used to encrypt the file, a string from the file (e. g. a + word) is required. + + This example uses the string "Uryyb" and writes the decrypted values + into the file "/tmp/output.txt": + + $ ./rotate-cracker.py -s "Uryyb" -o /tmp/output.txt + + The output file shows that when using the ROT13 method, the string + "Uryyb" decrypted with value 13 returns the word "Hello": + + (...) + [ROT13] + - Value 0: Uryyb + - Value 1: Tqxxa + - Value 2: Spwwz + - Value 3: Rovvy + - Value 4: Qnuux + - Value 5: Pmttw + - Value 6: Olssv + - Value 7: Nkrru + - Value 8: Mjqqt + - Value 9: Lipps + - Value 10: Khoor + - Value 11: Jgnnq + - Value 12: Ifmmp + - Value 13: Hello + (...) + + So, the file or at least that string seems to be encrypted with that + method and value. Unfortunately, there is no analysis feature, which + means you will have to look at the returned value list to manually + check which entry makes most sense. + + If there are non-printable characters inside the string, they will be + replaced by whitespaces. If the string only contains of whitespaces, + a notice will be written to the output file. + diff --git a/python2/docs/usage_rotate-rot128.txt b/python2/docs/usage_rotate-rot128.txt new file mode 100644 index 0000000..53b835d --- /dev/null +++ b/python2/docs/usage_rotate-rot128.txt @@ -0,0 +1,56 @@ + +USAGE (rotate-rot128.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT128 script encrypts and decrypts a file using the ROT128 + method with the default rotation value or a user-defined one (using + the ROT128 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot128.py --help + + 3. Encrypt a file + + This works as described in section 3 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + + 4. Decrypt the file again + + This works as described in section 4 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + + 5. User-defined rotation values + + This works as described in section 5 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + diff --git a/python2/docs/usage_rotate-rot13.txt b/python2/docs/usage_rotate-rot13.txt new file mode 100644 index 0000000..918322b --- /dev/null +++ b/python2/docs/usage_rotate-rot13.txt @@ -0,0 +1,74 @@ + +USAGE (rotate-rot13.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT13 script encrypts and decrypts a file using the ROT13 + method with the default rotation value or a user-defined one (using + the ROT13 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot13.py --help + + 3. Encrypt a file + + For example, if you have the input file "hello.txt" containing the + line + + Hello, world! + + and want to encrypt it using ROT13 (with its default value) and write + the encrypted data into "secret.txt", the command line would look like + this: + + $ ./rotate-rot13.py -i hello.txt -o secret.txt -a encrypt + + The encrypted data inside "secret.txt" should look like this: + + Uryyb, Jbeyq! + + 4. Decrypt the file again + + To decrypt the file again and write the decrypted data into the file + "output.txt", type: + + $ ./rotate-rot13.py -i secret.txt -o output.txt -a decrypt + + 5. User-defined rotation values + + You can also encrypt the file using the ROT13 method, but with + rotation value 19 instead of the default value: + + $ ./rotate-rot13.py -i hello.txt -o secret.txt -a encrypt -v 19 + + The user-defined rotation value used to encrypt is also required to + properly decrypt the file again: + + $ ./rotate-rot13.py -i secret.txt -o output.txt -a decrypt -v 19 + diff --git a/python2/docs/usage_rotate-rot47.txt b/python2/docs/usage_rotate-rot47.txt new file mode 100644 index 0000000..1498be1 --- /dev/null +++ b/python2/docs/usage_rotate-rot47.txt @@ -0,0 +1,56 @@ + +USAGE (rotate-rot47.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT47 script encrypts and decrypts a file using the ROT47 + method with the default rotation value or a user-defined one (using + the ROT47 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot47.py --help + + 3. Encrypt a file + + This works as described in section 3 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + + 4. Decrypt the file again + + This works as described in section 4 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + + 5. User-defined rotation values + + This works as described in section 5 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + diff --git a/python2/license.txt b/python2/license.txt new file mode 100644 index 0000000..6f15220 --- /dev/null +++ b/python2/license.txt @@ -0,0 +1,27 @@ + +LICENSE (ROTate) + + ROTate - Encryption tool based on the ROT cipher + Copyright (C) 2018 by Ralf Kilian + + Distributed under the MIT License: + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/python2/readme.txt b/python2/readme.txt new file mode 100644 index 0000000..e643f41 --- /dev/null +++ b/python2/readme.txt @@ -0,0 +1,49 @@ + +README (ROTate) + + Project + + ROTate + Version 3.0.6 (based on Python framework 2.x) + Copyright (C) 2018 by Ralf Kilian + + Website: http://www.urbanware.org + GitHub: https://github.com/urbanware-org/rotate + + Definition + + The ROTate project is a collection of scripts to encrypt and decrypt + files using various ROT cipher methods. + + License + + This project is distributed under the MIT License. You should have + received a copy of the license along with this program (see the + 'license.txt' file). If not, you can find the license terms here: + + https://opensource.org/licenses/MIT + + Requirements + + The requirements for this project can be found inside the included + file 'requirements.txt'. + + In case this file is missing, you can also find the information on the + website of the project mentioned above. + + Usage + + For fundamental documentation as well as some usage examples for each + component of the project, you may have a look at the text files inside + the included 'docs' sub-directory. + + Legal information + + The project name is completely fictitious. Any correspondences with + existing websites, applications, companies and/or other projects are + purely coincidental. + + All trademarks belong to their respective owners. + + Errors and omissions excepted. + diff --git a/python2/requirements.txt b/python2/requirements.txt new file mode 100644 index 0000000..3d50015 --- /dev/null +++ b/python2/requirements.txt @@ -0,0 +1,17 @@ + +REQUIREMENTS (ROTate) + + Notice + + This version of ROTate was built for the Python 2.x framework. If you + need a version that works with Python 3.x, you may look here: + + http://www.urbanware.org/rotate.html + + General + + Software requirements: + + - Python 2.x (version 2.7 or higher is recommended, may also work + with earlier versions) + diff --git a/python2/rotate-cracker.py b/python2/rotate-cracker.py new file mode 100755 index 0000000..ba99fb6 --- /dev/null +++ b/python2/rotate-cracker.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Cracker script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import cracker + + try: + p = clap.Parser() + except Exception as e: + print "%s: error: %s" % (os.path.basename(sys.argv[0]), e) + sys.exit(1) + + p.set_description("Determine which ROT variant and rotation value was " + "used to encrypt a file or string by trying all " + "supported ROT variants with all rotation values " + "available (brute force).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + p.add_avalue("-s", "--string", "string to decrypt", "string", None, True) + + # Define optional arguments + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_switch(None, "--int-ordinals", "write integer ordinals instead " + "of the characters into the output file", "int_ordinals", + True, False) + p.add_predef("-m", "--method", "use only one instead of all supported " + "methods to decrypt", "method", ["rot13", "rot47", "rot128"], + False) + p.add_switch(None, "--no-limit", "do not limit the length of the lines " + "inside the output file", "no_limit", False, False) + p.add_switch(None, "--non-printable", "write non-printable characters " + "into the output file", "non_printable", True, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print cracker.get_version() + sys.exit(0) + + args = p.parse_args() + try: + cracker.brute_force(args.string, args.output_file, args.method, + args.int_ordinals, args.non_printable, + args.no_limit) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python2/rotate-rot128.py b/python2/rotate-rot128.py new file mode 100755 index 0000000..69c7235 --- /dev/null +++ b/python2/rotate-rot128.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT128 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot128 + + try: + p = clap.Parser() + except Exception as e: + print "%s: error: %s" % (os.path.basename(sys.argv[0]), e) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT128 with the " + "default rotation value or a user-defined one (using " + "the ROT128 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "255", "value", 128, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print rot128.get_version() + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot128.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot128.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python2/rotate-rot13.py b/python2/rotate-rot13.py new file mode 100755 index 0000000..16af6d6 --- /dev/null +++ b/python2/rotate-rot13.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT13 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot13 + + try: + p = clap.Parser() + except Exception as e: + print "%s: error: %s" % (os.path.basename(sys.argv[0]), e) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT13 with the " + "default rotation value or a user-defined one (using " + "the ROT13 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "25", "value", 13, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print rot13.get_version() + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot13.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot13.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python2/rotate-rot47.py b/python2/rotate-rot47.py new file mode 100755 index 0000000..b5e3e4d --- /dev/null +++ b/python2/rotate-rot47.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT47 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot47 + + try: + p = clap.Parser() + except Exception as e: + print "%s: error: %s" % (os.path.basename(sys.argv[0]), e) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT47 with the " + "default rotation value or a user-defined one (using " + "the ROT47 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "93", "value", 47, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print rot47.get_version() + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot47.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot47.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python3/changelog.txt b/python3/changelog.txt new file mode 100644 index 0000000..c5739f6 --- /dev/null +++ b/python3/changelog.txt @@ -0,0 +1,57 @@ + +CHANGELOG (ROTate) + + Version 3.0.6 (2018-03-13) + + + Added new versions of the Clap and PaVal core modules (replaced the + existing ones). + + * Revised (refurbished) all components of the project in general + (neglibible changes). + + # Fixed the wildcard bug (certain characters inside the strings to + encrypt and decrypt and will no longer be interpreted as wildcards). + + Version 3.0.5 (2015-01-03) + + * Revised some code inside the ROTate Cracker core module (negligible + changes). + + - Removed unnecessary module imports from the core modules. + + Version 3.0.4 (2014-04-03) + + + Added an optional command-line argument to the ROTate Cracker script + to write the integer ordinals of the decrypted string into the + output file. + + Version 3.0.3 (2014-03-21) + + * Revised (reduced) some code inside the ROTate Cracker core module. + * Revised the ROTate Cracker output file (non-printable characters are + now replaced either with whitespaces or a corresponding notice). + + # Fixed the attribute error inside the ROTate Cracker core module when + reading out the major version of the Python framework using Python + version 2.6 or below. + + Version 3.0.2 (2014-03-14) + + + Added an error handler to the ROTate scripts in case no command-line + argument parser can be initialized. + + * Revised (reduced) some code inside the ROTate Cracker core module. + * Revised the transform methods inside the ROT13, ROT47 and ROT128 + core module (reduced some code for increased readability). + + Version 3.0.1 (2014-03-07) + + * Revised the description of some command-line arguments inside all + ROTate scripts. + * Revised the header of the ROTate Cracker output file (negligible + text changes). + + Version 3.0.0 (2014-02-11) + + * First official release of this major version. + diff --git a/python3/core/__init__.py b/python3/core/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/python3/core/clap.py b/python3/core/clap.py new file mode 100755 index 0000000..e502975 --- /dev/null +++ b/python3/core/clap.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# Clap - Command-line argument parser module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/clap +# ============================================================================ + +__version__ = "1.1.10" + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +class Parser(object): + """ + Project independent command-line argument parser class. + """ + __arg_grp_opt = None + __arg_grp_req = None + __arg_parser = None + __is_argparser = False + __conflict_handler = "resolve" # used by OptionParser, only + + def __init__(self): + try: + from argparse import ArgumentParser + self.__arg_parser = ArgumentParser(add_help=False) + self.__arg_grp_req = \ + self.__arg_parser.add_argument_group("required arguments") + self.__arg_grp_opt = \ + self.__arg_parser.add_argument_group("optional arguments") + self.__is_argparser = True + return + except ImportError: + # Ignore the exception and proceed with the fallback + pass + + try: + from optparse import OptionParser + self.__arg_parser = \ + OptionParser(conflict_handler=self.__conflict_handler) + self.__arg_grp_req = \ + self.__arg_parser.add_option_group("Required arguments") + self.__arg_grp_opt = \ + self.__arg_parser.add_option_group("Optional arguments") + return + except ImportError: + # This should never happen + raise ImportError("Failed to initialize an argument parser.") + + def add_avalue(self, arg_short, arg_long, arg_help, arg_dest, arg_default, + arg_required): + """ + Add an argument that expects a single user-defined value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if arg_default is not None: + # Enclose the value with quotes in case it is not an integer + quotes = "'" + try: + arg_default = int(arg_default) + quotes = "" + except ValueError: + pass + + if arg_help.strip().endswith(")"): + arg_help = arg_help.rstrip(")") + arg_help += ", default is %s%s%s)" % \ + (quotes, str(arg_default), quotes) + else: + arg_help += " (default is %s%s%s)" % \ + (quotes, str(arg_default), quotes) + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + default=arg_default, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, default=arg_default, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + default=arg_default) + else: + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest, default=arg_default) + + def add_predef(self, arg_short, arg_long, arg_help, arg_dest, arg_choices, + arg_required): + """ + Add an argument that expects a certain predefined value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + choices=arg_choices, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, choices=arg_choices, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + choices=arg_choices) + else: + # The OptionParser does not print the values to choose from, + # so these have to be added manually to the description of + # the argument first + arg_help += " (choose from " + for item in arg_choices: + arg_help += "'%s', " % item + arg_help = arg_help.rstrip(", ") + ")" + + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest) + + def add_switch(self, arg_short, arg_long, arg_help, arg_dest, arg_store, + arg_required): + """ + Add an argument that does not expect anything, but returns a + boolean value. + """ + if arg_required: + obj = self.__arg_grp_req + else: + obj = self.__arg_grp_opt + + if arg_store: + arg_store = "store_true" + else: + arg_store = "store_false" + + if self.__is_argparser: + if arg_short is None: + obj.add_argument(arg_long, help=arg_help, dest=arg_dest, + action=arg_store, required=arg_required) + else: + obj.add_argument(arg_short, arg_long, help=arg_help, + dest=arg_dest, action=arg_store, + required=arg_required) + else: + if arg_short is None: + obj.add_option(arg_long, help=arg_help, dest=arg_dest, + action=arg_store) + else: + obj.add_option(arg_short, arg_long, help=arg_help, + dest=arg_dest, action=arg_store) + + def dependency(self, arg_name, arg_value, dependency): + """ + Check the dependency of a command-line argument. + """ + if dependency is not None: + if arg_value is None or str(arg_value) == "": + raise Exception("The '%s' argument depends on %s'." % + (arg_name, dependency)) + + def error(self, obj): + """ + Raise an error and cause the argument parser to print the error + message. + """ + if type(obj) == str: + obj = obj.strip() + + self.__arg_parser.error(obj) + + def parse_args(self): + """ + Parse and return the command-line arguments. + """ + if self.__is_argparser: + args = self.__arg_parser.parse_args() + else: + (args, values) = self.__arg_parser.parse_args() + return args + + def print_help(self): + """ + Print the usage, description, argument details and epilog. + """ + self.__arg_parser.print_help() + + def set_description(self, string): + """ + Set the description text. + """ + self.__arg_parser.description = string.strip() + + def set_epilog(self, string): + """ + Set the epilog text. + """ + self.__arg_parser.epilog = string.strip() + +# EOF diff --git a/python3/core/common.py b/python3/core/common.py new file mode 100755 index 0000000..3a5194d --- /dev/null +++ b/python3/core/common.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Common core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +from . import paval as pv + + +def get_file_size(file_path): + """ + Get the size of a file in bytes. + """ + pv.path(file_path, "", True, True) + + f = open(file_path, "rb") + f.seek(0, 2) + file_size = f.tell() + f.close() + + return int(file_size) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + +# EOF diff --git a/python3/core/cracker.py b/python3/core/cracker.py new file mode 100755 index 0000000..aa7f37b --- /dev/null +++ b/python3/core/cracker.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Cracker core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +import os +from . import paval as pv +from . import rot13 +from . import rot47 +from . import rot128 +import string as st +import sys + +from datetime import datetime as dt + + +def brute_force(string, file_output, method=None, int_ordinals=False, + non_printable=False, limit=True): + """ + Brute-force the given string to find out which ROT variant and + rotation value was used to encrypt. + """ + pv.path(file_output, "output", True, False) + pv.string(string, "string to decrypt", True) + file_output = os.path.abspath(file_output) + + if method is None: + method = "All" + else: + method = method.upper() + pv.compstr(method, "method", ["ROT13", "ROT47", "ROT128"]) + + list_values_rot13 = [] + list_values_rot47 = [] + list_values_rot128 = [] + timestamp = str(dt.now()) + output = "\r\n" + "=" * 78 + \ + "\r\nFile type: ROTate Cracker output file" \ + "\r\n" + "-" * 78 + \ + "\r\nOutput file name: " + file_output + \ + "\r\nString to decrypt: " + string + \ + "\r\nMethods used: " + method + \ + "\r\n" + "-" * 78 + \ + "\r\nTimestamp: " + timestamp[:-7] + \ + "\r\nROTate version: " + get_version() + \ + "\r\n" + "=" * 78 + "\r\n\r\n" + + if method == "All" or method == "ROT13": + list_values_rot13 = \ + __get_values_rot13(string, int_ordinals, non_printable, limit) + if method == "All" or method == "ROT47": + list_values_rot47 = \ + __get_values_rot47(string, int_ordinals, non_printable, limit) + if method == "All" or method == "ROT128": + list_values_rot128 = \ + __get_values_rot128(string, int_ordinals, non_printable, limit) + + if len(list_values_rot13) > 0: + output += "\r\n [ROT13]\r\n" + for value in list_values_rot13: + output += " - %s\r\n" % value + output += "\r\n" + if len(list_values_rot47) > 0: + output += "\r\n [ROT47]\r\n" + for value in list_values_rot47: + output += " - %s\r\n" % value + output += "\r\n" + if len(list_values_rot128) > 0: + output += "\r\n [ROT128]\r\n" + for value in list_values_rot128: + output += " - %s\r\n" % value + output += "\r\n" + fh_output = open(file_output, "wb") + + # Run the appropriate code for the Python framework used + if sys.version_info[0] == 2: + fh_output.write(output) + elif sys.version_info[0] > 2: + fh_output.write(output.encode(sys.getdefaultencoding())) + + fh_output.close() + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __get_int_ordinals(string): + """ + Return the integer ordinals of a string. + """ + output = "" + + for char in string: + output += str(ord(char)).rjust(3, " ") + ", " + output = output.rstrip(", ") + + return output + + +def __get_printable(string): + """ + Replace non-printable characters inside a string with whitespaces + """ + output = "" + + chars_remove = "\t\n\r\x0b\x0c" + for char in chars_remove: + string = string.replace(char, " ") + + for char in string: + if char not in st.printable: + output += " " + else: + output += char + + if output.strip() == "": + output = "(only non-printable characters or spaces)" + + return output + + +def __get_values_rot13(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT13 values for the given string. + """ + list_values = [] + for i in range(1, 26): + output = __prepare_line(rot13.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __get_values_rot47(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT47 values for the given string. + """ + list_values = [] + for i in range(1, 94): + output = __prepare_line(rot47.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __get_values_rot128(string, int_ordinals, non_printable, limit): + """ + Core method to get all possible ROT128 values for the given string. + """ + list_values = [] + for i in range(1, 256): + output = __prepare_line(rot128.decrypt_string(string, i), + int_ordinals, non_printable, limit) + list_values.append("Value %s: %s" % (str(i).rjust(3, " "), output)) + + return list_values + + +def __prepare_line(line, int_ordinals, non_printable, limit): + """ + Prepare the line before it gets written into the file. + """ + if int_ordinals: + line = __get_int_ordinals(line) + else: + if not non_printable: + line = __get_printable(line) + + if limit: + if len(line) > 58: + line = line[:55] + "..." + + return line + +# EOF diff --git a/python3/core/paval.py b/python3/core/paval.py new file mode 100755 index 0000000..159f357 --- /dev/null +++ b/python3/core/paval.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# PaVal - Parameter validation module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/paval +# ============================================================================ + +__version__ = "1.2.7" + +import filecmp +import os + + +def compfile(path, name="", list_files=[]): + """ + Compare files to avoid that the same file is given multiple times or + in different ways (e. g. different name but same content). + """ + __string(path, "%s path" % name, True) + + if list_files is None: + list_files = [] + elif len(list_files) == 0: + __ex("File list is empty (no files to compare with).", True) + else: + for item in list_files: + if not type(item) == list: + __ex("Every list item must be a sub-list.", True) + if not len(item) == 2: + __ex("Every sub-list must contain two items.", True) + + path = os.path.abspath(path) + for item in list_files: + path_compare = os.path.abspath(str(item[0])) + name_compare = str(item[1]) + if path == path_compare: + __ex("The %s and the %s file path must not be identical." % + (name, name_compare), False) + if os.path.exists(path) and os.path.exists(path_compare): + if filecmp.cmp(path, path_compare, 0): + __ex("The %s and %s file content must not be identical." % + (name, name_compare), False) + + +def compstr(string, name="", list_strings=[]): + """ + Compare a string with a list of strings and check if it is an item of + that list. + """ + __string(string, name, False) + if len(list_strings) == 0: + __ex("No %s strings to compare with." % name, True) + if string not in list_strings: + __ex("The %s '%s' does not exist." % (name, string), False) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def intrange(value, name="", value_min=None, value_max=None, zero=False): + """ + Validate an integer range. + """ + value = __integer(value, "%s value" % name, False) + if value_min is not None: + value_min = __integer(value_min, "minimal %s value" % name, True) + intvalue(value_min, name, True, True, True) + if value_max is not None: + value_max = __integer(value_max, "maximal %s value" % name, True) + intvalue(value_max, name, True, True, True) + if not zero: + if value == 0: + __ex("The %s value must not be zero." % name, False) + if (value_min is not None) and (value_max is not None): + if value_min > value_max: + __ex("The maximal %s value must be greater than the minimal " + "value." % name, False) + if (value_min == value_max) and (not value == value_min): + __ex("The %s value can only be %s (depending on further range " + "further range arguments)." % (name, value_min), False) + if (value < value_min) or (value > value_max): + __ex("The %s value must be between %s and %s (depending on " + "further range arguments)." % (name, value_min, value_max), + False) + elif value_min is not None: + if value < value_min: + __ex("The %s value must not be less than %s." % (name, value_min), + False) + elif value_max is not None: + if value > value_max: + __ex("The %s value must not be greater than %s." % + (name, value_max), False) + + +def intvalue(value, name="", positive=True, zero=False, negative=False): + """ + Validate a single integer value. + """ + value = __integer(value, "%s value" % name, False) + if not positive: + if value > 0: + __ex("The %s value must not be positive." % name, False) + if not zero: + if value == 0: + __ex("The %s value must not be zero." % name, False) + if not negative: + if value < 0: + __ex("The %s value must not be negative." % name, False) + + +def path(path, name="", is_file=False, exists=False): + """ + Validate a path of a file or directory. + """ + string(path, "%s path" % name, False, None) + path = os.path.abspath(path) + + if is_file: + path_type = "file" + else: + path_type = "directory" + if exists: + if not os.path.exists(path): + __ex("The given %s %s does not exist." % (name, path_type), False) + if (is_file and not os.path.isfile(path)) or \ + (not is_file and not os.path.isdir(path)): + __ex("The given %s %s path is not a %s." % (name, path_type, + path_type), False) + else: + if os.path.exists(path): + __ex("The given %s %s path already exists." % (name, path_type), + False) + + +def string(string, name="", wildcards=False, invalid_chars=None): + """ + Validate a string. + """ + __string(string, name, False) + if invalid_chars is None: + invalid_chars = "" + if not wildcards: + if ("*" in string) or ("?" in string): + __ex("The %s must not contain wildcards." % name, False) + if len(invalid_chars) > 0: + for char in invalid_chars: + if char in string: + # Use single quotes by default or double quotes in case the + # single quotes are the invalid character + quotes = "'" + if char == quotes: + quotes = "\"" + + __ex("The %s contains at least one invalid character " + "(%s%s%s)." % (name, quotes, char, quotes), False) + + +def __ex(string, internal=False): + """ + Internal method to raise an exception. + """ + string = str(string).strip() + while (" " * 2) in string: + string = string.replace((" " * 2), " ") + if internal: + string = "PaVal: " + string + raise Exception(string) + + +def __integer(value, name="", internal=False): + """ + Internal method for basic integer validation. + """ + if value is None: + __ex("The %s is missing." % name, internal) + if value == "": + __ex("The %s must not be empty." % name, internal) + try: + value = int(value) + except ValueError: + __ex("The %s must be an integer." % name, internal) + return int(value) + + +def __string(string, name="", internal=False): + """ + Internal method for basic string validation. + """ + if string is None: + __ex("The %s is missing." % name, internal) + if string == "": + __ex("The %s must not be empty." % name, internal) + +# EOF diff --git a/python3/core/rot128.py b/python3/core/rot128.py new file mode 100755 index 0000000..e295a54 --- /dev/null +++ b/python3/core/rot128.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT128 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +from . import common +import os +from . import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=128): + """ + Encrypt a file using ROT128. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value): + """ + Encrypt a string using ROT128. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=128): + """ + Decrypt a file using ROT128. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value): + """ + Decrypt a string using ROT128. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if encrypt: + data_output.append(((byte + rot_value) % 256)) + else: + data_output.append(((byte - rot_value) % 256)) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if encrypt: + output += chr(((ord(char) + rot_value) % 256)) + else: + output += chr(((ord(char) + rot_value) % 256)) + + return output + +# EOF diff --git a/python3/core/rot13.py b/python3/core/rot13.py new file mode 100755 index 0000000..03833b7 --- /dev/null +++ b/python3/core/rot13.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT13 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +from . import common +import os +from . import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=13): + """ + Encrypt a file using ROT13. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(True, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value=13): + """ + Encrypt a string using ROT13. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=13): + """ + Decrypt a file using ROT13. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value=13): + """ + Decrypt a string using ROT13. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if byte in range(65, 91): + # Uppercase letters + if encrypt: + data_output.append((((byte - 65) + rot_value) % 26) + 65) + else: + data_output.append((((byte - 65) - rot_value) % 26) + 65) + elif byte in range(97, 123): + # Lowercase letters + if encrypt: + data_output.append((((byte - 97) + rot_value) % 26) + 97) + else: + data_output.append((((byte - 97) - rot_value) % 26) + 97) + else: + data_output.append(byte) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if ord(char) in range(65, 91): + # Uppercase letters + if encrypt: + output += chr((((ord(char) - 65) + rot_value) % 26) + 65) + else: + output += chr((((ord(char) - 65) - rot_value) % 26) + 65) + elif ord(char) in range(97, 123): + # Lowercase letters + if encrypt: + output += chr((((ord(char) - 97) + rot_value) % 26) + 97) + else: + output += chr((((ord(char) - 97) - rot_value) % 26) + 97) + else: + output += char + + return output + +# EOF diff --git a/python3/core/rot47.py b/python3/core/rot47.py new file mode 100755 index 0000000..6ef3c47 --- /dev/null +++ b/python3/core/rot47.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT47 encryption/decryption core module +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +__version__ = "3.0.6" + +from . import common +import os +from . import paval as pv + + +def encrypt_file(file_input, file_output, buffer_size=4096, rot_value=47): + """ + Encrypt a file using ROT47. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def encrypt_string(string, rot_value=47): + """ + Encrypt a string using ROT47. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(True, string, rot_value) + + +def decrypt_file(file_input, file_output, buffer_size=4096, rot_value=47): + """ + Decrypt a file using ROT47. + """ + pv.path(file_input, "input", True, True) + pv.path(file_output, "output", True, False) + pv.intvalue(buffer_size, "buffer size", True, False, False) + pv.intvalue(rot_value, "ROT", True, False, False) + pv.compfile(file_input, "input", [[file_output, "output"]]) + + buffer_size = int(buffer_size) + rot_value = int(rot_value) + file_input = os.path.abspath(file_input) + file_output = os.path.abspath(file_output) + + fh_input = open(file_input, "rb") + fh_output = open(file_output, "wb") + + file_size = common.get_file_size(file_input) + byte_blocks = int(file_size / buffer_size) + byte_remainder = file_size % buffer_size + + for block in range(byte_blocks): + data_input = bytearray(b"" + fh_input.read(buffer_size)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + if byte_remainder > 0: + data_input = bytearray(b"" + fh_input.read(byte_remainder)) + fh_output.write(__transform_bytes(False, data_input, rot_value)) + + fh_input.close() + fh_output.close() + + +def decrypt_string(string, rot_value=47): + """ + Decrypt a string using ROT47. + """ + pv.string(string, "string", True) + pv.intvalue(rot_value, "ROT", True, False, False) + + return __transform_string(False, string, rot_value) + + +def get_version(): + """ + Return the version of this module. + """ + return __version__ + + +def __transform_bytes(encrypt, data_input, rot_value): + """ + Core method to manipulate bytes. + """ + data_output = bytearray(b"") + + for byte in data_input: + if byte in range(32, 126): + if encrypt: + data_output.append((((byte - 32) + rot_value) % 94) + 32) + else: + data_output.append((((byte - 32) - rot_value) % 94) + 32) + else: + data_output.append(byte) + + return data_output + + +def __transform_string(encrypt, string, rot_value): + """ + Core method to manipulate a string. + """ + output = "" + + for char in string: + if ord(char) in range(32, 126): + if encrypt: + output += chr((((ord(char) - 32) + rot_value) % 94) + 32) + else: + output += chr((((ord(char) - 32) - rot_value) % 94) + 32) + else: + output += char + + return output + +# EOF diff --git a/python3/docs/usage_rotate-cracker.txt b/python3/docs/usage_rotate-cracker.txt new file mode 100644 index 0000000..60c548f --- /dev/null +++ b/python3/docs/usage_rotate-cracker.txt @@ -0,0 +1,83 @@ + +USAGE (rotate-cracker.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Crack a ROT encrypted file + + 1. Definition + + The ROTate Cracker script helps to determine which ROT variant and + rotation value was used to encrypt a file or string by trying all + supported ROT variants with all rotation values available (brute + force). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-cracker.py --help + + 3. Crack a ROT encrypted file + + For example, you have the ROT encrypted file "secret.txt" containing + the line + + Uryyb, Jbeyq! + + and want to decrypt it. So, to find out which ROT method and rotation + value was used to encrypt the file, a string from the file (e. g. a + word) is required. + + This example uses the string "Uryyb" and writes the decrypted values + into the file "/tmp/output.txt": + + $ ./rotate-cracker.py -s "Uryyb" -o /tmp/output.txt + + The output file shows that when using the ROT13 method, the string + "Uryyb" decrypted with value 13 returns the word "Hello": + + (...) + [ROT13] + - Value 0: Uryyb + - Value 1: Tqxxa + - Value 2: Spwwz + - Value 3: Rovvy + - Value 4: Qnuux + - Value 5: Pmttw + - Value 6: Olssv + - Value 7: Nkrru + - Value 8: Mjqqt + - Value 9: Lipps + - Value 10: Khoor + - Value 11: Jgnnq + - Value 12: Ifmmp + - Value 13: Hello + (...) + + So, the file or at least that string seems to be encrypted with that + method and value. Unfortunately, there is no analysis feature, which + means you will have to look at the returned value list to manually + check which entry makes most sense. + + If there are non-printable characters inside the string, they will be + replaced by whitespaces. If the string only contains of whitespaces, + a notice will be written to the output file. + diff --git a/python3/docs/usage_rotate-rot128.txt b/python3/docs/usage_rotate-rot128.txt new file mode 100644 index 0000000..53b835d --- /dev/null +++ b/python3/docs/usage_rotate-rot128.txt @@ -0,0 +1,56 @@ + +USAGE (rotate-rot128.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT128 script encrypts and decrypts a file using the ROT128 + method with the default rotation value or a user-defined one (using + the ROT128 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot128.py --help + + 3. Encrypt a file + + This works as described in section 3 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + + 4. Decrypt the file again + + This works as described in section 4 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + + 5. User-defined rotation values + + This works as described in section 5 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT128 instead of the + script used there. + diff --git a/python3/docs/usage_rotate-rot13.txt b/python3/docs/usage_rotate-rot13.txt new file mode 100644 index 0000000..918322b --- /dev/null +++ b/python3/docs/usage_rotate-rot13.txt @@ -0,0 +1,74 @@ + +USAGE (rotate-rot13.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT13 script encrypts and decrypts a file using the ROT13 + method with the default rotation value or a user-defined one (using + the ROT13 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot13.py --help + + 3. Encrypt a file + + For example, if you have the input file "hello.txt" containing the + line + + Hello, world! + + and want to encrypt it using ROT13 (with its default value) and write + the encrypted data into "secret.txt", the command line would look like + this: + + $ ./rotate-rot13.py -i hello.txt -o secret.txt -a encrypt + + The encrypted data inside "secret.txt" should look like this: + + Uryyb, Jbeyq! + + 4. Decrypt the file again + + To decrypt the file again and write the decrypted data into the file + "output.txt", type: + + $ ./rotate-rot13.py -i secret.txt -o output.txt -a decrypt + + 5. User-defined rotation values + + You can also encrypt the file using the ROT13 method, but with + rotation value 19 instead of the default value: + + $ ./rotate-rot13.py -i hello.txt -o secret.txt -a encrypt -v 19 + + The user-defined rotation value used to encrypt is also required to + properly decrypt the file again: + + $ ./rotate-rot13.py -i secret.txt -o output.txt -a decrypt -v 19 + diff --git a/python3/docs/usage_rotate-rot47.txt b/python3/docs/usage_rotate-rot47.txt new file mode 100644 index 0000000..1498be1 --- /dev/null +++ b/python3/docs/usage_rotate-rot47.txt @@ -0,0 +1,56 @@ + +USAGE (rotate-rot47.py) + + Contents: + + 1. Definition + 2. General stuff + 2.1 How to run Python scripts + 2.2 Overview of all command-line arguments + 3. Encrypt a file + 4. Decrypt the file again + 5. User-defined rotation values + + 1. Definition + + The ROTate ROT47 script encrypts and decrypts a file using the ROT47 + method with the default rotation value or a user-defined one (using + the ROT47 character set). + + 2. General stuff + + 2.1 How to run Python scripts + + All usage examples below show how to execute the Python scripts on + the shell of a Unix-like system. If you do not know, how to run + those scripts on your operating system, you may have a look at + this page: + + http://www.urbanware.org/howto_python.html + + 2.2 Overview of all command-line arguments + + Usually, each script requires command-line arguments to operate. + So, to get an overview of all arguments available, simply run the + script with the "--help" argument. For example: + + $ ./rotate-rot47.py --help + + 3. Encrypt a file + + This works as described in section 3 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + + 4. Decrypt the file again + + This works as described in section 4 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + + 5. User-defined rotation values + + This works as described in section 5 inside the ROT13 documentation, + but, of course, you need to run the ROTate ROT47 instead of the script + used there. + diff --git a/python3/license.txt b/python3/license.txt new file mode 100644 index 0000000..d87dbbb --- /dev/null +++ b/python3/license.txt @@ -0,0 +1,33 @@ + +LICENSE (ROTate) + + General + + This project is distributed under the MIT License which can be found + below, inside the file "readme.txt" as well as in the header of each + script file. In case the license text is missing for some reason, you + can also find it here: + + http://www.urbanware.org/license.html + + License terms + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/python3/readme.txt b/python3/readme.txt new file mode 100644 index 0000000..34bb176 --- /dev/null +++ b/python3/readme.txt @@ -0,0 +1,49 @@ + +README (ROTate) + + Project + + ROTate + Version 3.0.6 (based on Python framework 3.x) + Copyright (C) 2018 by Ralf Kilian + + Website: http://www.urbanware.org + GitHub: https://github.com/urbanware-org/rotate + + Definition + + The ROTate project is a collection of scripts to encrypt and decrypt + files using various ROT cipher methods. + + License + + This project is distributed under the MIT License. You should have + received a copy of the license along with this program (see the + 'license.txt' file). If not, you can find the license terms here: + + https://opensource.org/licenses/MIT + + Requirements + + The requirements for this project can be found inside the included + file 'requirements.txt'. + + In case this file is missing, you can also find the information on the + website of the project mentioned above. + + Usage + + For fundamental documentation as well as some usage examples for each + component of the project, you may have a look at the text files inside + the included 'docs' sub-directory. + + Legal information + + The project name is completely fictitious. Any correspondences with + existing websites, applications, companies and/or other projects are + purely coincidental. + + All trademarks belong to their respective owners. + + Errors and omissions excepted. + diff --git a/python3/requirements.txt b/python3/requirements.txt new file mode 100644 index 0000000..5392db1 --- /dev/null +++ b/python3/requirements.txt @@ -0,0 +1,17 @@ + +REQUIREMENTS (ROTate) + + Notice + + This version of ROTate was built for the Python 3.x framework. If you + need a version that works with Python 2.x, you may look here: + + http://github.com/urbanware-org/rotate + + General + + Software requirements: + + - Python 2.x (version 2.7 or higher is recommended, may also work + with earlier versions) + diff --git a/python3/rotate-cracker.py b/python3/rotate-cracker.py new file mode 100755 index 0000000..e63915e --- /dev/null +++ b/python3/rotate-cracker.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# Cracker script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import cracker + + try: + p = clap.Parser() + except Exception as e: + print("%s: error: %s" % (os.path.basename(sys.argv[0]), e)) + sys.exit(1) + + p.set_description("Determine which ROT variant and rotation value was " + "used to encrypt a file or string by trying all " + "supported ROT variants with all rotation values " + "available (brute force).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + p.add_avalue("-s", "--string", "string to decrypt", "string", None, True) + + # Define optional arguments + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_switch(None, "--int-ordinals", "write integer ordinals instead " + "of the characters into the output file", "int_ordinals", + True, False) + p.add_predef("-m", "--method", "use only one instead of all supported " + "methods to decrypt", "method", ["rot13", "rot47", "rot128"], + False) + p.add_switch(None, "--no-limit", "do not limit the length of the lines " + "inside the output file", "no_limit", False, False) + p.add_switch(None, "--non-printable", "write non-printable characters " + "into the output file", "non_printable", True, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print(cracker.get_version()) + sys.exit(0) + + args = p.parse_args() + try: + cracker.brute_force(args.string, args.output_file, args.method, + args.int_ordinals, args.non_printable, + args.no_limit) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python3/rotate-rot128.py b/python3/rotate-rot128.py new file mode 100755 index 0000000..fddfbc2 --- /dev/null +++ b/python3/rotate-rot128.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT128 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot128 + + try: + p = clap.Parser() + except Exception as e: + print("%s: error: %s" % (os.path.basename(sys.argv[0]), e)) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT128 with the " + "default rotation value or a user-defined one (using " + "the ROT128 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "255", "value", 128, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print(rot128.get_version()) + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot128.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot128.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python3/rotate-rot13.py b/python3/rotate-rot13.py new file mode 100755 index 0000000..c14dfd9 --- /dev/null +++ b/python3/rotate-rot13.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT13 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot13 + + try: + p = clap.Parser() + except Exception as e: + print("%s: error: %s" % (os.path.basename(sys.argv[0]), e)) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT13 with the " + "default rotation value or a user-defined one (using " + "the ROT13 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "25", "value", 13, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print(rot13.get_version()) + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot13.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot13.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/python3/rotate-rot47.py b/python3/rotate-rot47.py new file mode 100755 index 0000000..78d6cd4 --- /dev/null +++ b/python3/rotate-rot47.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ============================================================================ +# ROTate - Encryption tool based on the ROT cipher +# ROT47 encryption/decryption script +# Copyright (C) 2018 by Ralf Kilian +# Distributed under the MIT License (https://opensource.org/licenses/MIT) +# +# Website: http://www.urbanware.org +# GitHub: https://github.com/urbanware-org/rotate +# ============================================================================ + +import os +import sys + + +def main(): + from core import clap + from core import rot47 + + try: + p = clap.Parser() + except Exception as e: + print("%s: error: %s" % (os.path.basename(sys.argv[0]), e)) + sys.exit(1) + + p.set_description("Encrypt or decrypt a file using ROT47 with the " + "default rotation value or a user-defined one (using " + "the ROT47 character set).") + p.set_epilog("Further information and usage examples can be found inside " + "the documentation file for this script.") + + # Define required arguments + p.add_predef("-a", "--action", "action to perform", "action", + ["encrypt", "decrypt"], True) + p.add_avalue("-i", "--input-file", "input file path", "input_file", None, + True) + p.add_avalue("-o", "--output-file", "output file path", "output_file", + None, True) + + # Define optional arguments + p.add_avalue("-b", "--buffer-size", "buffer size in bytes", "buffer_size", + 4096, False) + p.add_switch("-h", "--help", "print this help message and exit", None, + True, False) + p.add_avalue("-v", "--value", "user-defined rotation value between 0 and " + "93", "value", 47, False) + p.add_switch(None, "--version", "print the version number and exit", None, + True, False) + + if len(sys.argv) == 1: + p.error("At least one required argument is missing.") + elif ("-h" in sys.argv) or ("--help" in sys.argv): + p.print_help() + sys.exit(0) + elif "--version" in sys.argv: + print(rot47.get_version()) + sys.exit(0) + + args = p.parse_args() + if args.action is None: + p.error("The required action argument is missing.") + elif args.action.lower() == ("encrypt"): + encrypt = True + elif args.action.lower() == ("decrypt"): + encrypt = False + else: + p.error("An unsupported action was given.") + + try: + if encrypt: + rot47.encrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + else: + rot47.decrypt_file(args.input_file, args.output_file, + args.buffer_size, args.value) + except Exception as e: + p.error(e) + + +if __name__ == "__main__": + main() + +# EOF diff --git a/rotate.png b/rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..2f7d55458614e9a4ae7fa0cf37fd6d1f5a8ad0ef GIT binary patch literal 26077 zcmXt91yoee_kX)fEWLD>pme9yE)oJtBNEagNJ>e=(kTsRY6l)YJ2# zoKd$9##htYM!t@(+{W;KnPzYQ{d>5(n{!w$vE8~Yy#{WaGmql=|Hs>OpX(VoY3$;n z!9L_6*PrbA->*~vU|bTi8KqP|^yezpsfmr``OVQIgE7yMsp}tWq{tB59I@cEc9d#^*nul~pVU{#RZMEpk(=w-`#wfwabl zt;axEY>)KLP>XDDo`kCn`yQG6`%&Ag2uLOh-)3s)=|sQ&%;4o-|86bYfpYaD*qBIT zf%1blNP}s8;h->R%tSIBMp74>!6OQu!(nBJDyS)?;`=RfI5|^=ca#KW0>!bz5|P$q zOkjk3C%uW7)6eJvd$^C+?J_Vv`NoP7VQ2@bVTs!LuznGS_A=iH_54* z4&iuYAsl4JOZZklfRk8rThm;QQ-j8qUA3s0e|Sg{YLXp5Y@%X-k7U>DL6q+Lm#B)U zjT60+?Gi*5nC#S77`Mk97tEw0o>vfX^~a)dbh!HFu&{!NLV@_zqUapJw`{at{DDQ{?WeO+pnQ%P1 z|9c?2a^Qcn?sLn&XikgdP9j>#4%kCqKl+>{X06FSE~R&IPIB~_DM(3P6dxyqGC>ny z2IM>C8tLc^C2Ds2)%m1IfJQrG3r5$6&W_`8KF#Ip+A1MH6_lCnwR3O9{!+WDeg|R= z!Pe*oF0cwVFp~P!CQ~--{kQgt8TZjgvO&*R3n~BA5w-F(f(Eeill};c*hv!NE|nPU zRG-|~Nbt*I^t~mE)9`C!9+wJ*!UDSJZ%
    u&hCs82LFfFARY2XPPI?!00S#_)NH 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�@U`&d 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 af&T-oOE|#)1Z6$|0000