Example #1
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        """
        Attempts to decode both variants of the Baconian cipher.
        """
        logger.trace("Attempting Baconian cracker")
        candidates = []
        result = []
        ctext_decoded = ""
        ctext_decoded2 = ""

        # Convert to uppercase and replace delimiters and whitespace with nothing
        ctext = re.sub(r"[,;:\-\s]", "", ctext.upper())

        # Make sure ctext only contains A and B
        if bool(re.search(r"[^AB]", ctext)) is True:
            logger.trace(
                "Failed to crack baconian due to non baconian character(s)")
            return None

        # Make sure ctext is divisible by 5
        ctext_len = len(ctext)
        if ctext_len % 5:
            logger.trace(
                f"Failed to decode Baconian because length must be a multiple of 5, not '{ctext_len}'"
            )
            return None

        # Split ctext into groups of 5
        ctext = " ".join(ctext[i:i + 5] for i in range(0, len(ctext), 5))
        ctext_split = ctext.split(" ")
        baconian_keys = self.BACONIAN_DICT.keys()

        # Decode I=J and U=V variant
        for i in ctext_split:
            if i in baconian_keys:
                ctext_decoded += self.BACONIAN_DICT[i]

        # Decode variant that assigns each letter a unique code
        for i in ctext_split:
            if "+" + i in baconian_keys:
                ctext_decoded2 += self.BACONIAN_DICT["+" + i]

        candidates.append(ctext_decoded)
        candidates.append(ctext_decoded2)
        for candidate in candidates:
            if candidate != "":
                if candidate == candidates[0]:
                    result.append(
                        CrackResult(value=candidate, key_info="I=J & U=V"))
                else:
                    result.append(CrackResult(value=candidate))
        logger.trace(f"Baconian cracker - Returning results: {result}")
        return result
Example #2
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        """
        Brute forces all the possible combinations of a and b to attempt to crack the cipher.
        """
        logger.trace("Attempting affine")
        candidates = []

        # a and b are coprime if gcd(a,b) is 1.
        possible_a = [
            a for a in range(1, self.alphabet_length)
            if mathsHelper.gcd(a, self.alphabet_length) == 1
        ]
        logger.debug(
            f"Trying Affine Cracker with {len(possible_a)} a-values and {self.alphabet_length} b-values"
        )

        for a in possible_a:
            a_inv = mathsHelper.mod_inv(a, self.alphabet_length)
            # If there is no inverse, we cannot decrypt the text
            if a_inv is None:
                continue
            for b in range(self.alphabet_length):
                # Pass in lowered text. This means that we expect alphabets to not contain both 'a' and 'A'.
                translated = self.decrypt(ctext.lower(), a_inv, b,
                                          self.alphabet_length)

                candidate_probability = self.plaintext_probability(translated)
                if candidate_probability > self.plaintext_prob_threshold:
                    candidates.append(
                        CrackResult(value=fix_case(translated, ctext),
                                    key_info=f"a={a}, b={b}"))
        logger.debug(f"Affine Cipher returned {len(candidates)} candidates")
        return candidates
Example #3
0
    def attemptCrack(self, ctext: bytes) -> List[CrackResult]:
        logging.info("Trying xor single cipher")
        # TODO: handle different alphabets

        logging.debug("Beginning cipheycore simple analysis")
        logging.debug(f"{ctext}")

        # Hand it off to the core
        analysis = self.cache.get_or_update(
            ctext,
            "cipheycore::simple_analysis",
            lambda: cipheycore.analyse_bytes(ctext),
        )
        logging.debug("Beginning cipheycore::xor_single")
        possible_keys = cipheycore.xor_single_crack(
            analysis, self.expected, self.p_value
        )

        n_candidates = len(possible_keys)
        logging.info(f"XOR single returned {n_candidates} candidates")

        candidates = []

        for candidate in possible_keys:
            translated = cipheycore.xor_single_decrypt(ctext, candidate.key)
            logging.debug(f"Candidate {candidate.key} has prob {candidate.p_value}")
            candidates.append(CrackResult(value=translated, key_info=candidate.key))

        logging.debug(f"{candidates}")

        return candidates
Example #4
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        logger.debug(f"Trying ASCII shift cipher on {ctext}")

        logger.trace("Beginning cipheycore simple analysis")

        # Hand it off to the core
        analysis = self.cache.get_or_update(
            ctext,
            "cipheycore::simple_analysis",
            lambda: cipheycore.analyse_string(ctext),
        )
        logger.trace("Beginning cipheycore::caesar")
        possible_keys = cipheycore.caesar_crack(analysis, self.expected,
                                                self.group, self.p_value)

        n_candidates = len(possible_keys)
        logger.debug(f"ASCII shift returned {n_candidates} candidates")

        if n_candidates == 0:
            logger.trace("Filtering for better results")
            analysis = cipheycore.analyse_string(ctext, self.group)
            possible_keys = cipheycore.caesar_crack(analysis, self.expected,
                                                    self.group, self.p_value)

        candidates = []

        for candidate in possible_keys:
            logger.trace(
                f"Candidate {candidate.key} has prob {candidate.p_value}")
            translated = cipheycore.caesar_decrypt(ctext, candidate.key,
                                                   self.group)
            candidates.append(
                CrackResult(value=translated, key_info=candidate.key))

        return candidates
Example #5
0
    def search(self, ctext: Any) -> List[SearchLevel]:
        deadline = (datetime.now() + self._config().objs["timeout"]
                    if self._config().timeout is not None else datetime.max)

        success, expand_res = self.expand(
            [SearchLevel(name="input", result=CrackResult(value=ctext))])
        if success:
            return expand_res

        nodes = set(expand_res)

        while datetime.now() < deadline:
            # logger.trace(f"Have node tree {nodes}")

            if len(nodes) == 0:
                raise LookupError("Could not find any solutions")

            best_node = self.findBestNode(nodes)
            nodes.remove(best_node)
            success, eval_res = self.evaluate(best_node)
            if success:
                # logger.trace(f"Success with node {best_node}")
                return eval_res
            nodes.update(eval_res)

        raise TimeoutError("Search ran out of time")
Example #6
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        logger.debug("Trying caesar cipher")
        # Convert it to lower case
        #
        # TODO: handle different alphabets
        if self.lower:
            message = ctext.lower()
        else:
            message = ctext

        logger.trace("Beginning cipheycore simple analysis")

        # Hand it off to the core
        analysis = self.cache.get_or_update(
            ctext,
            "cipheycore::simple_analysis",
            lambda: cipheycore.analyse_string(message),
        )
        logger.trace("Beginning cipheycore::caesar")
        possible_keys = cipheycore.caesar_crack(analysis, self.expected,
                                                self.group, True, self.p_value)
        n_candidates = len(possible_keys)
        logger.debug(f"Caesar returned {n_candidates} candidates")

        candidates = []

        for candidate in possible_keys:
            translated = cipheycore.caesar_decrypt(message, candidate.key,
                                                   self.group)
            candidates.append(
                CrackResult(value=translated, key_info=candidate.key))

        return candidates
Example #7
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        """
        Checks an input if it only consists of two or three different letters.
        If this is the case, it attempts to regard those letters as
        0 and 1 (with the third characters as an optional delimiter) and then
        converts it to ASCII text.
        """
        logger.trace("Attempting X-Y replacement")
        variants = []
        candidates = []
        result = []

        # Convert the ctext to all-lowercase and regex-match & replace all whitespace
        ctext = re.sub(r"\s+", "", ctext.lower(), flags=re.UNICODE)

        # cset contains every unique value in the ctext
        cset = list(set(list(ctext)))
        cset_len = len(cset)

        if not 1 < cset_len < 4:
            # We only consider inputs with two or three unique values
            logger.trace(
                "Failed to crack X-Y due to not containing two or three unique values"
            )
            return None

        logger.trace(f"String contains {cset_len} unique values: {cset}")

        # In case of three unique values, we regard the least frequent character as the delimiter
        if cset_len == 3:
            # Count each unique character in the set to determine the least frequent one
            counting_list = []
            for char in cset:
                counting_list.append(ctext.count(char))
            val, index = min((val, index) for (index, val) in enumerate(counting_list))
            delimiter = cset[index]
            logger.trace(
                f"{delimiter} occurs {val} times and is the probable delimiter"
            )
            # Remove the delimiter from the ctext and compute new cset
            ctext = ctext.replace(delimiter, "")
            cset = list(set(list(ctext)))

        # Form both variants of the substitution
        for i in range(2):
            if i:
                variants.append(ctext.replace(cset[0], "1").replace(cset[1], "0"))
            else:
                variants.append(ctext.replace(cset[0], "0").replace(cset[1], "1"))

        # Apply function to both variants and strip stray NULL characters
        for variant in variants:
            candidates.append(self.binary_to_ascii(variant).strip("\x00"))
        for i, candidate in enumerate(candidates):
            if candidate != "":
                keyinfo = f"{cset[0]} -> {i} & {cset[1]} -> {str(int(not i))}"
                result.append(CrackResult(value=candidate, key_info=keyinfo))
                logger.trace(f"X-Y cracker - Returning results: {result}")
                return result
Example #8
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        """
        Attempts to crack Soundex by generating all possible combinations.
        """
        logger.trace("Attempting Soundex cracker")
        word_list = []
        sentences = []
        result = []

        # Convert to uppercase and replace delimiters and whitespace with nothing
        ctext = re.sub(r"[,;:\-\s]", "", ctext.upper())

        # Make sure ctext contains only A-Z and 0-9
        if bool(re.search(r"[^A-Z0-9]", ctext)) is True:
            logger.trace(
                "Failed to crack soundex due to non soundex character(s)")
            return None

        # Make sure ctext is divisible by 4
        ctext_len = len(ctext)
        if ctext_len % 4:
            logger.trace(
                f"Failed to decode Soundex because length must be a multiple of 4, not '{ctext_len}'"
            )
            return None

        # Split ctext into groups of 4
        ctext = " ".join(ctext[i:i + 4] for i in range(0, len(ctext), 4))
        ctext_split = ctext.split(" ")
        soundex_keys = self.SOUNDEX_DICT.keys()

        # Find all words that correspond to each given soundex code
        for code in ctext_split:
            if code in soundex_keys:
                word_list.append(self.SOUNDEX_DICT[code])

        logger.debug(f"Possible words for given encoded text: {word_list}")

        # Find all possible sentences
        self.getSentenceCombo(
            word_list,
            sentences,
            self.frequency_dict,
            self.sentence_freq,
            self.word_freq,
        )

        sorted_sentences = self.sortlistwithdict(sentences,
                                                 self.frequency_dict)

        for sentence in sorted_sentences:
            result.append(CrackResult(value=sentence))

        logger.trace(f"Soundex cracker - Returning results: {result}")
        return result
Example #9
0
 def crackOne(
     self, ctext: str, analysis: cipheycore.windowed_analysis_res
 ) -> List[CrackResult]:
     possible_keys = cipheycore.vigenere_crack(
         analysis, self.expected, self.group, self.p_value
     )
     logger.trace(f"Vigenere crack got keys: {[[i for i in candidate.key] for candidate in possible_keys]}")
     return [
         CrackResult(
             value=cipheycore.vigenere_decrypt(ctext, candidate.key, self.group),
             key_info="".join([self.group[i] for i in candidate.key]),
         )
         for candidate in possible_keys[:min(len(possible_keys), 10)]
     ]
Example #10
0
    def crackOne(
            self, ctext: bytes,
            analysis: cipheycore.windowed_analysis_res) -> List[CrackResult]:
        possible_keys = cipheycore.xorcrypt_crack(analysis, self.expected,
                                                  self.p_value)

        logger.trace(
            f"xorcrypt crack got keys: {[[i for i in candidate.key] for candidate in possible_keys]}"
        )
        return [
            CrackResult(
                value=cipheycore.xorcrypt_decrypt(ctext, candidate.key),
                key_info="0x" +
                "".join(["{:02x}".format(i) for i in candidate.key]),
            ) for candidate in possible_keys[:min(len(possible_keys), 10)]
        ]
Example #11
0
    def decoding(config: Config, route: Union[Cracker, Decoder], result: Any,
                 source: "Node") -> "Node":
        if not config.cache.mark_ctext(result):
            raise DuplicateNode()

        checker: Checker = config.objs["checker"]
        ret = Node(
            parent=None,
            level=SearchLevel(name=type(route).__name__.lower(),
                              result=CrackResult(value=result)),
            depth=source.depth + 1,
        )
        edge = Edge(source=source, route=route, dest=ret)
        ret.parent = edge
        check_res = checker(result)
        if check_res is not None:
            raise AuSearchSuccessful(target=ret, info=check_res)
        return ret
Example #12
0
    def attemptCrack(self, ctext: str) -> List[CrackResult]:
        logging.debug("Trying xortool cipher")
        # TODO handle different charsets
        # TODO allow more config over xortool

        logging.debug(f"{ctext}")

        # https://github.com/Ciphey/xortool/discussions/4
        # for docs on this function
        try:
            result = tool_main.api(str.encode(ctext))
        except:
            logging.debug("Xor failed.")
            return

        result = CrackResult(value=result[1]["Dexored"],
                             key_info=result[0]["keys"])

        return [result]
Example #13
0
 def crackOne(
     self, ctext: str, analysis: cipheycore.windowed_analysis_res, real_ctext: str
 ) -> List[CrackResult]:
     possible_keys = cipheycore.vigenere_crack(
         analysis, self.expected, self.group, self.p_value
     )
     if len(possible_keys) > self.clamp:
         possible_keys = possible_keys[:self.clamp]
     logger.trace(
         f"Vigenere crack got keys: {[[i for i in candidate.key] for candidate in possible_keys]}"
     )
     return [
         CrackResult(
             value=fix_case(cipheycore.vigenere_decrypt(ctext, candidate.key, self.group), real_ctext),
             key_info="".join([self.group[i] for i in candidate.key]),
             misc_info=f"p-value was {candidate.p_value}"
         )
         for candidate in possible_keys[: min(len(possible_keys), 10)]
     ]
Example #14
0
    def attemptCrack(self, ctext: T) -> List[CrackResult]:
        logger.debug(f"Starting to crack hashes")
        result = False

        candidates = []
        if len(ctext) == 32:
            for api in md5:
                r = api(ctext, "md5")
                if result is not None or r is not None:
                    logger.trace("MD5 returns True {r}")
                    candidates.append(result, "MD5")
        elif len(ctext) == 40:
            for api in sha1:
                r = api(ctext, "sha1")
                if result is not None and r is not None:
                    logger.trace("sha1 returns true")
                    candidates.append(result, "SHA1")
        elif len(ctext) == 64:
            for api in sha256:
                r = api(ctext, "sha256")
                if result is not None and r is not None:
                    logger.trace("sha256 returns true")
                    candidates.append(result, "SHA256")
        elif len(ctext) == 96:
            for api in sha384:
                r = api(ctext, "sha384")
                if result is not None and r is not None:
                    logger.trace("sha384 returns true")
                    candidates.append(result, "SHA384")
        elif len(ctext) == 128:
            for api in sha512:
                r = api(ctext, "sha512")
                if result is not None and r is not None:
                    logger.trace("sha512 returns true")
                    candidates.append(result, "SHA512")

        # TODO what the f**k is this code?
        logger.trace(f"Hash buster returning {result}")
        # TODO add to 5.1 make this return multiple possible candidates
        return [
            CrackResult(value=candidates[0][0], misc_info=candidates[1][1])
        ]
Example #15
0
    def handleDecodings(
        self, target: Any
    ) -> (bool, Union[Tuple[SearchLevel, str], List[SearchLevel]]):
        """
            If there exists a decoding that the checker returns true on, returns (True, result).
            Otherwise, returns (False, names and successful decodings)

            The CrackResult object should only have the value field filled in

            MUST NOT recurse into decodings! evaluate does that for you!
        """
        # This tag is necessary, as we could have a list as a decoding target, which would then screw over type checks
        ret = []

        decoders = []

        for decoder_type, decoder_class in registry[Decoder][type(
                target)].items():
            for decoder in decoder_class:
                decoders.append(DecoderComparer(decoder))
        # Fun fact:
        #     with Python's glorious lists, inserting n elements into the right position (with bisect) is O(n^2)
        decoders.sort(reverse=True)

        for decoder_cmp in decoders:
            logger.trace(f"Inspecting {decoder_cmp}")
            res = self._config()(decoder_cmp.value).decode(target)
            if res is None:
                continue
            level = SearchLevel(
                name=decoder_cmp.value.__name__.lower(),
                result=CrackResult(value=res),
            )
            if type(res) == self._final_type:
                check_res = self._checker(res)
                if check_res is not None:
                    return True, (level, check_res)
            ret.append(level)
        return False, ret