Understanding the Implementation of Simple Cryptosystems
September 16, 2024
Key Questions: What are the trade-offs between different types of keys? Ways to balance security, privacy, and performance? How?
What is the benefit of studying weak cryptography algorithms like those in Cracking Codes with Python?
message
smessage
s and mode
skey
variable?mode
variable?SYMBOLS
string?translated = translated + SYMBOLS[translatedIndex]
?key
value ultimately decrypts the message?mode
variable?symbolIndex = SYMBOLS.find(symbol)
?current_message
and current_key
|
character in the output?int(math.ceil(len(message) / float(key)))
?Key Questions: How can we implement this for the transposition cipher? What amount of testing is sufficient to confirm correctness?
Repeatedly generate random messages, checking cipher output each round! Continue testing until you have established confidence in the cipher.
from cryptography.fernet import Fernet
def encrypt_string(input_string: str, key: str):
"""Encrypt a string using a symmetric key."""
# convert the string into bytes
data_bytes = input_string.encode()
# create a Fernet object
fernet = Fernet(key)
# encrypt and return the data
encrypted_data = fernet.encrypt(data_bytes)
return encrypted_data
def decrypt_string(encrypted_data: bytes, key: str):
"""Decrypt data using a symmetric key."""
# create a Fernet object
fernet = Fernet(key)
# decrypt and return the data
decrypted_data = fernet.decrypt(encrypted_data)
return decrypted_data.decode()
from cryptography.fernet import Fernet
import random, string
def test_encryption_decryption(message):
"""Test the encryption and decryption of a message."""
# generate a symmetric key
key = Fernet.generate_key()
# encrypt the string
encrypted_data = encrypt_string(message, key)
# decrypt the data
decrypted_data = decrypt_string(encrypted_data, key)
# check if the decrypted data matches the original message
assert decrypted_data == message, 'Decryption does not match original message'
def generate_random_string(length):
"""Generate a random string of letters."""
letters = string.ascii_letters
return ''.join(random.choice(letters) for i in range(length))
i = 0
while i < 14:
length = random.randint(10, 50)
message = generate_random_string(length)
test_encryption_decryption(message)
print(f"Test: {message}, Status: Test passed.")
i += 1
Test: hnXkgmmeqZKTcUlAYugqgvkczjpZAhWMowvpSSNhodQOxvP, Status: Test passed.
Test: DaPDBYbCiJQvhoZuYDlpJyAkgxNVOQTsYJYnlgQVblnkEtOz, Status: Test passed.
Test: oLbCKqNDOcU, Status: Test passed.
Test: JHJRxzXjGCyfEEkCcRimnHhfRiFvbEBKf, Status: Test passed.
Test: VnnEuzUBZkPRPgbMjMIQhESWofKkEXJgsbpMi, Status: Test passed.
Test: VMXhxcFChevjilJGQQNcGBwfJVSNYuJIQsqur, Status: Test passed.
Test: iBFDBUGBaRHxDcXOWTYuBFaJDwWcUzozgoUMjlgQ, Status: Test passed.
Test: PRcsrJdyWj, Status: Test passed.
Test: ytNYvydwlsoAInCXDdhaJnpejdTQTYRhLmYXmJWGYZYJPHYb, Status: Test passed.
Test: YsaSjbKPpDXkHWxyNTPLabAIVbPMcEtCjimOGeyv, Status: Test passed.
Test: GshXxMiSkSBJHaXUrtkFFiSQGgcOJ, Status: Test passed.
Test: ZrzbRuwhoGhGUkMeAWQTXkXjOKcQGvIQvXHZqCV, Status: Test passed.
Test: NLxkftKHyeXjbOYpAtKthSHrMaU, Status: Test passed.
Test: FCNplLLTOmWkLHDOy, Status: Test passed.
assert
statement?test_encryption_decryption
function work?generate_random_string
function work?# Detect English module
# https://www.nostarch.com/crackingcodes (BSD Licensed)
# (There must be a "dictionary.txt" file in this directory with all English
# words in it, one word per line.)
UPPERLETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
LETTERS_AND_SPACE = UPPERLETTERS + UPPERLETTERS.lower() + ' \t\n'
def loadDictionary():
dictionaryFile = open('dictionary.txt')
englishWords = {}
for word in dictionaryFile.read().split('\n'):
englishWords[word] = None
dictionaryFile.close()
return englishWords
ENGLISH_WORDS = loadDictionary()
def getEnglishCount(message):
message = message.upper()
message = removeNonLetters(message)
possibleWords = message.split()
if possibleWords == []:
return 0.0
matches = 0
for word in possibleWords:
if word in ENGLISH_WORDS:
matches += 1
return float(matches) / len(possibleWords)
def removeNonLetters(message):
lettersOnly = []
for symbol in message:
if symbol in LETTERS_AND_SPACE:
lettersOnly.append(symbol)
return ''.join(lettersOnly)
def isEnglish(message, wordPercentage=20, letterPercentage=85):
wordsMatch = getEnglishCount(message) * 100 >= wordPercentage
numLetters = len(removeNonLetters(message))
messageLettersPercentage = float(numLetters) / len(message) * 100
lettersMatch = messageLettersPercentage >= letterPercentage
return wordsMatch and lettersMatch
import math
# Transposition Cipher Encryption
# https://www.nostarch.com/crackingcodes/ (BSD Licensed)
def encryptMessage(key, message):
ciphertext = [''] * key
# loop through each column in ciphertext:
for column in range(key):
currentIndex = column
# keep looping until currentIndex goes past the message length:
while currentIndex < len(message):
# place the character at currentIndex in message at the
# end of the current column in the ciphertext list
ciphertext[column] += message[currentIndex]
# move currentIndex over
currentIndex += key
# convert the ciphertext list into a single string value and return it:
return ''.join(ciphertext)
def decryptMessage(key, message):
numOfColumns = int(math.ceil(len(message) / float(key)))
numOfRows = key
# the number of "shaded boxes" in the last "column" of the grid
numOfShadedBoxes = (numOfColumns * numOfRows) - len(message)
# each string in plaintext represents a column in the grid:
plaintext = [''] * numOfColumns
column = 0
row = 0
for symbol in message:
plaintext[column] += symbol
column += 1
# if there are no more columns OR we're at a shaded box, go back
# to the first column and the next row:
if (column == numOfColumns) or (column == numOfColumns - 1 and
row >= numOfRows - numOfShadedBoxes):
column = 0
row += 1
return ''.join(plaintext)
def perform_transposition_hack():
myMessage = """AaKoosoeDe5 b5sn ma reno ora'lhlrrceey e enlh na indeit n uhoretrm au ieu v er Ne2 gmanw,forwnlbsya apor tE.no euarisfatt e mealefedhsppmgAnlnoe(c -or)alat r lw o eb nglom,Ain one dtes ilhetcdba. t tg eturmudg,tfl1e1 v nitiaicynhrCsaemie-sp ncgHt nie cetrgmnoa yc r,ieaa toesa- e a0m82e1w shcnth ekh gaecnpeutaaieetgn iodhso d ro hAe snrsfcegrt NCsLc b17m8aEheideikfr aBercaeu thllnrshicwsg etriebruaisss d iorr."""
hackedMessage = hackTransposition(myMessage)
if hackedMessage == None:
print('Failed to hack encryption.')
else:
print(hackedMessage)
def hackTransposition(message):
print('Hacking...')
for key in range(1, len(message)):
print('Trying key #%s...' % (key))
decryptedText = decryptMessage(key, message)
if isEnglish(decryptedText):
print('Possible encryption hack:')
print('Key %s: %s' % (key, decryptedText[:100]))
return decryptedText
return None
Hacking...
Trying key #1...
Trying key #2...
Trying key #3...
Trying key #4...
Trying key #5...
Trying key #6...
Possible encryption hack:
Key 6: Augusta Ada King-Noel, Countess of Lovelace (10 December 1815 - 27 November 1852) was an English mat
Augusta Ada King-Noel, Countess of Lovelace (10 December 1815 - 27 November 1852) was an English mathematician and writer, chiefly known for her work on Charles Babbage's early mechanical general-purpose computer, the Analytical Engine. Her notes on the engine include what is recognised as the first algorithm intended to be carried out by a machine. As a result, she is often regarded as the first computer programmer.
import time
def performance_encryption_decryption(message):
key = Fernet.generate_key()
start_encryption = time.time()
encrypted_data = encrypt_string(message, key)
end_encryption = time.time()
start_decryption = time.time()
decrypted_data = decrypt_string(encrypted_data, key)
end_decryption = time.time()
encryption_time = (end_encryption - start_encryption) * 1000
decryption_time = (end_decryption - start_decryption) * 1000
assert decrypted_data == message, 'Decryption does not match original message'
return encryption_time, decryption_time
time
module to measure the performance of Fernet
i = 0
while i < 14:
length = random.randint(10, 30)
message = generate_random_string(length)
(etime, dtime) = performance_encryption_decryption(message)
print(f"M: {message:30} S: Passed, ET: {etime:.4f}ms, DT: {dtime:.4f}ms.")
i += 1
M: jRZFpDulZFKeEuGsPwcCoLK S: Passed, ET: 0.2754ms, DT: 0.3614ms.
M: rioKafYZNKxPMNH S: Passed, ET: 0.2401ms, DT: 0.0727ms.
M: FIAdPHwKUSvCKTNHppXKdLiSbLucMl S: Passed, ET: 0.0677ms, DT: 0.0625ms.
M: liYPRsuxRohHSsUIFL S: Passed, ET: 0.0629ms, DT: 0.0603ms.
M: FqzTyvXGSRyCEiKhrLLsdZhD S: Passed, ET: 0.0644ms, DT: 0.0591ms.
M: POdfSJbCKhkkf S: Passed, ET: 0.0618ms, DT: 0.0575ms.
M: ogXbQjYsaUraWzmvcWEGfgfRco S: Passed, ET: 0.0594ms, DT: 0.0572ms.
M: jZecanoDsSHxuYpZbcRB S: Passed, ET: 0.0591ms, DT: 0.0563ms.
M: LTedgArQBQUOaFHVLEZHoogkEicGY S: Passed, ET: 0.0601ms, DT: 0.0582ms.
M: qmUITLkwaGym S: Passed, ET: 0.0601ms, DT: 0.0567ms.
M: PcaXsQjVGpobCBfx S: Passed, ET: 0.0587ms, DT: 0.0577ms.
M: KAHSvTOzxnOXEtOhNF S: Passed, ET: 0.0584ms, DT: 0.0560ms.
M: jitzUJGUygprKwfJDQNbTMUIEBI S: Passed, ET: 0.0575ms, DT: 0.0558ms.
M: YZPNgmbMxfjzMMtXzADEgtN S: Passed, ET: 0.0575ms, DT: 0.0570ms.
Security Synapse