Crypto Tools

Notes from reading and function prototypes

Character-based string manipulation and statistics

from collections import defaultdict
import logging
from random import choice
from scipy.stats import chisquare
from string import ascii_lowercase as alphabet
from typing import List, Dict
def rot(given: str, n: int) -> str:
    """
    Passes anything that is not lowercase ascii
    """
    return "".join(
        [
            alphabet[(alphabet.index(letter.lower()) + n) % 26]
            if letter.lower() in alphabet
            else letter
            for letter in given
        ]
    )
def substitute(start: List[str], end: List[str], given: str) -> str:
    """
    Monoalphabetic substitution
    """
    if len(start) != len(end):
        raise ValueError("Both alphabets must be the same length")
    for i, x in enumerate(start):
        given = given.replace(end[i], x)
    return given
en_freq = {
    "e": 0.1201,
    "t": 0.0911,
    "a": 0.0813,
    "o": 0.0768,
    "i": 0.0731,
    "n": 0.0695,
    "s": 0.0628,
    "r": 0.0602,
    "h": 0.0592,
    "d": 0.0432,
    "l": 0.0398,
    "u": 0.0288,
    "c": 0.0271,
    "m": 0.0261,
    "f": 0.0230,
    "y": 0.0211,
    "w": 0.0209,
    "g": 0.0203,
    "p": 0.0182,
    "b": 0.0149,
    "v": 0.0111,
    "k": 0.0069,
    "x": 0.0017,
    "q": 0.0011,
    "j": 0.0010,
    "z": 0.0007,
}
def compare_frequency(given: str, lang_dict: Dict[str, float] = en_freq) -> float:
    """
    Calculate frequency distribution, test against language, return liklihood of belonging
    One sided Chi Squared Test: lower results signify that `given` more likely drew from the same distribution as `lang_dict`
    """
    given = given.replace(" ", "")
    freq = defaultdict()
    for letter in lang_dict.keys():
        freq[letter] = 0
    for letter in given:
        if letter.lower() in lang_dict.keys():
            freq[letter.lower()] += 1 / len(given)
        else:
            logging.warning(f"{letter} not in language alphabet")
    observed = [freq[k] for k in alphabet]
    expected = [lang_dict[k] for k in alphabet]
    return chisquare(observed, expected)[0]

Bit-based

Large number math shortcuts

# Fermat Primality Test -- (Paar, Pezl. Pg 189)
# probabalistic on s
# beware Carmichael numbers (false positives)


def is_prime_fermat(candidate: int, s: int) -> bool:
    candidate = abs(candidate)
    for i in range(s):
        try:
            alpha = choice(range(2, candidate - 2))
        except IndexError:  # lookup candidate <= 4
            if candidate in [2, 3]:
                return True
            else:
                return False
        if alpha ** (candidate - 1) % candidate != 1:
            return False
    return True
# Fast decryption with Chinese Remainder Theorem (CRT) -- (Paar, Pezl. Pg 184)


def mod_multiplicative_inverse(remainder: int, base: int) -> int:
    """
    What x solves: x * remainder % base == 1?
    """
    for x in range(1, base):
        if ((remainder % base) * (x % base)) % base == 1:
            return x
    return None


def decrypt_crt(ciphertext: int, p: int, q: int, d: int) -> int:
    """
    "total speedup obtained through CRT is a factor of 4" (Paar 186).
    """
    ciphertext_p = ciphertext % p
    ciphertext_q = ciphertext % q
    plain_p = ciphertext_p ** (d % (p - 1)) % p
    plain_q = ciphertext_q ** (d % (q - 1)) % q
    cp = mod_multiplicative_inverse(q, p)
    cq = mod_multiplicative_inverse(p, q)
    return (q * cp * plain_p + p * cq * plain_q) % (p * q)