deardaniel/secdata-raw huggingface dataset:
Cryptopals Attack Solution — S5C33:

from random import randint


def modular_pow(base, exponent, modulus):
    """Computes (base**exponent) % modulus by using the right-to-left binary method."""
    if modulus == -1:
        return 0

    result = 1
    base %= modulus

    while exponent > 0:
        if exponent % 2:
            result = (result * base) % modulus
        exponent >>= 1
        base = (base * base) % modulus

    return result


class DiffieHellman():
    """Implements the Diffie-Helman key exchange. Each class is a party, which has his secret key (usually
    referred to as lowercase a or b) shares the public key (usually referred to as uppercase A or B) and can
    compute the shared secret key between itself and another party, given their public key, assuming that
    they are agreeing on the same p and g.
    """

    DEFAULT_G = 2
    DEFAULT_P = int('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b225'
                    '14a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f4'
                    '4c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc20'
                    '07cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed5'
                    '[PII_REDACTED]6d670c354e4abc9804f1746c08ca237327ffffffffffffffff', 16)

    def __init__(self, g=DEFAULT_G, p=DEFAULT_P):
        self.g = g
        self.p = p
        self._secret_key = randint(0, p - 1)
        self.shared_key = None

    def get_public_key(self):
        return modular_pow(self.g, self._secret_key, self.p)

    def get_shared_secret_key(self, other_party_public_key):
        if self.shared_key is None:
            self.shared_key = modular_pow(other_party_public_key, self._secret_key, self.p)
        return self.shared_key


def main():
    dh1 = DiffieHellman()
    dh2 = DiffieHellman()

    # Check that our DiffieHellman implementation works and two parties will agree on the same key
    assert dh1.get_shared_secret_key(dh2.get_public_key()) == dh2.get_shared_secret_key(dh1.get_public_key())


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S2C10:

from base64 import b64decode
from Crypto.Cipher import AES
from S2C09 import pkcs7_pad, pkcs7_unpad
from S1C07 import aes_ecb_decrypt


def aes_ecb_encrypt(data, key):
    """Encrypts the given data with AES-ECB, using the given key.
    The data is always PKCS 7 padded before being encrypted.
    """
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.encrypt(pkcs7_pad(data, AES.block_size))


def xor_data(binary_data_1, binary_data_2):
    """Returns the xor of the two binary arrays given."""
    return bytes([b1 ^ b2 for b1, b2 in zip(binary_data_1, binary_data_2)])


def aes_cbc_encrypt(data, key, iv):
    """Encrypts the given data with AES-CBC, using the given key and iv."""
    ciphertext = b''
    prev = iv

    # Process the encryption block by block
    for i in range(0, len(data), AES.block_size):

        # Always PKCS 7 pad the current plaintext block before proceeding
        curr_plaintext_block = pkcs7_pad(data[i:i + AES.block_size], AES.block_size)
        block_cipher_input = xor_data(curr_plaintext_block, prev)
        encrypted_block = aes_ecb_encrypt(block_cipher_input, key)
        ciphertext += encrypted_block
        prev = encrypted_block

    return ciphertext


def aes_cbc_decrypt(data, key, iv, unpad=True):
    """Decrypts the given AES-CBC encrypted data with the given key and iv.
    Returns the unpadded decrypted message when unpad is true, or keeps the plaintext
    padded when unpad is false.
    """
    plaintext = b''
    prev = iv

    # Process the decryption block by block
    for i in range(0, len(data), AES.block_size):
        curr_ciphertext_block = data[i:i + AES.block_size]
        decrypted_block = aes_ecb_decrypt(curr_ciphertext_block, key)
        plaintext += xor_data(prev, decrypted_block)
        prev = curr_ciphertext_block

    # Return the plaintext either unpadded or left with the padding depending on the unpad flag
    return pkcs7_unpad(plaintext) if unpad else plaintext


def main():
    iv = b'\x00' * AES.block_size
    key = b'YELLOW SUBMARINE'
    with open("S2C10_input.txt") as input_file:
        binary_data = b64decode(input_file.read())

    # Compute and print the decrypted plaintext with the given input
    print(aes_cbc_decrypt(binary_data, key, iv).decode().rstrip())

    # Check that the encryption/decryption methods work fine with a custom input
    custom_input = b'Trying to decrypt something else to see if it works.'
    assert aes_cbc_decrypt(aes_cbc_encrypt(custom_input, key, iv), key, iv) == custom_input

if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S1C04:

from S1C03 import singlechar_xor_brute_force, pretty_print_result


def detect_encrypted_text(encrypted_strings):
    """Performs a singlechar XOR brute force attack to every ciphertext of the input, gets a plaintext
    from each of the ciphertexts and returns the decrypted plaintext which has the highest English score.
    """
    candidates = []

    for string in encrypted_strings:
        candidates.append(singlechar_xor_brute_force(string))

    # Return the candidate with the highest English score
    return sorted(candidates, key=lambda c: c['score'], reverse=True)[0]


def main():
    ciphertexts = [bytes.fromhex(line.strip()) for line in open("S1C04_input.txt")]
    most_likely_plaintext = detect_encrypted_text(ciphertexts)
    pretty_print_result(most_likely_plaintext)

    # Check that the attack works properly
    assert most_likely_plaintext['plaintext'].rstrip() == b"Now that the party is jumping"

if __name__ == "__main__":
    main()
Cryptopals Attack Solution — S4C28:

import struct
import hashlib


def left_rotate(value, shift):
    """Returns value left-rotated by shift bits. In other words, performs a circular shift to the left."""
    return ((value << shift) & 0xffffffff) | (value >> (32 - shift))


def sha1(message, ml=None, h0=0x67452301, h1=0xEFCDAB89, h2=0x98BADCFE, h3=0x10325476, h4=0xC3D2E1F0):
    """Returns a string containing the SHA1 hash of the input message. This is a pure python 3 SHA1
    implementation, written starting from the SHA1 pseudo-code on Wikipedia.

    The parameters ml, h0, ..., h5 are for the next challenge.
    """
    # Pre-processing:
    if ml is None:
        ml = len(message) * 8

    message += b'\x80'
    while (len(message) * 8) % 512 != 448:
        message += b'\x00'

    message += struct.pack('>Q', ml)

    # Process the message in successive 512-bit chunks:
    for i in range(0, len(message), 64):

        # Break chunk into sixteen 32-bit big-endian integers w[i]
        w = [0] * 80
        for j in range(16):
            w[j] = struct.unpack('>I', message[i + j * 4:i + j * 4 + 4])[0]

        # Extend the sixteen 32-bit integers into eighty 32-bit integers:
        for j in range(16, 80):
            w[j] = left_rotate(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1)

        # Initialize hash value for this chunk:
        a = h0
        b = h1
        c = h2
        d = h3
        e = h4

        # Main loop
        for j in range(80):
            if j <= 19:
                f = d ^ (b & (c ^ d))
                k = 0x5A827999
            elif 20 <= j <= 39:
                f = b ^ c ^ d
                k = 0x6ED9EBA1
            elif 40 <= j <= 59:
                f = (b & c) | (d & (b | c))
                k = 0x8F1BBCDC
            else:
                f = b ^ c ^ d
                k = 0xCA62C1D6

            temp = left_rotate(a, 5) + f + e + k + w[j] & 0xffffffff
            e = d
            d = c
            c = left_rotate(b, 30)
            b = a
            a = temp

        # Add this chunk's hash to result so far:
        h0 = (h0 + a) & 0xffffffff
        h1 = (h1 + b) & 0xffffffff
        h2 = (h2 + c) & 0xffffffff
        h3 = (h3 + d) & 0xffffffff
        h4 = (h4 + e) & 0xffffffff

    # Produce the final hash value (big-endian) as a 160 bit number, hex formatted:
    return '%08x%08x%08x%08x%08x' % (h0, h1, h2, h3, h4)


def sha1_mac(key, message):
    return sha1(key + message)


def main():
    key = b'THIs_iS-a_"SecRet"-kEy.'
    message = b'This is a message to test that our implementation of the SHA1 MAC works properly.'

    hashed = sha1_mac(key, message)

    # Verify that I implemented SHA1 correctly
    h = hashlib.sha1(key + message)
    assert (hashed == h.hexdigest())


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S3C24:

from S3C21 import MT19937
from random import randint
from Crypto import Random
from S2C10 import xor_data
import struct


class MT19937Cipher:

    def __init__(self, key):
        self._rng = MT19937(key)

    def encrypt(self, plaintext):
        """Uses the MT19937 PRNG to generate a keystream of enough bytes (at least as long as the
        text we want to encrypt/decrypt), and then XORs it with the input text.
        """
        keystream = b''

        # We use all the bits of the PRNG outputs (there is no need take just 16 bits per output)
        while len(keystream) < len(plaintext):
            keystream += struct.pack('>L', self._rng.extract_number())

        return xor_data(plaintext, keystream)

    def decrypt(self, ciphertext):
        """Decryption works the same as encryption."""
        return self.encrypt(ciphertext)


def find_mt19937_stream_cipher_key(ciphertext, known_plaintext):
    """Brute-force all possible 16-bit seeds (used as a key for the MT19937 stream cipher) until the
    ciphertext decrypts to a message containing our username (which is a known part of the plaintext).
    """
    print("> Brute-forcing all possible seeds...")

    for guessed_seed in range(2**16):
        candidate = MT19937Cipher(guessed_seed).decrypt(ciphertext)

        if known_plaintext in candidate:
            print("> Seed found:", guessed_seed)
            return guessed_seed

    # If after trying all the possible 16-bit seeds we still haven't
    # found the right one, it means that it was not a 16-bit number.
    raise Exception("The seed was not a 16 bit number")


def main():
    # Generate a random seed, which we'll use as a key
    seed = randint(0, 2 ** 16 - 1)

    # Generate the plaintext which will be encrypted to get the password token
    random_prefix = Random.new().read(randint(0, 100)) + b';'  # Small to make the cracking just slightly faster
    known_plaintext = b'ricpacca'                       # Username of someone willing to reset his password
    random_suffix = b';' + b'password_reset=true'       # Let's make it more realistic

    ciphertext = MT19937Cipher(seed).encrypt(random_prefix + known_plaintext + random_suffix)
    guessed_seed = find_mt19937_stream_cipher_key(ciphertext, known_plaintext)

    # Check that the attack worked and print the recovered plaintext
    assert guessed_seed == seed
    print("> Decrypted password reset plaintext:", MT19937Cipher(seed).encrypt(ciphertext))


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S5C39:

from Crypto.Util.number import getPrime


def int_to_bytes(n):
    """Converts the given int n to bytes and returns them."""
    return n.to_bytes((n.bit_length() + 7) // 8, 'big')


def gcd(a, b):
    """Computes the greatest common divisor between a and b using the Euclidean algorithm."""
    while b != 0:
        a, b = b, a % b

    return a


def lcm(a, b):
    """Computes the lowest common multiple between a and b using the GCD method."""
    return a // gcd(a, b) * b


def mod_inv(a, n):
    """Computes the multiplicative inverse of a modulo n using the extended Euclidean algorithm."""
    t, r = 0, n
    new_t, new_r = 1, a

    while new_r != 0:
        quotient = r // new_r
        t, new_t = new_t, t - quotient * new_t
        r, new_r = new_r, r - quotient * new_r

    if r > 1:
        raise Exception("a is not invertible")
    if t < 0:
        t = t + n

    return t


class RSA:
    """Implements the RSA public key encryption / decryption."""

    def __init__(self, key_length):
        """In this exercise, e is fixed to 3 so we will have to find p and q that fit the requirements."""
        self.e = 3
        phi = 0

        while gcd(self.e, phi) != 1:
            p, q = getPrime(key_length // 2), getPrime(key_length // 2)
            phi = lcm(p - 1, q - 1)
            self.n = p * q

        self._d = mod_inv(self.e, phi)

    def encrypt(self, binary_data):
        """Converts the input bytes to an int (bytes -> int) and then encrypts the int with RSA."""
        int_data = int.from_bytes(binary_data, byteorder='big')
        return pow(int_data, self.e, self.n)

    def decrypt(self, encrypted_int_data):
        """Decrypts the encrypted input data to an int and then converts it back to bytes (int -> bytes)."""
        int_data = pow(encrypted_int_data, self._d, self.n)
        return int_to_bytes(int_data)


def main():

    # Check that the implementation of mod inv is correct
    assert mod_inv(17, 3120) == 2753

    # Check that the implementation of RSA is correct
    rsa = RSA(1024)
    some_text = b"Hello, let's try if the RSA code works"
    assert rsa.decrypt(rsa.encrypt(some_text)) == some_text


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S4C31:

from binascii import hexlify
from statistics import median
import requests

# The attacker knows that the HMAC is 20 bytes long
HMAC_LEN = 20


def get_next_byte(known_bytes, filename, rounds):
    """Guesses the next byte of the HMAC for the given filename by performing a timing attack.
    The guess is done by making an average of the time taken by $rounds requests to the web-server.
    Because we guess that we web-server is using the insecure_compare function, we can guess that
    the requests that take the longest to be done are the ones with the correct byte.
    """

    # Count the number of zeros to add to our padding
    suffix_len = HMAC_LEN - len(known_bytes)

    # Initialize array counting the request times for every possible byte
    times = [[] for _ in range(256)]

    # For each byte, perform $rounds requests, so that we can have a better
    # statistical evidence of what requests take longer.
    for _ in range(rounds):

        # Try all possible bytes
        for i in range(256):
            suffix = bytes([i]) + (b'\x00' * (suffix_len - 1))
            signature = hexlify(known_bytes + suffix).decode()

            response = requests.get('http://localhost:8082/test?file=' + filename + '&signature=' + signature)

            # Just in case we found the correct signature already, return what we discovered
            if response.status_code == 200:
                return suffix

            times[i].append(response.elapsed.total_seconds())

    # Take the median of the requests times for each byte
    median_times = [median(byte_times) for byte_times in times]

    # Get the index of the item which took the highest median time for the requests
    best = max(range(256), key=lambda b: median_times[b])

    return bytes([best])


def discover_mac_with_timing_attack(filename, rounds):
    """Performs a timing attack on the HMAC server."""
    print("Timing attack started.")

    # Get the HMAC byte by byte
    known_bytes = b''
    while len(known_bytes) < HMAC_LEN:
        known_bytes += get_next_byte(known_bytes, filename, rounds)

        signature = hexlify(known_bytes).decode()
        print("Discovered so far:", signature)

    # Check if the HMAC we found is correct
    response = requests.get('http://localhost:8082/test?file=' + filename + '&signature=' + signature)

    if response.status_code == 200:
        print("\n> We made it! The HMAC is:", signature)
    else:
        print("\n> Unfortunately the attack did not work.")


def main():
    """Make sure that the web server S4C31_server is running.
    NOTE: This attack takes hours to finish.
    """

    # Correct HMAC for foo: 8c80a95a8e72b3e822a[PII_REDACTED]a433e267d8
    discover_mac_with_timing_attack("foo", 10)


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S5C40:

from S5C39 import RSA, mod_inv, int_to_bytes


def find_cube_root(n):
    """Finds the cube root of n using binary search."""
    lo = 0
    hi = n

    while lo < hi:
        mid = (lo + hi) // 2
        if mid**3 < n:
            lo = mid + 1
        else:
            hi = mid

    return lo


def rsa_broadcast_attack(ciphertexts):
    """Uses the Chinese Remainder Theorem (CRT) to break e=3 RSA given three ciphertexts of the same plaintext.
    This attack could be easily coded to work also when a different number of ciphertexts is provided.
    Check here for reference: https://crypto.stanford.edu/pbc/notes/numbertheory/crt.html
    """
    c0, c1, c2 = ciphertexts[0][0], ciphertexts[1][0], ciphertexts[2][0]
    n0, n1, n2 = ciphertexts[0][1], ciphertexts[1][1], ciphertexts[2][1]
    m0, m1, m2 = n1 * n2, n0 * n2, n0 * n1

    t0 = (c0 * m0 * mod_inv(m0, n0))
    t1 = (c1 * m1 * mod_inv(m1, n1))
    t2 = (c2 * m2 * mod_inv(m2, n2))
    c = (t0 + t1 + t2) % (n0 * n1 * n2)

    return int_to_bytes(find_cube_root(c))


def main():
    plaintext = b"Hello, I'm a Javascript programmer."

    ciphertexts = []
    for _ in range(3):
        rsa = RSA(1024)
        ciphertexts.append((rsa.encrypt(plaintext), rsa.n))

    assert rsa_broadcast_attack(ciphertexts) == plaintext


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S5C36:

from random import randint
from hashlib import sha256
from S5C33 import modular_pow
from S2C10 import xor_data
from requests import post


BASE_URL = "http://127.0.0.1:5000/"

# Generated using "openssl dhparam -text 1024".
N = int("008c5f8a80af99a7db03599f8dae8fb2f75b52501ef54a827b8a1a586f14dfb20d6b5e2ff878b9ad6bca0bb9"
        "18d30431fca1770760aa48be455cf5b949f3b86aa85a2573769e6c598f8d902cc1a0971a92e55b6e04c4d07e"
        "01ac1fa9bdefd1f04f95f197b000486c43917568ff58fafbffe12bde0c7e8f019fa1cb2b8e1bcb1f33", 16)

# Client and server agree on these values beforehand
g = 2
k = 3
I = 'ricpacca@crypto.com'
P = "PaS$w0rd"
a = randint(0, N - 1)


def hmac_sha256(key, message):
    """Returns the HMAC-SHA256 for the given key and message. Written following Wikipedia pseudo-code."""

    if len(key) > 64:
        key = sha256(key).digest()
    if len(key) < 64:
        key += b'\x00' * (64 - len(key))

    o_key_pad = xor_data(b'\x5c' * 64, key)
    i_key_pad = xor_data(b'\x36' * 64, key)

    return sha256(o_key_pad + sha256(i_key_pad + message).digest()).hexdigest()


def h(data):
    """Computes the sha1 hash of the input string and returns the integer corresponding to the output."""
    return int(sha256(data.encode()).hexdigest(), 16)


def srp():
    """Implements Secure Remote Password on the client side."""

    # Generate A (a la Diffie Hellman)
    A = modular_pow(g, a, N)
    response = post(BASE_URL, json={'I': I, 'A': A}).json()

    # Get B and salt from the server
    salt = response.get('salt')
    B = response.get('B')

    # Generate u
    u = h(str(A) + str(B))

    # Do the client processing
    x = h(salt + P)
    S = modular_pow(B - k * modular_pow(g, x, N), a + u * x, N)
    K = sha256(str(S).encode()).digest()

    # Compute HMAC
    hm = hmac_sha256(K, salt.encode())

    # Get the verification from the server
    response = post(BASE_URL, json={'hm': hm}).text
    return response


def main():
    outcome = srp()

    # Check that the implementation works
    assert outcome == "OK"


if __name__ == '__main__':
    main()
Cryptopals Attack Solution — S4C27:

from S2C10 import aes_cbc_decrypt
from S2C16 import find_block_length, find_prefix_length, Oracle
from S2C10 import xor_data


def check_ascii_compliance(plaintext):
    """Returns true if all the characters of plaintext are ASCII compliant (ie are in the ASCII table)."""
    return all(c < 128 for c in plaintext)


class LazyOracle(Oracle):
    """This oracle uses the key also as IV, which makes it insecure."""

    def __init__(self):
        super().__init__()
        self._iv = self._key    # Let's be lazy here

    def decrypt_and_check_admin(self, ciphertext):
        """Decrypts the ciphertext and: if the decrypted message is not ASCII compliant, raises an exception 
        and returns the bad plaintext; otherwise returns whether the characters ";admin=true;" are in the string.
        """
        plaintext = aes_cbc_decrypt(ciphertext, self._key, self._iv)

        if not check_ascii_compliance(plaintext):
            raise Exception("The message is not valid", plaintext)

        return b';admin=true;' in plaintext


def get_key_from_insecure_cbc(encryption_oracle):
    """Recovers the key from the lazy encryption oracle using the key also as iv.
    The approach used is the simple one outlined in the challenge description.
    """
    block_length = find_block_length(encryption_oracle.encrypt)
    prefix_length = find_prefix_length(encryption_oracle.encrypt, block_length)

    # Create three different blocks of plaintext and encrypt their concatenation
    p_1 = 'A' * block_length
    p_2 = 'B' * block_length
    p_3 = 'C' * block_length
    ciphertext = encryption_oracle.encrypt(p_1 + p_2 + p_3)

    # Force the ciphertext to be "C_1, 0, C_1"
    forced_ciphertext = ciphertext[prefix_length:prefix_length + block_length] + b'\x00' * block_length + \
                        ciphertext[prefix_length:prefix_length + block_length]

    # Expect an exception from the lazy oracle
    try:
        encryption_oracle.decrypt_and_check_admin(forced_ciphertext)
    except Exception as e:
        forced_plaintext = e.args[1]

        # Compute the key and return it
        # The first block of the plaintext will be equal to (decryption of c_1 XOR iv).
        # The last block of the plaintext will be equal to (decryption of c_1 XOR 0).
        # Therefore, to get the iv (which we know is equal to the key), we can just
        # xor the first and last blocks together.
        return xor_data(forced_plaintext[:block_length], forced_plaintext[-block_length:])

    raise Exception("Was not able to hack the key")


def main():
    encryption_oracle = LazyOracle()
    hacked_key = get_key_from_insecure_cbc(encryption_oracle)

    # Check that the key was recovered correctly
    assert encryption_oracle._key == hacked_key


if __name__ == '__main__':
    main()