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
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
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
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
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")
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
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
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
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)] ]
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)] ]
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
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]
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)] ]
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]) ]
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