├── README.md ├── Slides ├── Crypto3.pdf ├── Crypto_1.pdf ├── Crypto_2.pdf ├── Crypto_4.pdf ├── Crypto_5_1.pdf ├── Crypto_6.pdf ├── Crypto_7.pdf ├── Crypto_8.pdf ├── Intro.pdf ├── PhysicalSecurity.pdf ├── PostQuantum.pdf ├── Protocols_1.pdf ├── ProvableSecurity1.pdf ├── Review:.pdf.pdf ├── Secret_Sharing.pdf └── SideChannel.pdf ├── assignments ├── assignment_1 │ ├── assignment_1.pdf │ ├── problem_1.py │ ├── problem_2.py │ ├── sample.json │ └── solution_2.py ├── assignment_2 │ ├── assignment_2.pdf │ ├── problem.py │ └── solution.py ├── assignment_3 │ ├── assignment_3.pdf │ ├── problem.py │ └── solution.py └── assignment_4 │ ├── assignment_4.pdf │ ├── problem.py │ └── solution.py └── obsolete └── FinalExam2016 ├── ByLock Secure Chat Talk_v1.1.7_apkpure.com.apk ├── ImageFromTwitter.png ├── Issuers.txt ├── README.txt ├── Wordpress.pdf ├── final.pdf └── source.zip /README.md: -------------------------------------------------------------------------------- 1 | practicalcrypto 2 | =============== 3 | Repository for course materials and slides for Practical Cryptographic Systems, JHU CS 445/645. 4 | 5 | Click [here](https://github.com/matthewdgreen/practicalcrypto/wiki) for the main course page. 6 | -------------------------------------------------------------------------------- /Slides/Crypto3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto3.pdf -------------------------------------------------------------------------------- /Slides/Crypto_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_1.pdf -------------------------------------------------------------------------------- /Slides/Crypto_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_2.pdf -------------------------------------------------------------------------------- /Slides/Crypto_4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_4.pdf -------------------------------------------------------------------------------- /Slides/Crypto_5_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_5_1.pdf -------------------------------------------------------------------------------- /Slides/Crypto_6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_6.pdf -------------------------------------------------------------------------------- /Slides/Crypto_7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_7.pdf -------------------------------------------------------------------------------- /Slides/Crypto_8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Crypto_8.pdf -------------------------------------------------------------------------------- /Slides/Intro.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Intro.pdf -------------------------------------------------------------------------------- /Slides/PhysicalSecurity.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/PhysicalSecurity.pdf -------------------------------------------------------------------------------- /Slides/PostQuantum.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/PostQuantum.pdf -------------------------------------------------------------------------------- /Slides/Protocols_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Protocols_1.pdf -------------------------------------------------------------------------------- /Slides/ProvableSecurity1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/ProvableSecurity1.pdf -------------------------------------------------------------------------------- /Slides/Review:.pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Review:.pdf.pdf -------------------------------------------------------------------------------- /Slides/Secret_Sharing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/Secret_Sharing.pdf -------------------------------------------------------------------------------- /Slides/SideChannel.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/Slides/SideChannel.pdf -------------------------------------------------------------------------------- /assignments/assignment_1/assignment_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/assignments/assignment_1/assignment_1.pdf -------------------------------------------------------------------------------- /assignments/assignment_1/problem_1.py: -------------------------------------------------------------------------------- 1 | # Python version 3.9 or later 2 | 3 | import os 4 | 5 | 6 | # This is a helper function that returns the length of the input N in bytes. 7 | # 8 | # The cryptographically secure randomness generator, os.urandom, takes as input an 9 | # integer 'size' and outputs 'size' random bytes. These bytes can be interpretted as an 10 | # integer between 0 and 256**size - 1 (both inclusive). 11 | # 12 | # To sample a random number between 0 and N, we compute 'size' so that 256**size is the 13 | # smallest power of 256 greater than or equal to N. 14 | def num_rand_bytes(N): 15 | return (N.bit_length() + 7) // 8 16 | 17 | 18 | # Alice's random number generator 19 | def alice_rand_gen(N): 20 | num_bytes = num_rand_bytes(N) 21 | 22 | # Initialize with a sentinel so that at least one iteration of the loop is run. 23 | val = N + 1 24 | 25 | # Keep re-sampling until we obtain a value less that or equal to N. 26 | while val > N: 27 | # Get securely generated random bytes. 28 | random_bytes = os.urandom(num_bytes) 29 | # Convert the bytestring returned by os.urandom to an integer. 30 | val = int.from_bytes(random_bytes, "big") 31 | 32 | return val 33 | 34 | 35 | # Bob's random number generator 36 | def bob_rand_gen(N): 37 | num_bytes = num_rand_bytes(N) 38 | 39 | # Get securely generated random bytes. 40 | random_bytes = os.urandom(num_bytes) 41 | 42 | # Convert the bytestring returned by os.urandom to an integer and reduce it modulo 43 | # (N+1) to obtain a value between 0 and N. 44 | val = int.from_bytes(random_bytes, "big") % (N + 1) 45 | 46 | return val 47 | -------------------------------------------------------------------------------- /assignments/assignment_1/problem_2.py: -------------------------------------------------------------------------------- 1 | # Python version 3.9 or later 2 | 3 | 4 | # This file implements the Vigenere cipher and helps run test cases. 5 | # 6 | # You do NOT need to submit nor modify this file. Instead, add your solutions to 7 | # 'solution_2.py'. 8 | # 9 | # To verify your solutions, run: `python problem_2.py sample.json`. 10 | # This will require that 'solution_2.py', 'problem_2.py' and 'sample.json' are in the 11 | # same directory. 12 | # 13 | # Your submission will be graded by running your solution on a number of test cases. 14 | # Scores on 'sample.json' might not imply the same score for the total points awarded. 15 | 16 | import solution_2 17 | import json 18 | import sys 19 | import base64 20 | 21 | 22 | # Enciphers the given plaintext under the input key using Vigenere cipher. 23 | # Both key and plaintext are instances of bytes. 24 | def vigenere_cipher(key, plaintext): 25 | n = len(key) 26 | 27 | # Encipher the plaintext in chunks of size n 28 | ciphertext = b"" 29 | for i in range(0, len(plaintext), n): 30 | # Get the i-th chunk 31 | chunk = plaintext[i : i + n] 32 | # XOR the j-th byte of the chunk with the j-th byte of the key 33 | ciphertext += bytes(chunk[j] ^ key[j] for j in range(len(chunk))) 34 | 35 | return ciphertext 36 | 37 | 38 | # -------------------- Test Harness -------------------- 39 | 40 | 41 | def score_recovered_plaintext(recovered, plaintext): 42 | n = len(plaintext) 43 | 44 | if len(recovered) != n: 45 | return 0 46 | 47 | num_match = sum(map(lambda i: 1 if recovered[i] == plaintext[i] else 0, range(n))) 48 | percent_match = (num_match * 100.0) / n 49 | 50 | return percent_match 51 | 52 | 53 | def run_caesar_cipher_test(key, plaintext): 54 | key = bytes([key[0]]) 55 | ciphertext = vigenere_cipher(key, plaintext) 56 | 57 | recovered = solution_2.break_caesar_cipher(ciphertext) 58 | 59 | return score_recovered_plaintext(recovered, plaintext) 60 | 61 | 62 | def run_vigenere_key_length_test(key, plaintext): 63 | ciphertext = vigenere_cipher(key, plaintext) 64 | answer = len(key) 65 | 66 | guess = solution_2.find_vigenere_key_length(ciphertext) 67 | 68 | score = 0 69 | if guess == answer: 70 | score = 100 71 | elif guess != 0 and guess % answer == 0: 72 | score = 80 73 | 74 | return score 75 | 76 | 77 | def run_vigenere_cipher_test(key, plaintext): 78 | ciphertext = vigenere_cipher(key, plaintext) 79 | 80 | recovered = solution_2.break_vigenere_cipher(ciphertext, len(key)) 81 | 82 | return score_recovered_plaintext(recovered, plaintext) 83 | 84 | 85 | if __name__ == "__main__": 86 | if len(sys.argv) < 2: 87 | print(f"Usage: {sys.argv[0]} ") 88 | sys.exit() 89 | 90 | with open(sys.argv[1], "r") as f: 91 | test_data = json.load(f) 92 | 93 | # The key and plaintext are encoded in base64. 94 | # Convert them to bytes. 95 | key = base64.b64decode(test_data["key"]) 96 | plaintext = base64.b64decode(test_data["plaintext"]) 97 | 98 | scores = [ 99 | run_caesar_cipher_test(key, plaintext), 100 | run_vigenere_key_length_test(key, plaintext), 101 | run_vigenere_cipher_test(key, plaintext), 102 | ] 103 | 104 | print("---- Scores ---") 105 | print(f"Breaking Caesar cipher: {scores[0]:.2f}%") 106 | print(f"Finding Vigenere key length: {scores[1]:.2f}%") 107 | print(f"Breaking Vigenere cipher: {scores[2]:.2f}%") 108 | -------------------------------------------------------------------------------- /assignments/assignment_1/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "QnJ1dHVz", 3 | "plaintext": "QU5UT05ZLgpGcmllbmRzLCBSb21hbnMsIGNvdW50cnltZW4sIGxlbmQgbWUgeW91ciBlYXJzOwpJIGNvbWUgdG8gYnVyeSBDYWVzYXIsIG5vdCB0byBwcmFpc2UgaGltLgpUaGUgZXZpbCB0aGF0IG1lbiBkbyBsaXZlcyBhZnRlciB0aGVtLApUaGUgZ29vZCBpcyBvZnQgaW50ZXJyZWQgd2l0aCB0aGVpciBib25lczsKU28gbGV0IGl0IGJlIHdpdGggQ2Flc2FyLiBUaGUgbm9ibGUgQnJ1dHVzCkhhdGggdG9sZCB5b3UgQ2Flc2FyIHdhcyBhbWJpdGlvdXMuCklmIGl0IHdlcmUgc28sIGl0IHdhcyBhIGdyaWV2b3VzIGZhdWx0LApBbmQgZ3JpZXZvdXNseSBoYXRoIENhZXNhciBhbnN3ZXLigJlkIGl0LgpIZXJlLCB1bmRlciBsZWF2ZSBvZiBCcnV0dXMgYW5kIHRoZSByZXN0LApGb3IgQnJ1dHVzIGlzIGFuIGhvbm91cmFibGUgbWFuLApTbyBhcmUgdGhleSBhbGwsIGFsbCBob25vdXJhYmxlIG1lbiwKQ29tZSBJIHRvIHNwZWFrIGluIENhZXNhcuKAmXMgZnVuZXJhbC4KSGUgd2FzIG15IGZyaWVuZCwgZmFpdGhmdWwgYW5kIGp1c3QgdG8gbWU7CkJ1dCBCcnV0dXMgc2F5cyBoZSB3YXMgYW1iaXRpb3VzLApBbmQgQnJ1dHVzIGlzIGFuIGhvbm91cmFibGUgbWFuLgpIZSBoYXRoIGJyb3VnaHQgbWFueSBjYXB0aXZlcyBob21lIHRvIFJvbWUsCldob3NlIHJhbnNvbXMgZGlkIHRoZSBnZW5lcmFsIGNvZmZlcnMgZmlsbDoKRGlkIHRoaXMgaW4gQ2Flc2FyIHNlZW0gYW1iaXRpb3VzPwpXaGVuIHRoYXQgdGhlIHBvb3IgaGF2ZSBjcmllZCwgQ2Flc2FyIGhhdGggd2VwdDsKQW1iaXRpb24gc2hvdWxkIGJlIG1hZGUgb2Ygc3Rlcm5lciBzdHVmZjoKWWV0IEJydXR1cyBzYXlzIGhlIHdhcyBhbWJpdGlvdXM7CkFuZCBCcnV0dXMgaXMgYW4gaG9ub3VyYWJsZSBtYW4uCllvdSBhbGwgZGlkIHNlZSB0aGF0IG9uIHRoZSBMdXBlcmNhbApJIHRocmljZSBwcmVzZW50ZWQgaGltIGEga2luZ2x5IGNyb3duLApXaGljaCBoZSBkaWQgdGhyaWNlIHJlZnVzZS4gV2FzIHRoaXMgYW1iaXRpb24/CllldCBCcnV0dXMgc2F5cyBoZSB3YXMgYW1iaXRpb3VzOwpBbmQgc3VyZSBoZSBpcyBhbiBob25vdXJhYmxlIG1hbi4KSSBzcGVhayBub3QgdG8gZGlzcHJvdmUgd2hhdCBCcnV0dXMgc3Bva2UsCkJ1dCBoZXJlIEkgYW0gdG8gc3BlYWsgd2hhdCBJIGRvIGtub3cuCllvdSBhbGwgZGlkIGxvdmUgaGltIG9uY2UsIG5vdCB3aXRob3V0IGNhdXNlOwpXaGF0IGNhdXNlIHdpdGhob2xkcyB5b3UgdGhlbiB0byBtb3VybiBmb3IgaGltPwpPIGp1ZGdlbWVudCwgdGhvdSBhcnQgZmxlZCB0byBicnV0aXNoIGJlYXN0cywKQW5kIG1lbiBoYXZlIGxvc3QgdGhlaXIgcmVhc29uLiBCZWFyIHdpdGggbWUuCk15IGhlYXJ0IGlzIGluIHRoZSBjb2ZmaW4gdGhlcmUgd2l0aCBDYWVzYXIsCkFuZCBJIG11c3QgcGF1c2UgdGlsbCBpdCBjb21lIGJhY2sgdG8gbWUuCgpGSVJTVCBDSVRJWkVOLgpNZXRoaW5rcyB0aGVyZSBpcyBtdWNoIHJlYXNvbiBpbiBoaXMgc2F5aW5ncy4KClNFQ09ORCBDSVRJWkVOLgpJZiB0aG91IGNvbnNpZGVyIHJpZ2h0bHkgb2YgdGhlIG1hdHRlciwKQ2Flc2FyIGhhcyBoYWQgZ3JlYXQgd3JvbmcuCgpUSElSRCBDSVRJWkVOLgpIYXMgaGUsIG1hc3RlcnM/CkkgZmVhciB0aGVyZSB3aWxsIGEgd29yc2UgY29tZSBpbiBoaXMgcGxhY2UuCgpGT1VSVEggQ0lUSVpFTi4KTWFya+KAmWQgeWUgaGlzIHdvcmRzPyBIZSB3b3VsZCBub3QgdGFrZSB0aGUgY3Jvd247ClRoZXJlZm9yZSDigJl0aXMgY2VydGFpbiBoZSB3YXMgbm90IGFtYml0aW91cy4KCkZJUlNUIENJVElaRU4uCklmIGl0IGJlIGZvdW5kIHNvLCBzb21lIHdpbGwgZGVhciBhYmlkZSBpdC4KClNFQ09ORCBDSVRJWkVOLgpQb29yIHNvdWwsIGhpcyBleWVzIGFyZSByZWQgYXMgZmlyZSB3aXRoIHdlZXBpbmcuCgpUSElSRCBDSVRJWkVOLgpUaGVyZeKAmXMgbm90IGEgbm9ibGVyIG1hbiBpbiBSb21lIHRoYW4gQW50b255LgoKRk9VUlRIIENJVElaRU4uCk5vdyBtYXJrIGhpbTsgaGUgYmVnaW5zIGFnYWluIHRvIHNwZWFrLgoKQU5UT05ZLgpCdXQgeWVzdGVyZGF5IHRoZSB3b3JkIG9mIENhZXNhciBtaWdodApIYXZlIHN0b29kIGFnYWluc3QgdGhlIHdvcmxkOyBub3cgbGllcyBoZSB0aGVyZSwKQW5kIG5vbmUgc28gcG9vciB0byBkbyBoaW0gcmV2ZXJlbmNlLgpPIG1hc3RlcnMhIElmIEkgd2VyZSBkaXNwb3PigJlkIHRvIHN0aXIKWW91ciBoZWFydHMgYW5kIG1pbmRzIHRvIG11dGlueSBhbmQgcmFnZSwKSSBzaG91bGQgZG8gQnJ1dHVzIHdyb25nIGFuZCBDYXNzaXVzIHdyb25nLApXaG8sIHlvdSBhbGwga25vdywgYXJlIGhvbm91cmFibGUgbWVuLgpJIHdpbGwgbm90IGRvIHRoZW0gd3Jvbmc7IEkgcmF0aGVyIGNob29zZQpUbyB3cm9uZyB0aGUgZGVhZCwgdG8gd3JvbmcgbXlzZWxmIGFuZCB5b3UsClRoYW4gSSB3aWxsIHdyb25nIHN1Y2ggaG9ub3VyYWJsZSBtZW4uCkJ1dCBoZXJl4oCZcyBhIHBhcmNobWVudCB3aXRoIHRoZSBzZWFsIG9mIENhZXNhciwKSSBmb3VuZCBpdCBpbiBoaXMgY2xvc2V0OyDigJl0aXMgaGlzIHdpbGw6CkxldCBidXQgdGhlIGNvbW1vbnMgaGVhciB0aGlzIHRlc3RhbWVudCwKV2hpY2gsIHBhcmRvbiBtZSwgSSBkbyBub3QgbWVhbiB0byByZWFkLApBbmQgdGhleSB3b3VsZCBnbyBhbmQga2lzcyBkZWFkIENhZXNhcuKAmXMgd291bmRzLApBbmQgZGlwIHRoZWlyIG5hcGtpbnMgaW4gaGlzIHNhY3JlZCBibG9vZDsKWWVhLCBiZWcgYSBoYWlyIG9mIGhpbSBmb3IgbWVtb3J5LApBbmQsIGR5aW5nLCBtZW50aW9uIGl0IHdpdGhpbiB0aGVpciB3aWxscywKQmVxdWVhdGhpbmcgaXQgYXMgYSByaWNoIGxlZ2FjeQpVbnRvIHRoZWlyIGlzc3VlLgo=" 4 | } -------------------------------------------------------------------------------- /assignments/assignment_1/solution_2.py: -------------------------------------------------------------------------------- 1 | # Python version 3.9 or later 2 | 3 | # Complete the functions below and include this file in your submission. 4 | # 5 | # You can verify your solution by running `problem_2.py`. See `problem_2.py` for more 6 | # details. 7 | 8 | # ------------------------------------- IMPORTANT -------------------------------------- 9 | # Do NOT modify the name or signature of the three functions below. You can, however, 10 | # add any additional functons to this file. 11 | # -------------------------------------------------------------------------------------- 12 | 13 | # Given a ciphertext enciphered using the Caesar cipher, recover the plaintext. 14 | # In the Caesar cipher, each byte of the plaintext is XORed by the key (which is a 15 | # single byte) to compute the ciphertext. 16 | # 17 | # The input `ciphertext` is a bytestring i.e., it is an instance of `bytes` 18 | # (see https://docs.python.org/3.9/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview). 19 | # The function should return the plaintext, which is also a bytestring. 20 | def break_caesar_cipher(ciphertext): 21 | # TODO: Update the body to compute the plaintext 22 | return b'' 23 | 24 | 25 | # Given a ciphertext enciphered using a Vigenere cipher, find the length of the secret 26 | # key using the 'index of coincidence' method. 27 | # 28 | # The input `ciphertext` is a bytestring. 29 | # The function returns the key length, which is an `int`. 30 | def find_vigenere_key_length(ciphertext): 31 | # TODO: Update the body to find the key length 32 | return 0 33 | 34 | 35 | # Given a ciphertext enciphered using a Vigenere cipher and the length of the key, 36 | # recover the plaintext. 37 | # 38 | # The input `ciphertext` is a bytestring. 39 | # The function should return the plaintext, which is also a bytestring. 40 | def break_vigenere_cipher(ciphertext, key_length): 41 | # TODO: Update the body to compute the plaintext 42 | return b'' 43 | -------------------------------------------------------------------------------- /assignments/assignment_2/assignment_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/assignments/assignment_2/assignment_2.pdf -------------------------------------------------------------------------------- /assignments/assignment_2/problem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Assignment 2 5 | 6 | Python version 3.9 or later. 7 | 8 | Overview: 9 | This file implements the assignment challenge along with a test harness. 10 | You do NOT need to modify this file; instead, add your solutions to 'solution.py'. 11 | 12 | Required Packages: 13 | - pycryptodome: Install by running `pip install pycryptodome`. 14 | The documentation is available at https://www.pycryptodome.org/. 15 | You only need this package to run the script; it is not required for implementing your solutions. 16 | 17 | Usage: 18 | To verify your solutions, run: `python problem.py`. 19 | (Make sure that both `solution.py` and `problem.py` are in the same directory.) 20 | """ 21 | 22 | from Crypto.Cipher import AES 23 | from Crypto.Util import Padding 24 | from Crypto.Random import random, get_random_bytes 25 | 26 | import solution 27 | 28 | 29 | AES_BLOCK_SIZE = 16 30 | 31 | 32 | # ========================== Problem 1: Padding Oracle Attack ========================== 33 | 34 | 35 | class PaddingOracleProblem: 36 | # Create a server that receives ciphertexts, decrypts it and checks padding. 37 | @staticmethod 38 | def create_server(key): 39 | # The server receives a bytestring as input. 40 | def server(ctx): 41 | # The first block of the ciphertext is the IV. 42 | iv = ctx[:AES_BLOCK_SIZE] 43 | # Decrypt the remaining blocks of the message. 44 | aes = AES.new(key, AES.MODE_CBC, iv=iv) 45 | raw_msg = aes.decrypt(ctx[AES_BLOCK_SIZE:]) 46 | 47 | try: 48 | # Attempt to unpad the decrypted message. 49 | # An error is thrown if the padding is invalid. 50 | Padding.unpad(raw_msg, AES_BLOCK_SIZE, "pkcs7") 51 | except: 52 | # In case of invalid padding, return False. 53 | return False 54 | 55 | # The padding on the decrypted message was valid. 56 | return True 57 | 58 | return server 59 | 60 | # Create a server and test it against the submitted solution in `solution.py`. 61 | @staticmethod 62 | def test(): 63 | # Genreate key and IV as random bytes. 64 | # 65 | # For debugging, you might find it helpful to make the encryption deterministic. 66 | # You can set the key, IV and msg to a fixed value to do so. 67 | # For example: `key = b"YELLOW SUBMARINE"` (or any other 16 byte string) 68 | key = get_random_bytes(AES_BLOCK_SIZE) 69 | iv = get_random_bytes(AES_BLOCK_SIZE) 70 | aes = AES.new(key, AES.MODE_CBC, iv=iv) 71 | 72 | # Note that msg_len is set to 10 as an example here. Your solution will be tested 73 | # against messages of different lengths. 74 | msg_len = 10 75 | msg = get_random_bytes(msg_len) 76 | 77 | # Pad and encrypt the message. 78 | pad_msg = Padding.pad(msg, AES_BLOCK_SIZE, "pkcs7") 79 | ctx = iv + aes.encrypt(pad_msg) 80 | 81 | # Create the server and run the attack. 82 | server = PaddingOracleProblem.create_server(key) 83 | guess = solution.solve_padding_oracle(ctx, server) 84 | 85 | score = 0 86 | if guess == msg: 87 | score = 100 88 | 89 | return score 90 | 91 | 92 | # ======================== Problem 2: Stateful CBC Encryption ========================== 93 | 94 | 95 | class StatefulCBCProblem: 96 | # Create a 'compromised device' that receives a message and returns the ciphertext. 97 | @staticmethod 98 | def create_compromised_device(): 99 | # Generate cookie. 100 | # 101 | # Note that cookie_len is set to 5 as an example here. Your solution will be 102 | # tested against cookies of different lengths. 103 | cookie_len = 5 104 | cookie = get_random_bytes(cookie_len) 105 | 106 | # Sample random key, iv and create a new AES instance. 107 | key = get_random_bytes(AES_BLOCK_SIZE) 108 | iv = get_random_bytes(AES_BLOCK_SIZE) 109 | aes = AES.new(key, AES.MODE_CBC, iv=iv) 110 | 111 | def device(path): 112 | # Create the message to be encrypted. 113 | msg = b"".join([path, b";cookie=", cookie]) 114 | 115 | # Pad the message. This is just to ensure that there are no errors when 116 | # messages are not multiples of the block length. 117 | pad_msg = Padding.pad(msg, AES_BLOCK_SIZE, "pkcs7") 118 | 119 | # Encrypt the padded message. 120 | # 121 | # Note that we use the same AES instance every time. This uses the last 122 | # block of the previous ciphertext when encrypting the current message. 123 | # 124 | # More precisely, the pycryptodome API ensures the following: 125 | # key = get_random_bytes(AES_BLOCK_SIZE) 126 | # iv = get_random_bytes(AES_BLOCK_SIZE) 127 | # msg1 = b"YELLOW SUBMARINE" 128 | # msg2 = b"DR MATTHEW GREEN" 129 | # 130 | # aes1 = AES.new(key, AES.MODE_CBC, iv) 131 | # ctx = aes1(msg1 + msg2) 132 | # 133 | # aes2 = AES.new(key, AES.MODE_CBC, iv) 134 | # ctx1 = aes2.encrypt(msg1) 135 | # ctx2 = aes2.encrypt(msg2) 136 | # 137 | # ctx1 + ctx1 == ctx 138 | ctx = aes.encrypt(pad_msg) 139 | return ctx 140 | 141 | return cookie, device 142 | 143 | @staticmethod 144 | def test_cookie_length(): 145 | cookie, device = StatefulCBCProblem.create_compromised_device() 146 | 147 | guess = solution.find_cookie_length(device) 148 | 149 | score = 0 150 | if guess == len(cookie): 151 | score = 100 152 | 153 | return score 154 | 155 | @staticmethod 156 | def test(): 157 | cookie, device = StatefulCBCProblem.create_compromised_device() 158 | 159 | guess = solution.find_cookie(device) 160 | 161 | score = 0 162 | if guess == cookie: 163 | score = 100 164 | 165 | return score 166 | 167 | 168 | if __name__ == "__main__": 169 | scores = [ 170 | PaddingOracleProblem.test(), 171 | StatefulCBCProblem.test_cookie_length(), 172 | StatefulCBCProblem.test(), 173 | ] 174 | 175 | print("--- Scores ---") 176 | print(f"Padding oracle attack: {scores[0]:.2f}") 177 | print(f"Finding cookie length: {scores[1]:.2f}") 178 | print(f"Finding cookie: {scores[2]:.2f}") 179 | -------------------------------------------------------------------------------- /assignments/assignment_2/solution.py: -------------------------------------------------------------------------------- 1 | AES_BLOCK_SIZE = 16 2 | 3 | """ 4 | Solution to Assignment 2 5 | 6 | Python version 3.9 or later. 7 | 8 | Your final submission must contain the following functions: 9 | - solve_padding_oracle(ctx, server) 10 | - find_cookie_length(server) 11 | - find_cookie(server) 12 | """ 13 | 14 | 15 | def solve_padding_oracle(ctx, server): 16 | """ 17 | Recovers the original plaintext message from a given ciphertext using a padding oracle attack. 18 | 19 | Parameters: 20 | ctx (bytes): A ciphertext produced using AES in CBC mode. The first AES_BLOCK_SIZE bytes 21 | of ctx are the Initialization Vector (IV), and the remaining bytes are the ciphertext. 22 | 23 | server (function): A padding oracle function with the signature: 24 | server(ciphertext: bytes) -> bool 25 | When passed a ciphertext, the server function decrypts it (using the unknown key) 26 | and returns True if the resulting plaintext has valid PKCS#7 padding, 27 | or False if the padding is invalid. 28 | 29 | Returns: 30 | bytes: The recovered plaintext message with the padding removed. 31 | """ 32 | return b"" 33 | 34 | 35 | def find_cookie_length(device): 36 | """ 37 | Determines the length (in bytes) of a secret cookie that the device appends to a plaintext message 38 | before encryption. 39 | 40 | Parameters: 41 | device (function): A stateful CBC encryption oracle with the signature: 42 | device(path: bytes) -> bytes 43 | The device takes a bytes object "path" as input and internally constructs a message: 44 | msg = path + b";cookie=" + cookie 45 | It then pads and encrypts this message using AES in CBC mode. 46 | Importantly, the device retains its CBC state between calls, so the encryption is stateful. 47 | 48 | Returns: 49 | int: The length of the secret cookie (in bytes). 50 | """ 51 | return 0 52 | 53 | 54 | def find_cookie(device): 55 | """ 56 | Recovers the secret cookie that the device appends to the plaintext message before encryption. 57 | 58 | Parameters: 59 | device (function): A stateful CBC encryption oracle with the signature: 60 | device(path: bytes) -> bytes 61 | The device builds the message as: 62 | msg = path + b";cookie=" + cookie 63 | and then pads and encrypts msg using AES in CBC mode, while maintaining the CBC chaining 64 | state across calls. 65 | 66 | Returns: 67 | bytes: The secret cookie that was appended to the plaintext. 68 | """ 69 | return b"" 70 | -------------------------------------------------------------------------------- /assignments/assignment_3/assignment_3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/assignments/assignment_3/assignment_3.pdf -------------------------------------------------------------------------------- /assignments/assignment_3/problem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import random 3 | import sys 4 | from functools import reduce 5 | 6 | import solution 7 | 8 | """ 9 | Assignment 3 10 | 11 | Python version 3.9 or later. 12 | 13 | Overview: 14 | This file implements the assignment challenge along with a test harness. 15 | You do NOT need to modify this file; instead, add your solutions to 'solution.py'. 16 | 17 | Usage: 18 | To verify your solutions, run: `python problem.py `. 19 | (Make sure that both `solution.py` and `problem.py` are in the same directory.) 20 | param_type denotes the set of parameters used for the test (see end of script). 21 | """ 22 | 23 | # ============================================ Utils ============================================= 24 | class Params: 25 | """A class used to store parameter values, subsequently used in tests. 26 | 27 | Attributes: 28 | - mod: Prime number used as modulus 29 | - factors: A list of the form [(p_1, e_1), ..., (p_n, e_n)] such that 30 | P - 1 = p_1^{e_1} * ... * p_n^{e_n}. 31 | - gen: Generator for Z_mod. 32 | - exp_bound: The secret key or exponent is sampled at random from the set 33 | {0, ..., exp_bound - 1}. 34 | """ 35 | 36 | def __init__(self, mod, factors, gen, exp_bound): 37 | self.mod = mod 38 | self.factors = factors 39 | self.gen = gen 40 | self.exp_bound = exp_bound 41 | 42 | 43 | # ========================== Problem 1.a: Brute Force Discrete Log =============================== 44 | class BruteForceDL: 45 | @staticmethod 46 | def test(mod, sub_grp_gen, sub_grp_order): 47 | s = random.randint(0, sub_grp_order - 1) 48 | val = pow(sub_grp_gen, s, mod) 49 | 50 | guess = solution.brute_force_dl(mod, sub_grp_gen, sub_grp_order, val) 51 | 52 | score = 0 53 | if guess % sub_grp_order == s: 54 | score = 100 55 | 56 | return score 57 | 58 | 59 | # ====================== Problem 1.b: Baby Step Giant Step Discrete Log ========================== 60 | class BabyStepGiantStepDL: 61 | @staticmethod 62 | def test(mod, sub_grp_gen, sub_grp_order): 63 | s = random.randint(0, sub_grp_order - 1) 64 | val = pow(sub_grp_gen, s, mod) 65 | 66 | guess = solution.baby_step_giant_step_dl(mod, sub_grp_gen, sub_grp_order, val) 67 | 68 | score = 0 69 | if guess % sub_grp_order == s: 70 | score = 100 71 | 72 | return score 73 | 74 | 75 | # ========================== Problem 1.c: Chinese Remainder Theorem ============================== 76 | class CRT: 77 | @staticmethod 78 | def test(vals, mods): 79 | res = solution.crt(vals, mods) 80 | 81 | for val, mod in zip(vals, mods): 82 | if res % mod != val: 83 | return 0 84 | 85 | return 100 86 | 87 | 88 | # ===================== Problem 1.d: Pohlig-Hellman Prime Power =================================== 89 | class PohligHellman: 90 | @staticmethod 91 | def test(mod, sub_grp_gen, sub_grp_order_factors): 92 | sub_grp_order = reduce( 93 | lambda acc, x: acc * x[0] ** x[1], sub_grp_order_factors, 1 94 | ) 95 | s = random.randint(0, sub_grp_order - 1) 96 | val = pow(sub_grp_gen, s, mod) 97 | 98 | guess = solution.pohlig_hellman(mod, sub_grp_gen, sub_grp_order_factors, val) 99 | 100 | score = 0 101 | if guess % sub_grp_order == s: 102 | score = 100 103 | 104 | return score 105 | 106 | 107 | # =========================== Problem 1.e: ElGamal Attack ========================================= 108 | class ElGamalAttack: 109 | @staticmethod 110 | def test(params): 111 | sk = random.randint(0, params.exp_bound - 1) 112 | pk = pow(params.gen, sk, params.mod) 113 | 114 | guess = solution.elgamal_attack(params, pk) 115 | 116 | score = 0 117 | if guess % (params.mod - 1) == sk: 118 | score = 100 119 | 120 | return score 121 | 122 | 123 | if __name__ == "__main__": 124 | if len(sys.argv) < 2: 125 | print(f"Usage: {sys.argv[0]} ") 126 | sys.exit() 127 | 128 | param_type = sys.argv[1] 129 | 130 | score_names = [ 131 | "Brute force DL", 132 | "Baby Step Giant Step DL", 133 | "Chinese Remainder Theorem", 134 | "Pohlig-Hellman", 135 | "ElGamal Attack", 136 | ] 137 | 138 | scores = [] 139 | # See Param class definition to see what each parameter denotes. 140 | if param_type == "tiny": 141 | # Tiny (6-bit prime) 142 | # These params are mainly to help debug. 143 | # Note that exp_bound is equal to Φ(mod) = mod - 1. This means that the secret key is 144 | # sampled uniformly at random from {0, ..., mod - 2}, as in standard ElGamal encryption. 145 | params = Params(mod=61, factors=[(2, 2), (3, 1), (5, 1)], gen=30, exp_bound=60) 146 | 147 | # Brute force DL 148 | # We compute DL over Z*_mod, which can be done efficiently since mod is small. 149 | scores.append(BruteForceDL.test(params.mod, params.gen, params.mod - 1)) 150 | 151 | # Baby Step Giant Step DL 152 | # We compute DL over Z*_mod. 153 | scores.append(BabyStepGiantStepDL.test(params.mod, params.gen, params.mod - 1)) 154 | 155 | # CRT 156 | mods = [x[0] ** x[1] for x in params.factors] 157 | vals = [random.randint(0, mod - 1) for mod in mods] 158 | scores.append(CRT.test(vals, mods)) 159 | 160 | # Pohlig-Hellman 161 | # We compute DL over Z*_mod. 162 | scores.append(PohligHellman.test(params.mod, params.gen, params.factors)) 163 | elif param_type == "small": 164 | # Small (130-bit prime) 165 | # Observe that exp_bound is equal to Φ(mod) = mod - 1, which means that the secret key 166 | # is sampled from a very large set, consisting of ~2^130 values. Moreover, the secret key 167 | # space is {0, ..., mod-2}, as in the standard ElGamal encryption scheme. 168 | # 169 | # However, we will be able to recover the secret key (despite it being sampled from a 170 | # large set) using Pohlig-Hellman because the order of Z*_mod, namely Φ(mod) = mod - 1, 171 | # does not contain any large prime factors. 172 | params = Params( 173 | mod=927561315432648769274106771080177774153, 174 | factors=[(2, 3), (67, 3), (131, 5), (257, 3), (521, 1), (1031, 4)], 175 | gen=216606503944793770949515456773896502127, 176 | exp_bound=927561315432648769274106771080177774152, 177 | ) 178 | 179 | # Brute force DL 180 | # Contrary to the case of 'tiny' params, brute force is no longer feasible to find the 181 | # discrete log over Z*_mod when mod is a 130-bit modulus since this will require iterating 182 | # over approximately 2^138 ~ 10^41 values. 183 | # Thus, we use a much smaller subgroup in Z*_mod to run the brute force discrete log test. 184 | exp = 67 * 131**5 * 257**3 * 521 * 1031**4 185 | # To understand why sub_grp_gen is computed this way: 186 | # - See "Subgroups of Cyclic Groups" in https://crypto.stanford.edu/pbc/notes/numbertheory/cyclic.html, or 187 | # - Theorem 2.15 in https://shoup.net/ntb/ntb-v2.pdf 188 | sub_grp_gen = pow(params.gen, exp, params.mod) 189 | sub_grp_order = (params.mod - 1) // exp # sub_grp_order = 35,912 190 | scores.append(BruteForceDL.test(params.mod, sub_grp_gen, sub_grp_order)) 191 | 192 | # Baby Step Giant Step DL 193 | # Compared to brute force DL, we can compute discrete log over a larger subgroup using the 194 | # BSGS algorithm, since it improves the runtime to √sub_grp_order. 195 | exp = 131**5 * 257**3 * 1031**4 196 | sub_grp_gen = pow(params.gen, exp, params.mod) 197 | sub_grp_order = ( 198 | params.mod - 1 199 | ) // exp # sub_grp_order = 1,253,580,184, √sub_grp_order = 35,406 200 | scores.append(BabyStepGiantStepDL.test(params.mod, sub_grp_gen, sub_grp_order)) 201 | 202 | # CRT 203 | mods = [x[0] ** x[1] for x in params.factors] 204 | vals = [random.randint(0, mod - 1) for mod in mods] 205 | scores.append(CRT.test(vals, mods)) 206 | 207 | # Pohlig-Hellman 208 | # As mentioned previously, because the order of the subgroup `mod - 1` does not contain 209 | # any large prime factors, we can compute the discrete log for any element in Z*_mod. 210 | scores.append(PohligHellman.test(params.mod, params.gen, params.factors)) 211 | else: 212 | # Medium (522-bit prime) 213 | # Observe that exp_bound is much smaller than mod - 1, however, the resulting secret key 214 | # space is still large, consisting of 2^128 values. 215 | params = Params( 216 | mod=7428206452375868051112377676516436620612011672582445792917111703118561592770349740098371912233951285585673180734253488802868241288412306084645060103575110649, 217 | factors=[(2, 3), (257, 8), (1031, 7), (18446744073709551629, 6)], 218 | gen=4026924533573022326359046855640747468905899959801793804281259660339223595723025138872101897427591935153581205666098085487298502501358616958443351972709036895, 219 | exp_bound=2**128, 220 | ) 221 | 222 | # Brute force DL 223 | # Once again, we consider a small subgroup in Z*_mod over which we can efficiently brute 224 | # force the discrete log. 225 | exp = 257**6 * 1031**7 * 18446744073709551629**6 226 | sub_grp_gen = pow(params.gen, exp, params.mod) 227 | sub_grp_order = (params.mod - 1) // exp # sub_grp_order = 5,28,392 228 | scores.append(BruteForceDL.test(params.mod, sub_grp_gen, sub_grp_order)) 229 | 230 | # Baby Step Giant Step DL 231 | # Since BSGS improves the runtime, we can afford to compute the discrete log over a larger 232 | # subgroup of Z*_mod, compared to the brute force computation. 233 | exp = 257**6 * 1031**6 * 18446744073709551629**6 234 | sub_grp_gen = pow(params.gen, exp, params.mod) 235 | sub_grp_order = (params.mod - 1) // exp # sub_grp_order = 544,772,152 236 | scores.append(BabyStepGiantStepDL.test(params.mod, sub_grp_gen, sub_grp_order)) 237 | 238 | # CRT 239 | mods = [x[0] ** x[1] for x in params.factors] 240 | vals = [random.randint(0, mod - 1) for mod in mods] 241 | scores.append(CRT.test(vals, mods)) 242 | 243 | # Pohlig-Hellman 244 | # Let p_4 = 18446744073709551629. 245 | # Unlike the case of 'small' or 'tiny' parameters, the order of Z*_mod, namely mod - 1, 246 | # has a large prime factor p_4. Specifically, since 2^63 < p_4 < 2^64, we cannot compute 247 | # the discrete log over any subgroup of Z*_mod such that p_4 divides the order of the 248 | # subgroup, since this will require at least √2^64 = 2^32 steps. 249 | # 250 | # Thus, we compute discrete log over a subgroup of Z*_mod such that the order of the 251 | # subgroup consists of only small prime factors. 252 | sub_factors = [(2, 3), (257, 8), (1031, 7)] 253 | sub_grp_order = reduce(lambda acc, x: acc * x[0] ** x[1], sub_factors, 1) 254 | sub_grp_gen = pow(params.gen, (params.mod - 1) // sub_grp_order, params.mod) 255 | scores.append(PohligHellman.test(params.mod, sub_grp_gen, sub_factors)) 256 | 257 | # ElGamal Attack 258 | scores.append(ElGamalAttack.test(params)) 259 | 260 | # Output 261 | print("--- Scores ---") 262 | for name, score in zip(score_names, scores): 263 | print(f"{name}: {score:.2f}") 264 | -------------------------------------------------------------------------------- /assignments/assignment_3/solution.py: -------------------------------------------------------------------------------- 1 | """Solution to Assignment 3. 2 | 3 | Python version 3.9 or later. 4 | 5 | Your final submission must contain the following functions: 6 | - brute_force_dl(mod, gen, order, target) 7 | - baby_step_giant_step_dl(mod, gen, order, target): 8 | - crt(vals, mods) 9 | - pohlig_hellman(mod, gen, factors, target) 10 | - elgamal_attack(params, pk) 11 | """ 12 | 13 | 14 | def brute_force_dl(mod, gen, order, target): 15 | """Uses brute force to compute discrete log of target with respect to gen. 16 | 17 | Parameters: 18 | mod (int): The prime modulus over which computation is carried out. 19 | gen (int): An element of Z*_mod. 20 | order (int): The order of the subgroup generated by gen. 21 | target (int): The element whose discrete log is to be computed. 22 | 23 | Returns: 24 | int: The discrete log of target with respect to gen. 25 | """ 26 | return 0 27 | 28 | 29 | def baby_step_giant_step_dl(mod, gen, order, target): 30 | """Uses the baby step giant step algorithm to compute discrete log of 31 | target with respect to gen. 32 | 33 | Parameters: 34 | mod (int): The prime modulus over which computation is carried out. 35 | gen (int): An element of Z*_mod. 36 | order (int): The order of the subgroup generated by gen. 37 | target (int): The element whose discrete log is to be computed. 38 | 39 | Returns: 40 | int: The discrete log of target with respect to gen. 41 | """ 42 | return 0 43 | 44 | 45 | def crt(vals, mods): 46 | """Solves a system of congruences. 47 | 48 | Parameters: 49 | vals (list(int)): A list of values. 50 | mods (list(int)): A list of moduli which are pairwise coprime i.e., mod[i] and mod[j] are 51 | coprime for any i ≠ j. The length of this list is equal to that of vals. 52 | 53 | Returns: 54 | int: An integer z such that for every i in {0, .., len(vals) - 1}, z ≡ vals[i] mod mods[i]. 55 | """ 56 | return 0 57 | 58 | 59 | def pohlig_hellman(mod, gen, factors, target): 60 | """Uses the Pohlig-Hellman algorithm to compute discrete log of target with 61 | respect to gen, given the factorization of the order of the subgroup 62 | generated by gen. 63 | 64 | Parameters: 65 | mod (int): The prime modulus over which computation is carried out. 66 | gen (int): An element of Z*_mod. 67 | factors (list(int, int)): A list of values [(p_1, e_1), ..., (p_n, e_n)] such that the order 68 | of the subgroup generated by gen is p_1^{e_1} * ... * p_n^{e_n}. 69 | target (int): The element whose discrete log is to be computed. 70 | 71 | Returns: 72 | int: The discrete log of target with respect to gen. 73 | """ 74 | return 0 75 | 76 | 77 | def elgamal_attack(params, pk): 78 | """ 79 | Given an ElGamal public key in Z*_mod, where mod is prime, recovers the corresponding secret 80 | key when mod - 1 has sufficiently many 'small' prime factors. 81 | 82 | Parameters: 83 | params (Params): ElGamal parameters. It is an instance of the Params class defined in 84 | problem.py. 85 | pk (int): The ElGamal public key. It is guaranteed that the corresponding secret key is 86 | less than params.exp_bound. 87 | 88 | Returns: 89 | int: The discrete log of pk with respect to gen. 90 | """ 91 | return 0 92 | -------------------------------------------------------------------------------- /assignments/assignment_4/assignment_4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/assignments/assignment_4/assignment_4.pdf -------------------------------------------------------------------------------- /assignments/assignment_4/problem.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hashlib 3 | from datetime import datetime 4 | import json 5 | from tinyec.registry import get_curve 6 | from Crypto.Cipher import AES 7 | from dissononce.processing.impl.symmetricstate import SymmetricState 8 | from dissononce.processing.impl.cipherstate import CipherState 9 | from dissononce.cipher.chachapoly import ChaChaPolyCipher 10 | from dissononce.dh.x25519.x25519 import X25519DH, PrivateKey 11 | from dissononce.hash.sha512 import SHA512Hash 12 | 13 | import solution 14 | 15 | """ 16 | Assignment 4 17 | 18 | Python version 3.9 or later. 19 | 20 | Overview: 21 | This file implements the assignment problem along with a test harness. 22 | You do NOT need to modify this file; instead, add your solutions to 'solution.py'. 23 | 24 | Required Packages: 25 | - pycryptodome: Install by running `pip install pycryptodome`. 26 | - tinyec: Install by running `pip install tinyec`. 27 | - dissononce: Install by running `pip install dissononce`. 28 | 29 | Usage: 30 | To verify your solutions, run: `python problem.py`. 31 | (Make sure that both `solution.py` and `problem.py` are in the same directory.) 32 | """ 33 | 34 | #################################################################################################### 35 | # ECDSA 36 | #################################################################################################### 37 | class ECDSA: 38 | # Create an ECDSA instance using a secret key. 39 | # Required only to compute signatures since verification is a static method. 40 | def __init__(self, sk): 41 | self.sk = sk 42 | # Randomness required for signatures is sampled using AES in ECB mode 43 | self.rgen = AES.new(os.urandom(16), AES.MODE_ECB) 44 | 45 | # Hash a bytestring into an integer that is in {0, ..., N-1} where N is the order of the 46 | # elliptic curve 47 | @staticmethod 48 | def hash_msg_to_int(message, N): 49 | return int.from_bytes(hashlib.sha256(message).digest(), "big") % N 50 | 51 | def sign(self, message): 52 | # Use the P256 curve 53 | curve = get_curve("secp256r1") 54 | # N is the order of the group 55 | N = curve.field.n 56 | 57 | e = ECDSA.hash_msg_to_int(message, N) 58 | 59 | ##################### 60 | # Point of Interest # 61 | ##################### 62 | while True: 63 | # Generate 256 bits using AES 64 | rbyte = os.urandom(1) 65 | # Convert to integer and valid exponent for the elliptic curve 66 | k = int.from_bytes(self.rgen.encrypt(rbyte + 15 * b"0" + rbyte + 15 * b"1"), "big") % N 67 | if k == 0: 68 | continue 69 | 70 | R = k * curve.g 71 | r = R.x % N 72 | if r == 0: 73 | continue 74 | 75 | k_inv = pow(k, -1, N) 76 | s = (r * self.sk) % N 77 | s = (s + e) % N 78 | s = (s * k_inv) % N 79 | 80 | return (r, s) 81 | 82 | @staticmethod 83 | def verify(pk, message, signature): 84 | curve = get_curve("secp256r1") 85 | N = curve.field.n 86 | 87 | (r, s) = signature 88 | 89 | if not (1 <= r < N and 1 <= s < N): 90 | return False 91 | 92 | e = ECDSA.hash_msg_to_int(message, N) 93 | s_inv = pow(s, -1, N) 94 | u1 = (e * s_inv) % N 95 | u2 = (r * s_inv) % N 96 | 97 | R = (u1 * curve.g) + (u2 * pk) 98 | 99 | if R.x % N == r: 100 | return True 101 | return False 102 | 103 | 104 | #################################################################################################### 105 | # Noise Protocol 106 | #################################################################################################### 107 | # This is a simplified version of the HandShakeState class from the dissononce package, tailored to 108 | # implement only the Noise protocol K-pattern. While the dissononce package supports the K-pattern, 109 | # solving the assignment may require slight modifications to how handshake messages are computed, 110 | # and the simplified version provided here should be much easier to work with. 111 | # 112 | # You do not need to be familiar with the lower level details of the cryptographic functions--- 113 | # namely, CipherState, SymmetricState, and DH---used in this implementation. A high-level overview 114 | # of their functionality (including the description of each method they support) can be found in 115 | # the Noise protocol specification (https://noiseprotocol.org/noise.html#crypto-functions). 116 | class KHandShakeState: 117 | def __init__(self, symmetricstate, dh): 118 | # symmatricstate (which is an instance of SymmetricState) maintains a chaining key 119 | # and a hash of the complete handshake transcript. The chaining key is used to encrypt the 120 | # handshake messages. 121 | self.symmetricstate = symmetricstate 122 | # dh (an instance of DH) helps computed Diffie-Hellman key exchange 123 | self.dh = dh 124 | # Static keypair 125 | self.s = None 126 | # Ephemeral keypair 127 | self.e = None 128 | # Remote static public key 129 | self.rs = None 130 | # Remote ephemeral public key 131 | self.re = None 132 | # Denotes if this instance is the initiator or the responder in the handshake 133 | self.initiator = None 134 | # The protocol name summarizes the Noise protocol pattern and the cryptographic functions 135 | # used for the handshake. These are hardcoded for the purpose of this assignment to keep 136 | # things simple. 137 | self.protocol_name = "Noise_K_25519_ChaChaPoly_SHA256" 138 | 139 | # Initialize the instance 140 | # This method should be called after creating the instance and before reading or writing 141 | # messages. 142 | def initialize(self, initiator, s=None, e=None, rs=None, re=None): 143 | # Initialize symmetricstate (see specification for more details) 144 | self.symmetricstate.initialize_symmetric(self.protocol_name.encode()) 145 | 146 | # Prologue 147 | # The prologue allows the hash of arbitrary data to be included in the computation of the 148 | # chaining key; often to capture prior context. See https://noiseprotocol.org/noise.html#prologue 149 | # for more details. 150 | prologue = b"" 151 | self.symmetricstate.mix_hash(prologue) 152 | 153 | self.initiator = initiator 154 | self.s = s 155 | self.e = e 156 | self.rs = rs 157 | self.re = re 158 | 159 | # Process pre-messages 160 | # For the K pattern, the pre-messages includes the static public-keys of the initiator and 161 | # responder. 162 | if initiator: 163 | # mix_hash updates the hash of the transcript of the handshake. 164 | # See https://noiseprotocol.org/noise.html#the-symmetricstate-object 165 | self.symmetricstate.mix_hash(s.public.data) 166 | 167 | assert rs is not None, "a pre_message required rs but was empty" 168 | self.symmetricstate.mix_hash(rs.data) 169 | else: 170 | assert rs is not None, "a pre_message required rs but was empty" 171 | self.symmetricstate.mix_hash(rs.data) 172 | 173 | self.symmetricstate.mix_hash(s.public.data) 174 | 175 | # Writes the initiator' handshake message into message_buffer. 176 | # message_buffer is of type bytearray. 177 | def write_message(self, payload, message_buffer): 178 | assert self.initiator, "responder has no handshake message" 179 | 180 | # The handshake for the K-pattern is: e, es, ss. 181 | # We process each token sequentially. 182 | 183 | # Pattern token: e 184 | # Create an ephemeral key and include it in the message. 185 | self.e = self.dh.generate_keypair() 186 | message_buffer.extend(self.e.public.data) 187 | self.symmetricstate.mix_hash(self.e.public.data) 188 | 189 | # Pattern token: es 190 | # Compute the key es by performing DH key agreement on e and rs. 191 | # The resulting key is used to update the chaining key using mix_key. 192 | # See https://noiseprotocol.org/noise.html#the-symmetricstate-object 193 | self.symmetricstate.mix_key(self.dh.dh(self.e, self.rs)) 194 | 195 | # Pattern token: es 196 | # Same as above but compute the key ss by performing DH key agreement on s and rs. 197 | self.symmetricstate.mix_key(self.dh.dh(self.s, self.rs)) 198 | 199 | # Encrypt payload 200 | # Finalize the message by encrypting and authenticating the transcript and payload. 201 | message_buffer.extend(self.symmetricstate.encrypt_and_hash(payload)) 202 | 203 | # Reads the initiator' handshake message and writes the decrypted payload into payload_buffer. 204 | # Called by the responder. 205 | # payload_buffer is of type bytearray. 206 | def read_message(self, message, payload_buffer): 207 | assert not self.initiator, "initiator can't read handshake message" 208 | 209 | # As in the case of write_message, we process each token sequentially. 210 | 211 | # Pattern token: e 212 | self.re = self.dh.create_public(message[: self.dh.dhlen]) 213 | self.symmetricstate.mix_hash(self.re.data) 214 | message = message[self.dh.dhlen :] 215 | 216 | # Pattern token: es 217 | self.symmetricstate.mix_key(self.dh.dh(self.s, self.re)) 218 | 219 | # Pattern token: ss 220 | self.symmetricstate.mix_key(self.dh.dh(self.s, self.rs)) 221 | 222 | # Decrypt payload 223 | payload_buffer.extend(self.symmetricstate.decrypt_and_hash(message)) 224 | 225 | 226 | #################################################################################################### 227 | # Problem 228 | #################################################################################################### 229 | # Cloud storage server 230 | # Instances of this class are meant to emulate the cloud storage server for the purpose of this 231 | # assignment. To keep things simple, the implementation supports only a single registered user. 232 | class Server: 233 | def __init__(self): 234 | curve = get_curve("secp256r1") 235 | 236 | ##################### 237 | # Point of Interest # 238 | ##################### 239 | # Generate ECDSA keypair and static DH keypair for handshake 240 | sk = os.urandom(16) 241 | self.ecdsa_sk = int.from_bytes(sk, "big") % curve.field.n 242 | self.ecdsa_pk = self.ecdsa_sk * curve.g 243 | self.static_sk = PrivateKey(sk + b"0" * 16) 244 | 245 | self.ecdsa = ECDSA(self.ecdsa_sk) 246 | self.static_keypair = X25519DH().generate_keypair(self.static_sk) 247 | 248 | # Used to store client data 249 | self.db = None 250 | # Stores the static public key of the (single) registered user 251 | self.user_static_pk = None 252 | 253 | def get_ecdsa_pk(self): 254 | return self.ecdsa_pk 255 | 256 | def get_static_pk(self): 257 | return self.static_keypair.public 258 | 259 | def get_user_storage(self): 260 | return self.db 261 | 262 | def register_user(self, user_pk): 263 | self.user_static_pk = user_pk 264 | 265 | # Service provided by the server so that users can check for software updates. 266 | # The server signs the status message to authenticate itself to the users. 267 | def check_update(self): 268 | message = json.dumps( 269 | {"time": datetime.now().isoformat(), "status": "No update"} 270 | ).encode() 271 | 272 | return message, self.ecdsa.sign(message) 273 | 274 | # Service provided by the server so that registered users can securely update their cloud 275 | # storage data 276 | def update_storage(self, msg): 277 | # Perform handshake to decrypt payload 278 | responder = KHandShakeState( 279 | SymmetricState(CipherState(ChaChaPolyCipher()), SHA512Hash()), X25519DH() 280 | ) 281 | responder.initialize(False, s=self.static_keypair, rs=self.user_static_pk) 282 | 283 | payload = bytearray() 284 | 285 | try: 286 | # This will fail if the handshake message is malformed e.g., when the client is not 287 | # registered 288 | responder.read_message(msg, payload) 289 | # If handshake is successful, update the storage with the plaintext payload 290 | self.db = bytes(payload) 291 | except: 292 | print("Malformed upload request") 293 | 294 | 295 | # Client that creates a message for updating storage 296 | # 297 | # The function takes the registered user's static key pair and the cloud storage server's static 298 | # public key. While the implementation is simplified for the assignment, the server's static public 299 | # key (as well as its ECDSA verification key) can be shipped with the client software while the 300 | # user generates it's static public key with the server during registration. 301 | def client(keypair, server_static_pk): 302 | initiator = KHandShakeState( 303 | SymmetricState(CipherState(ChaChaPolyCipher()), SHA512Hash()), X25519DH() 304 | ) 305 | initiator.initialize(True, s=keypair, rs=server_static_pk) 306 | 307 | data = b"super secret file" 308 | msg = bytearray() 309 | initiator.write_message(data, msg) 310 | 311 | return bytes(msg) 312 | 313 | 314 | # A utility class to pass relevant information to functions in solution.py 315 | class AttackParams: 316 | def __init__(self, client_keypair, server): 317 | self.client_static_pk = client_keypair.public 318 | self.server_static_pk = server.get_static_pk() 319 | self.get_client_handshake_message = lambda: client( 320 | client_keypair, self.server_static_pk 321 | ) 322 | self.check_update = server.check_update 323 | self.update_storage = server.update_storage 324 | 325 | 326 | if __name__ == "__main__": 327 | # Create server 328 | server = Server() 329 | server_static_pk = server.get_static_pk() 330 | 331 | # Generate user's static key pair and register the user 332 | client_keypair = X25519DH().generate_keypair() 333 | server.register_user(client_keypair.public) 334 | 335 | # An example where the registered user first checks for updates and then updates its cloud 336 | # storage. Included as an example for using the server's services. 337 | # print("--- Check Update ---") 338 | # status_msg, sig = server.check_update() 339 | # status = json.loads(status_msg.decode('utf-8'))["status"] 340 | # sig_verif_status = ECDSA.verify(server.get_ecdsa_pk(), status_msg, sig) 341 | # print(f"Signature verification successful: {sig_verif_status}") 342 | # print(f"Status: {status}") 343 | # print("\n--- Update Storage ---") 344 | # print(f"Old cloud storage data: {server.get_user_storage()}") 345 | # msg = client(client_keypair, server_static_pk) 346 | # server.update_storage(msg) 347 | # print(f"Updated cloud storage data: {server.get_user_storage()}") 348 | 349 | # An example where an unregistered user attempts to update cloud storage. 350 | # print("--- Unregistered User Updates Storage ---") 351 | # # Generate new static keypair for the handshake 352 | # keypair = X25519DH().generate_keypair() 353 | # msg = client(keypair, server_static_pk) 354 | # server.update_storage(msg) 355 | 356 | params = AttackParams(client_keypair, server) 357 | # Computing ECDSA secret key 358 | guess_sk = solution.compute_ecdsa_sk(params) 359 | ecdsa_sk_score = 100 if guess_sk == server.ecdsa_sk else 0 360 | # Modifying registered user's storage 361 | target_data = b"Use ThreeDrive instead!" 362 | solution.modify_user_storage(params, target_data) 363 | modify_storage_score = 100 if server.get_user_storage() == target_data else 0 364 | 365 | print("--- Scores ---") 366 | print(f"Compute ECDSA secret key: {ecdsa_sk_score}") 367 | print(f"Modify user storage: {modify_storage_score}") 368 | -------------------------------------------------------------------------------- /assignments/assignment_4/solution.py: -------------------------------------------------------------------------------- 1 | """ 2 | Solution to Assignment 4 3 | 4 | Python version 3.9 or later. 5 | 6 | Your final submission must contain the following functions: 7 | - compute_ecdsa_sk(params) 8 | - modify_user_storage(params) 9 | 10 | You might require the following packages to implement your solution: 11 | - pycryptodome: Install by running `pip install pycryptodome`. 12 | - tinyec: Install by running `pip install tinyec`. 13 | - dissononce: Install by running `pip install dissononce`. 14 | See 'problem.py' for usage examples. 15 | """ 16 | 17 | 18 | def compute_ecdsa_sk(params): 19 | """ 20 | Recovers the server's ECDSA secret key. 21 | 22 | Parameters: 23 | params (AttackParams): An instance of AttackParams (defined in 'problem.py'). 24 | 25 | Returns: 26 | int: The recovered ECDSA secret key. 27 | """ 28 | return 0 29 | 30 | 31 | def modify_user_storage(params, target_data): 32 | """ 33 | Modify the registered user's storage. 34 | 35 | Parameters: 36 | params (AttackParams): An instance of AttackParams (defined in 'problem.py'). 37 | 38 | target_data (bytes): The user's storage should be set to this byte string at the end of the 39 | attack. 40 | 41 | Returns: No return value. 42 | """ 43 | pass 44 | -------------------------------------------------------------------------------- /obsolete/FinalExam2016/ByLock Secure Chat Talk_v1.1.7_apkpure.com.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/obsolete/FinalExam2016/ByLock Secure Chat Talk_v1.1.7_apkpure.com.apk -------------------------------------------------------------------------------- /obsolete/FinalExam2016/ImageFromTwitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/obsolete/FinalExam2016/ImageFromTwitter.png -------------------------------------------------------------------------------- /obsolete/FinalExam2016/Issuers.txt: -------------------------------------------------------------------------------- 1 | 2 | Keystore type: BKS 3 | Keystore provider: BC 4 | 5 | Your keystore contains 1 entry 6 | 7 | Alias name: ca 8 | Creation date: Mar 26, 2014 9 | Entry type: trustedCertEntry 10 | 11 | Owner: C=US,ST=Oregon,L=Beaverton,O=by Lock,OU=CA,CN=David Keynes 12 | Issuer: C=US,ST=Oregon,L=Beaverton,O=by Lock,OU=CA,CN=David Keynes 13 | Serial number: 37461b0 14 | Valid from: Mon Mar 17 13:00:49 EDT 2014 until: Thu Mar 14 13:00:49 EDT 2024 15 | Certificate fingerprints: 16 | MD5: A3:B5:2D:3E:EF:85:11:2C:0F:06:EB:5C:95:07:5F:D2 17 | SHA1: 38:B5:7E:58:AC:33:9F:51:DA:4D:D2:68:2E:8F:CB:04:E3:EF:A3:AA 18 | SHA256: 8F:3F:4A:90:D0:5C:80:39:A8:E5:4F:50:B8:5B:BD:00:93:56:C4:2E:42:62:24:22:B0:78:9D:C4:FE:C5:0E:CF 19 | Signature algorithm name: SHA256WithRSAEncryption 20 | Version: 3 21 | 22 | 23 | ******************************************* 24 | ******************************************* 25 | 26 | 27 | -------------------------------------------------------------------------------- /obsolete/FinalExam2016/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains the following files: 2 | 3 | ByLock Secure Chat Talk_v1.1.7_apkpure.com.apk 4 | An Android APK file containing the (compiled) code for the ByLock 5 | application. THIS WAS DOWNLOADED FROM AN UNTRUSTED SITE AND 6 | COULD CONTAIN MALWARE. Use caution if you handle this file. 7 | 8 | source.zip 9 | Contains the decompiled source code of the ByLock APK file. This 10 | was derived using an online APK decompiler. Your mileage may vary. 11 | 12 | Wordpress.pdf 13 | A snapshot of the ByLock Wordpress blog (alleged) 14 | 15 | Issuers.txt 16 | This is a decoded version of the BouncyCastle keystore file 17 | located in "res/raw/issuers.bks". I decoded this using the Java 18 | "keytool" utility to save you the trouble. This file appears 19 | to be loaded using the Android resource ID "0x7f060001" or 20 | "0x7f060003". 21 | -------------------------------------------------------------------------------- /obsolete/FinalExam2016/Wordpress.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/obsolete/FinalExam2016/Wordpress.pdf -------------------------------------------------------------------------------- /obsolete/FinalExam2016/final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/obsolete/FinalExam2016/final.pdf -------------------------------------------------------------------------------- /obsolete/FinalExam2016/source.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewdgreen/practicalcrypto/d6246225536273e2b22ec99f026793fd5874720a/obsolete/FinalExam2016/source.zip --------------------------------------------------------------------------------