1 AsymmetricEncryption


The mantra of any good security engineer is:
Security is a not a product, but a process.
It’s more than designing strong cryptography into a system;
it’s designing the entire system such that all security measures,
including cryptography, work together.”
- Bruce Schneier
https://en.wikipedia.org/wiki/Bruce_Schneier
https://www.schneier.com/

Discussion question:
Which parts of a computer system matter the most?
A system have high and low level parts.
Do the high or low level parts matter more?
Why?
Does any pattern always hold?

1.1 Reading

http://inventwithpython.com/cracking/ Chapter 24

Very good tutorial on RSA; read it!
http://doctrina.org/How-RSA-Works-With-Examples.html

Very good tutorial on RSA; read it!
http://doctrina.org/Why-RSA-Works-Three-Fundamental-Questions-Answered.html

1.2 Screencasts

1.3 Modern cryptography

1.3.1 Types of Encryption Schemes

Classification of Encryption Schemes

1.3.1.1 Symmetric vs. Asymmetric

A significant distinction.

1.3.1.1.1 Symmetric Key Encryption

Also referred to as private key encryption.
There is one private or secret key, which is generally shared between two parties.
Both encryption and decryption use the same key, and a similar process.
Key agreement and exchange can be a very challenging problem.
Relies on making a big mess of the data, using the pre-exchanged key, reversibly.
Generally computationally efficient to encrypt and decrypt (fast).
Examples of “modern” symmetric algorithms:
AES, DES, 3DES
Faster than asymmetric.

1.3.1.1.2 Asymmetric Key Encryption

It is also referred as public key encryption.
It has two keys: one public and one private.
The encryption operation uses only the public key,
which is known to everyone.
The decryption operation uses only the private key,
which is known to one person.
Modern innovation (newer algorithms).
Relies on math tricks (one way functions).
Moderately computationally expensive,
both in key-generation and in encryption and decryption.
Examples of “modern” asymmetric algorithms: RSA, DSA, ElGamal, NTRU.
Slower than asymmetric.

+++++++++++++++++++++
Cahoot-08.1

1.3.1.2 Computational vs. Information Theoretic

1.3.1.2.1 Computationally Secure

A computationally secure encryption scheme,
assumes the adversary has limited computing power.
The adversary can be assumed to solve polynomially-bounded problems,
but not to solve computationally infeasible NP-Complete, NP-hard, etc. problems
Almost all practical encryption schemes,
e.g., AES and RSA, are computationally secure.

1.3.1.2.2 Information Theoretically Secure

An information-theoretically secure encryption scheme,
assumes the adversary has unlimited computing power.
The adversary can be assumed to solve any computational problems
NP-Complete and NP-hard could hypothetically be efficiently solved,
and the scheme would still offer perfect security
Unconditional or perfect security.
One-time pad is information theoretically secure.
There are variants of the OTP,
but it’s the only one that fits this criteria.

1.4 Asymmetric key encryption

1.4.1 Asymmetric-Key Encryption Structure

Proposed by Diffie and Hellman in 1976 (quite recently!)
Based on mathematical one-way functions.

Q: What do we mean by one-way?
A: Those operations where the best algorithm to reverse them is a form of brute force.

Asymmetric schemes use two separate keys:
public key
private key
Public key is made public for others to use.

1.4.2 Asymmetric key terminology

Plaintext: Readable message or data that is fed into the algorithm as input
Encryption algorithm: Performs transformations on the plaintext
Public and private key: Pair of keys, one for encryption, one for decryption
Ciphertext: Scrambled message produced as output
Decryption key: Produces the original plaintext.
Q: Why seemingly redundant definition?
A: either can decrypt or encrypt
Decryption algorithm: Accepts the ciphertext and the matching key, and produces the original plaintext.

1.4.3 Requirements for asymmetric-Key Cryptosystems

Should be:

1.4.4 Requirements for asymmetric-Key Cryptosystems (more formal)

Computationally easy for a party B to generate a pair,
(public key PUb, ­private key PRb).

Computationally easy for a sender A,
knowing the public key and the ­message to be encrypted, M,
to generate the corresponding ciphertext:
C = E(PUb , M)

Computationally easy for the receiver B to decrypt the resulting ciphertext,
using the private key to recover the original message:
M = D(PRb ,C) = D[PRb, E(PUb, M)]

Computationally infeasible for an opponent,
knowing the public key, PUb,
to determine the private key, PRb

Computationally infeasible for an opponent,
knowing the public key, PUb, and a ciphertext, C,
to recover the original message, M.

Optionally, either of the two related keys can be used for encryption,
with the other used for decryption.
M = D[PUb, E(PRb, M)] = D[PRb, E(PUb, M)]
This is a neat feature, that you will see exists in RSA!

1.4.5 Applications

Meet Alice and Bob (and sometimes evil Eve)!
https://en.wikipedia.org/wiki/Alice_and_Bob
http://cryptocouple.com/

1.4.5.1 Encryption (top image below)

Each user generates pair of related keys, public and private.
ob encrypts a message using Alice’s public key.
Alice decrypts messages using her private key;
no other recipient can decrypt the message,
because only Alice knows Alice’s private key.

Go over this carefully:
AsymmetricEncryption/f6-crop-b.png

1.4.5.2 Digital signatures (bottom image above)

Bottom half: switch key roles

User encrypts data using his or her own private key.
Anyone who knows the corresponding public key will be able to decrypt the message.
Used for authenticating both:
source identity, and data integrity.
Created by encrypting hash code with private key.
Does not provide confidentiality,
even in the case of complete encryption.
Message is safe from alteration, but not eavesdropping.
Encryption is provided independently.

1.4.5.3 Applications for various public-Key cryptosystems

AsymmetricEncryption/image19.png

1.4.6 Asymmetric Encryption Algorithms

RSA (Rivest, Shamir, Adleman)
Developed in 1977.
This is the most widely accepted and implemented approach to public-key encryption.
Block cipher in which the plaintext and ciphertext are integers between 0 and n-1 for some n.

Diffie-Hellman key exchange algorithm
Enables two users to securely reach agreement about a shared secret,
that can be used as a secret key for subsequent symmetric encryption of messages.
Most widely accepted and implemented approach to key exchange.
Limited to the exchange of the keys, with no direct encryption.

Digital Signature Standard (DSS)
Provides only a digital signature function with SHA-1.
Cannot be used for encryption or key exchange.

Elliptic curve cryptography (ECC)
Security like RSA, but with much smaller keys for similar security.

1.5 Textbook RSA

Read Chapter 9 and all Appendices:
https://github.com/crypto101/crypto101.github.io/raw/master/Crypto101.pdf

https://en.wikipedia.org/wiki/RSA_(cryptosystem)
https://simple.wikipedia.org/wiki/RSA_algorithm

Video series detailing the algorithm:
https://www.khanacademy.org/computing/computer-science/cryptography/modern-crypt/v/intro-to-rsa-encryption

1.5.1 RSA Public-Key Encryption

Publically invented by Rivest, Shamir, and Adleman of MIT in 1977.
For a time, it was the best known, and widely used public-key algorithm.
Uses exponentiation of integers, modulo some prime.

1.5.1.1 Example of RSA

For a moment, let’s see the magic:

AsymmetricEncryption/f6-crop.png
How?

1.5.2 RSA Algorithm

Key generation involves multiple steps of repeated generate and test.
AsymmetricEncryption/02.png

1.5.3 Key Generation

Each key in the public key scheme is made of two numbers.
The public key will be two numbers, n and e.
The private key will be two numbers, n and d.

The steps to create these numbers follow:

Generate many random numbers,
until you find two that are distinct, very very large, and prime:
p and q

Multiply these two numbers,
p x q = n (n will then be the modulus for encryption).
Also, multiply these two numbers minus 1,
(p - 1) × (q - 1)
to get a number called \(\phi(n)\) (the modulus for key generation).

Generate many more random numbers,
until you find another number, which we will call e,
which is relatively prime to n.
This is part of the public key.

Calculate the modular inverse of e, which we’ll call d.
This is part of the private key.

Side note:
In practice, e is often just chosen as 65537, or some other fixed value…

Notice that the modulus, n, is used in both keys.
By convention, the d number must be kept secret,
as only it can decrypt messages encrypted to e,
which is pubic information.

More formally, generate

numbers p and q, where
where p and q are very very large primes, with roughly similar sizes, and
n = p * q
The size of p or q needs to be at least 2,048 bits,
though 4096 is a practical current upper limit in most implementations.

Plaintext space:
Zn

Ciphertext space:
Zn

Public key:
n, e, where,
e is an integer relatively prime to \(\phi(n)\)

Private key:
d where
\(ed \equiv 1 \mod \phi(n)\)

1.5.4 Encryption and Decryption Operations

Each done in sequence, in either direction, will cancel the other out!

Encryption: E(x, n, e) = xe mod n

Decryption: D(c, n, d) = cd mod n
= xed mod n
= x1

The above formulation is an over-simplification of the real proof,
but illustrates a key theoretical point.

1.5.4.1 Example

Iteratively, pick two primes:
p = 11
q = 17

Multiply (p - 1) × (q - 1):
n = 11 * 17 = 187
\(\phi(n)\) = 10 * 16 = 160

Iteratively with GCD Euclidean,
pick a relative prime to \(\phi(n)\):
e = 3

Using Extended Euclidean,
compute the inverse of e mod \(\phi(n)\):
d = 107

Write your hypothetical message:
Plaintext character encoded here as x = 10

Encrypt, producing ciphertext:
c = 103 mod 187 = 65

To decrypt C,
compute: D(c, e):
65107 mod 187 = 10

1.5.5 Components

Some are public.
Some are secret.

1.5.5.1 Public

n - the modulus
e - the public exponent
c - the cipher text

1.5.5.2 Secret

p - the prime factor of n
q - the other prime factor of n
\(\phi(n)\) - Euler’s totient of n
d - the private exponent

+++++++++++++++++++++
Cahoot-08.2

1.5.6 Security Guarantee of RSA

Brute force:
Involves trying all possible private keys.

Mathematical attacks:
There are several approaches,
all equivalent in effort to factoring the product of two primes.

Timing attacks:
These extract statistics based on on analyzing the running time of the decryption algorithm.

Chosen cipehrtext attacks:
This type of attack exploits properties of the RSA algorithm,
given some oracle,
which can produce arbitrary encrypted data for the attacker.

1.5.6.1 Breaking RSA

The easiest way we know how to break RSA is to factor n back into p * q,
which is a prime factorization…
After that, given p and q, the attacker could just repeat the analytical process,
that the legitimate owner of the key does during key generation,
in order to compute the private exponent d.

Fortunately, we don’t have an algorithm that can factor such large numbers in reasonable time.
Unfortunately, we also haven’t proven it doesn’t exist.

Even more unfortunately, is that there is a theoretical algorithm, called Shor’s algorithm,
that would be able to factor such a number in reasonable time on a quantum computer.

There is another memristive solution (non-Turing),
implemented in application-specific hardware,
that can factor primes in polynomial resources (energy).

Right now, quantum and memristive solutions are far from practical,
but it does appear that, if someone in the future manages to build one sufficiently large,
that RSA becomes ineffective.

Ask and discuss:
Does this just break RSA, or other algorithms too?

1.5.6.2 Factorization of large integers

RSA Labs (http://www.rsasecurity.com/rsalabs/) sponsored a challenge to factor some large n:

https://en.wikipedia.org/wiki/RSA_Factoring_Challenge

Progress in Factorization:
AsymmetricEncryption/03.png
What is a modern size?
How much bigger and better is it?

https://en.wikipedia.org/wiki/Integer_factorization_records

+++++++++++++++++++++
Cahoot-08.3

1.5.7 Python Code

AsymmetricEncryption/cryptomath.py

AsymmetricEncryption/primeNum.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Prime Number Sieve
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

import math, random
from typing import List


def isPrimeTrialDiv(num: int) -> bool:
    """
    Returns True if num is a prime number, otherwise False.
    Uses the trial division algorithm for testing primality.
    """
    # All numbers less than 2 are not prime:
    if num < 2:
        return False

    # See if num is divisible by any number up to the square root of num:
    for i in range(2, int(math.sqrt(num)) + 1):
        if num % i == 0:
            return False
    return True


def primeSieve(sieveSize: int) -> List[int]:
    """
    Returns a list of prime numbers calculated using
    the Sieve of Eratosthenes algorithm.
    """
    sieve = [True] * sieveSize
    sieve[0] = False  # Zero and one are not prime numbers.
    sieve[1] = False

    # Create the sieve:
    for i in range(2, int(math.sqrt(sieveSize)) + 1):
        pointer = i * 2
        while pointer < sieveSize:
            sieve[pointer] = False
            pointer += i

    # Compile the list of primes:
    primes = []
    for i in range(sieveSize):
        if sieve[i] == True:
            primes.append(i)

    return primes


def rabinMiller(num: int) -> bool:
    """Returns True if num is a prime number."""
    if num % 2 == 0 or num < 2:
        return False  # Rabin-Miller doesn't work on even integers.
    if num == 3:
        return True
    s = num - 1
    t = 0
    while s % 2 == 0:
        # Keep halving s until it is odd (and use t
        # to count how many times we halve s):
        s = s // 2
        t += 1
    for trials in range(5):  # Try to falsify num's primality 5 times.
        a = random.randrange(2, num - 1)
        v = pow(a, s, num)
        if v != 1:  # (This test does not apply if v is 1.)
            i = 0
            while v != (num - 1):
                if i == t - 1:
                    return False
                else:
                    i = i + 1
                    v = (v**2) % num
    return True


# Most of the time we can quickly determine if num is not prime
# by dividing by the first few dozen prime numbers. This is quicker
# than rabinMiller(), but does not detect all composites.
LOW_PRIMES = primeSieve(100)


def isPrime(num: int) -> bool:
    """
    Return True if num is a prime number. This function does a quicker
    prime number check before calling rabinMiller().
    """
    if num < 2:
        return False  # 0, 1, and negative numbers are not prime.
    # See if any of the low prime numbers can divide num:
    for prime in LOW_PRIMES:
        if num == prime:
            return True
        if num % prime == 0:
            return False
    # If all else fails, call rabinMiller() to determine if num is a prime:
    return rabinMiller(num)


def generateLargePrime(keysize: int = 1024) -> int:
    """Return a random prime number that is keysize bits in size."""
    while True:
        num = random.randrange(2 ** (keysize - 1), 2 ** (keysize))
        if isPrime(num):
            return num

AsymmetricEncryption/makeRSAKeys.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Public Key Generator
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

import random, sys, os, primeNum, cryptomath
from typing import Tuple


def main() -> None:
    # Create a public/private keypair with 1024 bit keys:
    print("Making key files...")
    makeKeyFiles("al_sweigart", 1024)
    print("Key files made.")


def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]:
    # Creates a public/private keys keySize bits in size.
    p = 0
    q = 0
    # Step 1: Create two prime numbers, p and q. Calculate n = p * q.
    print("Generating p & q primes...")
    while p == q:
        p = primeNum.generateLargePrime(keySize)
        q = primeNum.generateLargePrime(keySize)
    n = p * q

    # Step 2: Create a number e that is relatively prime to (p-1)*(q-1):
    print("Generating e that is relatively prime to (p-1)*(q-1)...")
    phiN = (p - 1) * (q - 1)
    while True:
        # Keep trying random numbers for e until one is valid:
        e = random.randrange(2 ** (keySize - 1), 2 ** (keySize))
        if cryptomath.gcd(e, phiN) == 1:
            break

    # Step 3: Calculate d, the mod inverse of e:
    print("Calculating d that is mod inverse of e...")
    d = cryptomath.findModInverse(e, phiN)

    publicKey = (n, e)
    privateKey = (n, d)

    print("Public key:", publicKey)
    print("Private key:", privateKey)

    return (publicKey, privateKey)


def makeKeyFiles(name: str, keySize: int) -> None:
    # Creates two files 'x_pubkey.txt' and 'x_privkey.txt' (where x
    # is the value in name) with the n,e and d,e integers written in
    # them, delimited by a comma.

    # Our safety check will prevent us from overwriting our old key files:
    if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists(
        "%s_privkey.txt" % (name)
    ):
        sys.exit(
            "Files %s_pubkey.txt or %s_privkey.txt already exists! Use a different name, or delete keys and re-run."
            % (name, name)
        )

    publicKey, privateKey = generateKey(keySize)

    print(
        "\nThe public key is a %s and a %s digit number."
        % (len(str(publicKey[0])), len(str(publicKey[1])))
    )
    print("Writing public key to file %s_pubkey.txt..." % (name))
    with open("%s_pubkey.txt" % (name), "w") as fo:
        fo.write("%s,%s,%s" % (keySize, publicKey[0], publicKey[1]))

    print(
        "\nThe private key is a %s and a %s digit number."
        % (len(str(publicKey[0])), len(str(publicKey[1])))
    )
    print("Writing private key to file %s_privkey.txt..." % (name))
    with open("%s_privkey.txt" % (name), "w") as fo:
        fo.write("%s,%s,%s" % (keySize, privateKey[0], privateKey[1]))


# If makePublicPrivateKeys.py is run (instead of imported as a module),
# call the main() function:
if __name__ == "__main__":
    main()

AsymmetricEncryption/RSACipher.py

#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Public Key Cipher
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)

import sys, math
from typing import List, Optional, Tuple

# The public and private keys for this program are created by
# the makePublicPrivateKeys.py program.
# This program must be run in the same folder as the key files.

SYMBOLS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?."


def main() -> None:
    # Runs a test that encrypts a message to a file or decrypts a message from a file.
    filename = "encrypted_file.txt"  # The file to write to/read from.
    # mode = "decrypt"  # Set to either 'encrypt' or 'decrypt'.
    mode = input("Type 'encrypt' or 'decrypt' and hit enter: ")

    if mode == "encrypt":
        message = "Journalists belong in the gutter because that is where the ruling classes throw their guilty secrets. Gerald Priestland. The Founding Fathers gave the free press the protection it must have to bare the secrets of government and inform the people. Hugo Black."
        pubKeyFilename = "al_sweigart_pubkey.txt"
        print("Encrypting and writing to %s..." % (filename))
        encryptedText = encryptAndWriteToFile(filename, pubKeyFilename, message)

        print("Encrypted text:")
        print(encryptedText)

    elif mode == "decrypt":
        privKeyFilename = "al_sweigart_privkey.txt"
        print("Reading from %s and decrypting..." % (filename))
        decryptedText = readFromFileAndDecrypt(filename, privKeyFilename)

        print("Decrypted text:")
        print(decryptedText)


def getBlocksFromText(message: str, blockSize: int) -> List[int]:
    # Converts a string message to a list of block integers.
    for character in message:
        if character not in SYMBOLS:
            print("ERROR: The symbol set does not have the character %s" % (character))
            sys.exit()
    blockInts = []
    for blockStart in range(0, len(message), blockSize):
        # Calculate the block integer for this block of text:
        blockInt = 0
        for i in range(blockStart, min(blockStart + blockSize, len(message))):
            blockInt += (SYMBOLS.index(message[i])) * (len(SYMBOLS) ** (i % blockSize))
        blockInts.append(blockInt)
    return blockInts


def getTextFromBlocks(blockInts: List[int], messageLength: int, blockSize: int) -> str:
    # Converts a list of block integers to the original message string.
    # The original message length is needed to properly convert the last
    # block integer.
    message: List[str] = []
    for blockInt in blockInts:
        blockMessage: List[str] = []
        for i in range(blockSize - 1, -1, -1):
            if len(message) + i < messageLength:
                # Decode the message string for the 128 (or whatever
                # blockSize is set to) characters from this block integer:
                charIndex = blockInt // (len(SYMBOLS) ** i)
                blockInt = blockInt % (len(SYMBOLS) ** i)
                blockMessage.insert(0, SYMBOLS[charIndex])
        message.extend(blockMessage)
    return "".join(message)


def encryptMessage(message: str, key: Tuple[int, int], blockSize: int) -> List[int]:
    # Converts the message string into a list of block integers, and then
    # encrypts each block integer. Pass the PUBLIC key to encrypt.
    encryptedBlocks = []
    n, e = key

    for block in getBlocksFromText(message, blockSize):
        # ciphertext = plaintext ^ e mod n
        encryptedBlocks.append(pow(block, e, n))
    return encryptedBlocks


def decryptMessage(
    encryptedBlocks: List[int], messageLength: int, key: Tuple[int, int], blockSize: int
) -> str:
    # Decrypts a list of encrypted block ints into the original message
    # string. The original message length is required to properly decrypt
    # the last block. Be sure to pass the PRIVATE key to decrypt.
    decryptedBlocks = []
    n, d = key
    for block in encryptedBlocks:
        # plaintext = ciphertext ^ d mod n
        decryptedBlocks.append(pow(block, d, n))
    return getTextFromBlocks(decryptedBlocks, messageLength, blockSize)


def readKeyFile(keyFilename: str) -> Tuple[int, int, int]:
    # Given the filename of a file that contains a public or private key,
    # return the key as a (n,e) or (n,d) tuple value.
    with open(keyFilename) as fo:
        content = fo.read()
    keySize, n, EorD = content.split(",")
    return (int(keySize), int(n), int(EorD))


def encryptAndWriteToFile(
    messageFilename: str,
    keyFilename: str,
    message: str,
    blockSize: Optional[int] = None,
) -> str:
    # Using a key from a key file, encrypt the message and save it to a
    # file. Returns the encrypted message string.
    keySize, n, e = readKeyFile(keyFilename)
    if blockSize is None:
        # If blockSize isn't given, set it to the largest size allowed by the key size and symbol set size.
        blockSize = int(math.log(2**keySize, len(SYMBOLS)))
    # Check that key size is large enough for the block size:
    if not (math.log(2**keySize, len(SYMBOLS)) >= blockSize):
        sys.exit(
            "ERROR: Block size is too large for the key and symbol set size. Did you specify the correct key file and encrypted file?"
        )
    # Encrypt the message:
    encryptedBlocks = encryptMessage(message, (n, e), blockSize)

    # Convert the large int values to one string value:
    # for i in range(len(encryptedBlocks)):
    #     encryptedBlocks[i] = str(encryptedBlocks[i])
    # encryptedContent = ",".join(encryptedBlocks)
    encryptedContent = ",".join(map(str, encryptedBlocks))

    # Write out the encrypted string to the output file:
    encryptedContent = "%s_%s_%s" % (len(message), blockSize, encryptedContent)
    with open(messageFilename, "w") as fo:
        fo.write(encryptedContent)
    # Also return the encrypted string:
    return encryptedContent


def readFromFileAndDecrypt(messageFilename: str, keyFilename: str) -> str:
    # Using a key from a key file, read an encrypted message from a file
    # and then decrypt it. Returns the decrypted message string.
    keySize, n, d = readKeyFile(keyFilename)

    # Read in the message length and the encrypted message from the file:
    with open(messageFilename) as fo:
        content = fo.read()
    messageLength_str, blockSize_str, encryptedMessage = content.split("_")
    messageLength = int(messageLength_str)
    blockSize = int(blockSize_str)

    # Check that key size is large enough for the block size:
    if not math.log(2**keySize, len(SYMBOLS)) >= blockSize:
        sys.exit(
            "ERROR: Block size is too large for the key and symbol set size. Did you specify the correct key file and encrypted file?"
        )

    # Convert the encrypted message into large int values:
    encryptedBlocks = []
    for block in encryptedMessage.split(","):
        encryptedBlocks.append(int(block))

    # Decrypt the large int values:
    return decryptMessage(encryptedBlocks, messageLength, (n, d), blockSize)


# If publicKeyCipher.py is run (instead of imported as a module) call
# the main() function.
if __name__ == "__main__":
    main()

Ask:
What line does the encryption?

Hint:
$ python3
>>> help(pow)

pow(x, y, z=None)
    Equivalent to x**y (with two arguments)
    or x**y % z (with three arguments).
    Some types, such as ints,
    are able to use a more efficient algorithm,
    when invoked using the three argument form.

Ask and discuss:
How do you take a string like 'Hello RSA’ to a power??

In order to encrypt text or data,
it must be turned into a number.
A method with blocks is used above.
How the text is turned into a number differs by implementation,
and is conceptually independent of the core RSA algorithm,
though it impacts the security!

1.5.8 Haskell Code:

AsymmetricEncryption/CryptoMath.hs

{-# LANGUAGE BangPatterns #-}

module CryptoMath where

fastpow :: Integer -> Integer -> Integer -> Integer
fastpow base exp modulo =
  let fastpow' b 0 m !r = r
      fastpow' b e m r = fastpow' (mod (b * b) m) (div e 2) m (if even e then r else mod (r * b) m)
   in fastpow' (mod base modulo) exp modulo 1

-- |
-- >>> gcd' 55 30
-- 5
gcd' :: Integer -> Integer -> Integer
gcd' a b =
  if mod a b == 0
    then b
    else gcd' b (mod a b)

-- |
-- >>> modInv 5 66
-- Just 53

-- |
-- >>> modInv 6 66
-- Nothing
modInv :: Integer -> Integer -> Maybe Integer
modInv a m
  | 1 == g = Just (mkPos i)
  | otherwise = Nothing
  where
    (i, _, g) = gcdExt a m
    mkPos x
      | x < 0 = x + m
      | otherwise = x

-- Extended Euclidean algorithm.
-- Given non-negative a and b, return x, y and g
-- such that ax + by = g, where g = gcd(a,b).
-- Note that x or y may be negative.
gcdExt :: Integer -> Integer -> (Integer, Integer, Integer)
gcdExt a 0 = (1, 0, a)
gcdExt a b =
  let (q, r) = quotRem a b
      (s, t, g) = gcdExt b r
   in (t, s - q * t, g)

AsymmetricEncryption/RabinMiller.hs

#!/usr/bin/runghc

module RabinMiller where

-- https://programmingpraxis.com/wp-content/uploads/2012/09/primenumbers.pdf
-- https://programmingpraxis.com/programming-with-prime-numbers-source-code-in-haskell/

import Control.Monad (forM_, when)
import Control.Monad.ST
import Data.Array.ST
import Data.Array.Unboxed
import Data.List (sort)

ancientSieve :: Int -> UArray Int Bool
ancientSieve n = runSTUArray $ do
  bits <- newArray (2, n) True
  forM_ [2 .. n] $ \p -> do
    isPrime <- readArray bits p
    when isPrime $ do
      forM_ [2 * p, 3 * p .. n] $ \i -> do
        writeArray bits i False
  return bits

ancientPrimes :: Int -> [Int]
ancientPrimes n =
  [ p
    | (p, True) <-
        assocs $ ancientSieve n
  ]

sieve :: Int -> UArray Int Bool
sieve n = runSTUArray $ do
  let m = (n - 1) `div` 2
      r = floor . sqrt $ fromIntegral n
  bits <- newArray (0, m - 1) True
  forM_ [0 .. r `div` 2 - 1] $ \i -> do
    isPrime <- readArray bits i
    when isPrime $ do
      let a = 2 * i * i + 6 * i + 3
          b = 2 * i * i + 8 * i + 6
      forM_ [a, b .. (m - 1)] $ \j -> do
        writeArray bits j False
  return bits

primes :: Int -> [Int]
primes n = 2 : [2 * i + 3 | (i, True) <- assocs $ sieve n]

tdPrime :: Int -> Bool
tdPrime n = prime (2 : [3, 5 ..])
  where
    prime (d : ds)
      | n < d * d = True
      | n `mod` d == 0 = False
      | otherwise = prime ds

tdFactors :: Int -> [Int]
tdFactors n = facts n (2 : [3, 5 ..])
  where
    facts n (f : fs)
      | n < f * f = [n]
      | n `mod` f == 0 =
          f : facts (n `div` f) (f : fs)
      | otherwise = facts n fs

powmod :: Integer -> Integer -> Integer -> Integer
powmod b e m =
  let times p q = (p * q) `mod` m
      pow b e x
        | e == 0 = x
        | even e =
            pow
              (times b b)
              (e `div` 2)
              x
        | otherwise =
            pow
              (times b b)
              (e `div` 2)
              (times b x)
   in pow b e 1

isSpsp :: Integer -> Integer -> Bool
isSpsp n a =
  let getDandS d s =
        if even d
          then getDandS (d `div` 2) (s + 1)
          else (d, s)
      spsp (d, s) =
        let t = powmod a d n
         in if t == 1 then True else doSpsp t s
      doSpsp t s
        | s == 0 = False
        | t == (n - 1) = True
        | otherwise = doSpsp ((t * t) `mod` n) (s - 1)
   in spsp $ getDandS (n - 1) 0

isPrime :: Integer -> Bool
isPrime n =
  let ps =
        [ 2,
          3,
          5,
          7,
          11,
          13,
          17,
          19,
          23,
          29,
          31,
          37,
          41,
          43,
          47,
          53,
          59,
          61,
          67,
          71,
          73,
          79,
          83,
          89,
          97
        ]
   in n `elem` ps || all (isSpsp n) ps

rhoFactor :: Integer -> Integer -> Integer
rhoFactor n c =
  let f x = (x * x + c) `mod` n
      fact t h
        | d == 1 = fact t' h'
        | d == n = rhoFactor n (c + 1)
        | isPrime d = d
        | otherwise = rhoFactor d (c + 1)
        where
          t' = f t
          h' = f (f h)
          d = gcd (t' - h') n
   in fact 2 2

rhoFactors :: Integer -> [Integer]
rhoFactors n =
  let facts n
        | n == 2 = [2]
        | even n = 2 : facts (n `div` 2)
        | isPrime n = [n]
        | otherwise =
            let f = rhoFactor n 1
             in f : facts (n `div` f)
   in sort $ facts n

-- main = do
--  print $ ancientPrimes 100
--  print $ primes 100
--  print $ length $ primes 1000000
--  print $ tdPrime 716151937
--  print $ tdFactors 8051
--  print $ powmod 437 13 1741
--  print $ isSpsp 2047 2
--  print $ isPrime 600851475143
--  print $ isPrime 2305843009213693951
--  print $ rhoFactors 600851475142

AsymmetricEncryption/RSACipher.hs

#!/usr/bin/runghc

import CryptoMath
import Data.Char
import Data.Maybe (fromJust)
import RabinMiller
import System.Random

-- Note: The keysize here is 2^64, so you can see the numbers on the screen. 
-- Normal key size is 2^4096...

genLargePrime :: IO Integer
genLargePrime = do
  bigRand <- randomRIO (2 ^ 63, 2 ^ 64) :: IO Integer
  if isPrime bigRand then return bigRand else genLargePrime

genPQ :: IO (Integer, Integer)
genPQ = do
  p <- genLargePrime
  q <- genLargePrime
  if p /= q then return (p, q) else genPQ

genCoPrime :: Integer -> IO Integer
genCoPrime n = do
  bigRand <- randomRIO (2 ^ 63, 2 ^ 64) :: IO Integer
  if gcd' bigRand n == 1 then return bigRand else genCoPrime n

genKeys :: IO ((Integer, Integer), (Integer, Integer))
genKeys = do
  (p, q) <- genPQ
  let n = p * q
      phiN = (p - 1) * (q - 1)
  e <- genCoPrime phiN
  let d = fromJust (modInv e phiN)
  return ((n, e), (n, d))

encryptBlock :: (Integer, Integer) -> Integer -> Integer
encryptBlock (n, e) block = fastpow block e n

decryptBlock :: (Integer, Integer) -> Integer -> Integer
decryptBlock (n, d) block = fastpow block d n

main :: IO ()
main = do
  let message = "This is a small message."
  putStrLn ("Message was: " ++ message)

  let codePoints = map (toInteger . ord) message
  putStrLn ("\nCodePoints were: " ++ show codePoints)

  (pubKey, privKey) <- genKeys
  putStrLn ("\npubKey was: " ++ show pubKey)
  putStrLn ("\nprivKey was: " ++ show privKey)

  let encryptedPoints = map (encryptBlock pubKey) codePoints
  putStrLn ("\nEncrypted message: " ++ show encryptedPoints)

  let decryptedPoints = map (decryptBlock privKey) encryptedPoints
  putStrLn ("\nDecrypted points: " ++ show decryptedPoints)

  let decryptedText = map (chr . fromIntegral) decryptedPoints
  putStrLn ("\nDecrypted text: " ++ decryptedText)

1.5.9 Textbook RSA

To make textbook RSA actually secure,
padding on the encoded plaintext must be used:
https://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding

1.5.10 Key compromise?

If your RSA key is compromised,
what happens to past communications?
Should you keep using the key?

Forward secrecy protects the past…
This is something of a misnomer.

1.6 Diffie-Hellman

https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
https://en.m.wikipedia.org/wiki/Multiplicative_group_of_integers_modulo_n
https://en.wikipedia.org/wiki/Discrete_logarithm_problem

1.6.1 Background: “Primitive root”

https://en.m.wikipedia.org/wiki/Primitive_root_modulo_n
https://en.m.wikipedia.org/wiki/Generating_set_of_a_group

In modular arithmetic,
some number g, is a primitive root modulo n,
if every number, a, co-prime to n,
is congruent to a power of g modulo n.

That is, g is a primitive root modulo n,
if for every integer, a, co-prime to n,
there is an integer, k,
such that \(g^k \equiv a \mod n\).

For example, g = 3, mod 7:

AsymmetricEncryption/primitive_root.svg
Here we see that the period of 3k modulo 7 is 6.
The remainders in the period,
which are 3, 2, 6, 4, 5, 1,
form a rearrangement of all nonzero remainders modulo 7,
implying that g = 3 is indeed a primitive root modulo 7.

1.6.2 Diffie-Hellman and Key Exchange

It was the first published public-key algorithm,
by Diffie and Hellman in 1976.
They first explained public key concepts.
It is used in a number of applications.
It is a practical method to exchange a secret key securely,
that can then be used for subsequent encryption of messages.
Security relies on difficulty of computing discrete logarithms.

Mention:
We previewed this “baby Diffie-Hellman idea before,
using nothing but exponents and log:
CryptoMath.html

1.6.3 Algorithm

Two people, Alice (A) annd Bob (B).

Example Public Parameters:
Prime number: q = 353
Primitive root of q: \(\alpha = 3\)

Example Private Parameters:
Alice randomly selects XA = 97
Bob randomly selects XB = 233

Alice and Bob each compute their public keys:
Alice computes YA = 397 mod 353 = 40
Bob computes YB = 3233 mod 353 = 248

Then exchange the public keys and compute secret key:

Alice:
\(K = (Y_B)^{X_A} \mod 353 = 248^{97} \mod 353 = 160\)

Bob:
\(K = (Y_A)^{X_B} \mod 353 = 40^{233} \mod 353 = 160\)

160 is the shared secret they both possess.

Diffie-Hellman Key Exchange, Generation and Computation
AsymmetricEncryption/04.png

Diffie-Hellman Key Exchange
AsymmetricEncryption/f8-crop.png

1.6.3.1 What is secret?

red = secret
blue = public
AsymmetricEncryption/dh_chart.png

1.6.4 Man-in-the-Middle Attack (MITM)

(against DH, RSA, or any other of these algorithms)
https://en.wikipedia.org/wiki/Man-in-the-middle_attack

Example of evil Eve in the middle:
AsymmetricEncryption/DH-mitm.png
1. Alice transmits YA to Bob.
2. Eve generates private keys XD1 and XD2, and their public keys YD1 and YD2
3. Eve intercepts YA and transmits YD1 to Bob.
4. Eve also calculates \(K2 = (Y_A)^{X_{D2}}\)
5. Bob receives YD1 and calculates \(K1 = (Y_{D1})^{X_B}\)
6. Bob transmits YB to Alice
7. Eve intercepts YB and transmits YD2 to Alice
8. Eve calculates \(K1 = (Y_B)^{X_{D1}}\)
9. Alice receives YD2 and calculates \(K2 = (Y_{D2})^{X_A}\)

If Alice and Bob were using these DH secrets as single-session private keys,
which communications are compromised, past, present, or future?

+++++++++++++++++++++
Cahoot-08.4

When setting up your web server:
Use DH-* with TLS for this nice feature:
https://en.wikipedia.org/wiki/Transport_Layer_Security#Key_exchange_or_key_agreement
More to come later.

1.7 Other Public-Key Algorithms

1.7.1 Digital Signature Standard (DSS)

1.7.2 Elliptic-Curve Cryptography (ECC)

1.8 Cracking prime-based cryptography

Using something like super-Turing solutions:

1.8.1 Shor’s algorithm

Which algorithms would primarily be directly vulnerable,
symmetric or asymmetric?
Even if both are not, does it matter?
If you are interested:
https://www.youtube.com/watch?v=lvTqbM5Dq4Q

1.8.2 Memristive solution

https://aip.scitation.org/doi/abs/10.1063/1.4975761
https://arxiv.org/pdf/1512.05064.pdf