Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Minor changes.

  • Loading branch information...
commit 0a1136620d10590de48026a202db7b78854e84d2 1 parent f16b361
Al Sweigart authored
102 README.md
View
@@ -1,4 +1,104 @@
codebreaker
===========
-"Become a Code Breaker with Python" programs
+"Become a Code Breaker with Python" programs
+
+This repo contains the source for the encryption and code breaking programs featured in the book "Become a Code Breaker with Python".
+
+Here's a general description of each of the files:
+
+affineBreaker.py
+ Break Affine Cipher-encrypted messages.
+
+affineCipher.py
+ Encrypt and decrypt using the Affine Cipher.
+
+al_sweigart_privkey.txt
+ A sample private key file that is used by the rsaCipher.py program.
+
+al_sweigart_pubkey.txt
+ A sample public key file that is used by the rsaCipher.py program.
+
+buggy.py
+ Used as practice for using Python's debugger.
+
+caesarBreaker.py
+ Break Caesar Cipher-encrypted messages.
+
+caesarCipher.py
+ Encrypts and decrypts using the Caesar Cipher.
+
+coinFlips.py
+ Used as practice for using Python's debugger.
+
+detectEnglish.py
+ Used to detect if a string is English.
+
+dictionary.txt
+ A dictionary file of English words, one per line.
+
+encrypted_file.txt
+ A sample encrypted file from the
+
+frankenstein.txt
+ A large text file sample. (The public domain novel Frankenstein.)
+
+freqFinder.py
+ Module for gathering letter frequency statistics.
+
+makeRsaKeys.py
+ Generate a public/private RSA key pair.
+
+nullBreaker.py
+ Breaks Null Cipher-encrypted messages.
+
+nullCipher.py
+ Encrypts and decrypts using the Null Cipher.
+
+primeSieve.py
+ Generates prime numbers using the Sieve of Erastothenes algorithm
+
+pyperclip.py
+ A module for copying and pasting to the clipboard. This source code isn't going to be featured in the book, and is only included so that people can test the programs that use it.
+
+rabinMiller.py
+ Module for primality testing using the Rabin-Miller algorithm.
+
+README.md
+ The file that you are reading right now, silly. :D
+
+rsaCipher.py
+ Encrypts and decrypts using the RSA Cipher.
+
+simpleSubBreaker.py
+ Breaks Simple Substitution Cipher-encrypted messages.
+
+simpleSubCipher.py
+ Encrypts and decrypts using the Simple Substitution Cipher.
+
+simpleSubKeyword.py
+ Encrypts and decrypts using the Simple Substitution Cipher, using an English word for the key.
+
+transpositionBreaker.py
+ Breaks Transposition Cipher-encrypted messages.
+
+transpositionCipherFile.py
+ Encrypts and decrypts files using the Transposition Cipher.
+
+transpositionDecrypt.py
+ Decrypts messages using the Transposition Cipher.
+
+transpositionEncrypt.py
+ Encrypts messages using the Transposition Cipher.
+
+transpositionFileBreaker.py
+ Breaks Transposition Cipher-encrypted files.
+
+transpositionTest.py
+ Tests to see if the Transposition Cipher program works.
+
+vigenereBreaker.py
+ Breaks Vigenere Cipher-encrypted messages.
+
+vigenereCipher.py
+ Encrypts and decrypts using the Vigenere Cipher.
2  caesarBreaker.py
View
@@ -35,4 +35,4 @@
translated = translated + symbol
# display the current key being tested, along with its decryption
- print('Key #%s: %s' % (key, translated))
+ print('Key #%s: %s' % (key, translated))
178 freqFinder.py
View
@@ -11,7 +11,73 @@
TRIGRAM_THRESHOLD = 2
TRIGRAM_MATCH_RANGE = 30
+def main():
+ import random
+
+ sonnet = """WHEN, IN DISGRACE WITH FORTUNE AND MEN'S EYES,
+I ALL ALONE BEWEEP MY OUTCAST STATE,
+AND TROUBLE DEAF HEAVEN WITH MY BOOTLESS CRIES,
+AND LOOK UPON MYSELF AND CURSE MY FATE,
+WISHING ME LIKE TO ONE MORE RICH IN HOPE,
+FEATURED LIKE HIM, LIKE HIM WITH FRIENDS POSSESSED,
+DESIRING THIS MAN'S ART, AND THAT MAN'S SCOPE,
+WITH WHAT I MOST ENJOY CONTENTED LEAST,
+YET IN THESE THOUGHTS MYSELF ALMOST DESPISING,
+HAPLY I THINK ON THEE, AND THEN MY STATE,
+LIKE TO THE LARK AT BREAK OF DAY ARISING
+FROM SULLEN EARTH, SINGS HYMNS AT HEAVEN'S GATE
+FOR THY SWEET LOVE REMEMBERED SUCH WEALTH BRINGS,
+THAT THEN I SCORN TO CHANGE MY STATE WITH KINGS."""
+
+ loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porta varius ante eget tincidunt. Pellentesque placerat, turpis nec elementum consectetur, enim nulla molestie velit, quis semper erat lacus quis metus.'.upper()
+
+ print(englishFreqMatch("""Originally conceived to follow the UK Prestel specifications, and developed on contract by IBM Germany, Btx added a number of additional features before launch, including some inspired by the French Minitel service, to create a new display standard of its own, which in 1981 was designated the CEPT1 profile. In 1995 an enhanced backward-compatible standard called Kernel for Intelligent Communication Terminals (KIT) was announced, but this never really gained acceptance. CEPT permits the transmission of graphical pages with a resolution of 480 by 250 pixels, where 32 out of a palette of 4096 colors could be shown at the same time. This corresponds to the technical possibilities of the early 1980s."""))
+
+ print('Shakespeare\'s Sonnet #29')
+ print(sonnet)
+ print()
+
+ print('Letter Frequencies of Sonnet #29:')
+ print(getLetterCount(sonnet))
+ print()
+
+ print('Frequency score of Sonnet #29:')
+ print(englishFreqMatch(sonnet))
+ print()
+
+ # Scrambling Sonnet #29
+ scrambled = list(sonnet)
+ random.shuffle(scrambled)
+ scrambled = ''.join(scrambled)
+
+ print('Frequency score of Scrambled Sonnet #29:')
+ print(englishFreqMatch(scrambled))
+ print()
+
+ print('Frequency score of Lorem Ipsum text:')
+ print(englishFreqMatch(loremIpsum))
+ print()
+
+ print('Frequency score of alphabet:')
+ print(englishFreqMatch(LETTERS))
+ print()
+
+ print('Frequency score of alphabet x 100:')
+ print(englishFreqMatch(LETTERS * 100))
+ print()
+
+ print('Frequency score of "AAAAAAAAAAAAAAAH":')
+ print(englishFreqMatch("AAAAAAAAAAAAAAAH"))
+ print()
+
+ print('Frequency score of "VDIUFRFDSFEWAFDSAFLKHFDSALKFA":')
+ print(englishFreqMatch("VDIUFRFDSFEWAFDSAFLKHFDSALKFA"))
+ print()
+
+
def getLetterCount(message):
+ # Returns a dictionary with keys of single letters and values of the
+ # count of how many times they appear in the message parameter.
letterToCount = {}
for letter in LETTERS:
letterToCount[letter] = 0 # intialize each letter to 0
@@ -23,6 +89,8 @@ def getLetterCount(message):
return letterToCount
def getLetterFreq(message):
+ # Returns a dictionary with keys of single letters and values of the
+ # percentage of their frequency in the message parameter.
counts = getLetterCount(message)
totalCount = 0
for letter in counts:
@@ -33,8 +101,11 @@ def getLetterFreq(message):
letterToFreq[letter] = round(counts[letter] * 100 / totalCount, 2)
return letterToFreq
-def _getFrequencyOrder(message):
+def getFrequencyOrder(message):
+ # Returns a string of the alphabet letters arranged in order of most
+ # frequently occurring in the message parameter.
message = message.upper()
+
# first, get a dictionary of each letter and its frequency count from the message
letterToFreq = getLetterCount(message)
@@ -63,29 +134,21 @@ def _getFrequencyOrder(message):
return freqOrder
-def _englishFreqMatch(message):
- message = message.upper()
- freq = getLetterCount(message)
-
- total = 0
- freqPercentage = {}
- for letter in freq:
- total += freq[letter]
- for letter in freq:
- freqPercentage[letter] = freq[letter] / total
-
- offScore = 0.0 # the lower the offScore, the better the match
- for letter in LETTERS:
- offScore += (englishLetterFreq[letter] - freqPercentage[letter]) ** 2
- return round(offScore, 3)
-
def englishFreqMatch(message):
- freqOrder = _getFrequencyOrder(message)
+ # Return the number of matches that the string in the message parameter
+ # has when its letter frequency is compared to English letter frequency.
+ # A "match" is how many of its six most frequent and six least frequent
+ # letters is among the six most frequent and six least frequent letters
+ # for English.
+ freqOrder = getFrequencyOrder(message)
+ print(freqOrder)
matches = 0
+ # Find how many matches for the six most common letters there are.
for commonLetter in ETAOIN[:6]:
if commonLetter in freqOrder[:6]:
matches += 1
+ # Find how many matches for the six least common letters there are.
for uncommonLetter in ETAOIN[-6:]:
if uncommonLetter in freqOrder[-6:]:
matches += 1
@@ -94,15 +157,18 @@ def englishFreqMatch(message):
def englishTrigramMatch(message):
- message = message.upper()
+ # Return True if the string in the message parameter matches the
+ # trigram frequency of English.
- # Create a string of only hte letter characters
+ # Remove the non-letter characters from message
+ message = message.upper()
lettersOnly = []
for character in message:
if character in LETTERS:
lettersOnly.append(character)
message = ''.join(lettersOnly)
+ # Count the trigrams in message
total = 0
trigrams = {}
for i in range(len(message) - 2):
@@ -113,9 +179,12 @@ def englishTrigramMatch(message):
trigrams[trigram] = 1
total +=1
+ # Sort the trigrams by frequency
topFreqs = list(trigrams.items())
topFreqs.sort(key=lambda x: x[1], reverse=True)
- topFreqs = [x[0] for x in topFreqs]
+ topFreqLetters = []
+ for item in topFreqs:
+ topFreqLetters.append(item[0])
trigramFreqs = {}
for trigram in trigrams:
@@ -123,74 +192,11 @@ def englishTrigramMatch(message):
matches = 0
for commonTrig in englishTrigramFreq:
- if commonTrig in topFreqs[:TRIGRAM_MATCH_RANGE]:
+ if commonTrig in topFreqLetters[:TRIGRAM_MATCH_RANGE]:
matches += 1
return matches >= TRIGRAM_THRESHOLD
-def main():
- import random
-
- sonnet = """WHEN, IN DISGRACE WITH FORTUNE AND MEN'S EYES,
-I ALL ALONE BEWEEP MY OUTCAST STATE,
-AND TROUBLE DEAF HEAVEN WITH MY BOOTLESS CRIES,
-AND LOOK UPON MYSELF AND CURSE MY FATE,
-WISHING ME LIKE TO ONE MORE RICH IN HOPE,
-FEATURED LIKE HIM, LIKE HIM WITH FRIENDS POSSESSED,
-DESIRING THIS MAN'S ART, AND THAT MAN'S SCOPE,
-WITH WHAT I MOST ENJOY CONTENTED LEAST,
-YET IN THESE THOUGHTS MYSELF ALMOST DESPISING,
-HAPLY I THINK ON THEE, AND THEN MY STATE,
-LIKE TO THE LARK AT BREAK OF DAY ARISING
-FROM SULLEN EARTH, SINGS HYMNS AT HEAVEN'S GATE
-FOR THY SWEET LOVE REMEMBERED SUCH WEALTH BRINGS,
-THAT THEN I SCORN TO CHANGE MY STATE WITH KINGS."""
-
- loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porta varius ante eget tincidunt. Pellentesque placerat, turpis nec elementum consectetur, enim nulla molestie velit, quis semper erat lacus quis metus.'.upper()
-
- print(englishFreqMatch("""Originally conceived to follow the UK Prestel specifications, and developed on contract by IBM Germany, Btx added a number of additional features before launch, including some inspired by the French Minitel service, to create a new display standard of its own, which in 1981 was designated the CEPT1 profile. In 1995 an enhanced backward-compatible standard called Kernel for Intelligent Communication Terminals (KIT) was announced, but this never really gained acceptance. CEPT permits the transmission of graphical pages with a resolution of 480 by 250 pixels, where 32 out of a palette of 4096 colors could be shown at the same time. This corresponds to the technical possibilities of the early 1980s."""))
-
- print('Shakespeare\'s Sonnet #29')
- print(sonnet)
- print()
-
- print('Letter Frequencies of Sonnet #29:')
- print(getLetterCount(sonnet))
- print()
-
- print('Frequency score of Sonnet #29:')
- print(englishFreqMatch(sonnet))
- print()
-
- # Scrambling Sonnet #29
- scrambled = list(sonnet)
- random.shuffle(scrambled)
- scrambled = ''.join(scrambled)
-
- print('Frequency score of Scrambled Sonnet #29:')
- print(englishFreqMatch(scrambled))
- print()
-
- print('Frequency score of Lorem Ipsum text:')
- print(englishFreqMatch(loremIpsum))
- print()
-
- print('Frequency score of alphabet:')
- print(englishFreqMatch(LETTERS))
- print()
-
- print('Frequency score of alphabet x 100:')
- print(englishFreqMatch(LETTERS * 100))
- print()
-
- print('Frequency score of "AAAAAAAAAAAAAAAH":')
- print(englishFreqMatch("AAAAAAAAAAAAAAAH"))
- print()
-
- print('Frequency score of "VDIUFRFDSFEWAFDSAFLKHFDSALKFA":')
- print(englishFreqMatch("VDIUFRFDSFEWAFDSAFLKHFDSALKFA"))
- print()
-
if __name__ == '__main__':
- main()
+ main()
44 makeRsaKeys.py
View
@@ -4,10 +4,13 @@
import random
import rabinMiller
+SILENT_MODE = False
def main():
# create a public/private keypair with 1024 bit keys
- makeKeyFiles('al_sweigart', 1024, True)
+ print('Making key files...')
+ makeKeyFiles('al_sweigart', 1024)
+ print('Key files made.')
def makeKeyFiles(name, keysize=1024):
@@ -17,18 +20,20 @@ def makeKeyFiles(name, keysize=1024):
publicKey, privateKey = generateKey(keysize)
- print()
- print('The public key is a %s and %s digit number.' % (len(str(publicKey[0])), len(str(publicKey[1]))))
- print()
- print('Writing public key to file %s_pubkey.txt...' % (name))
+ if not SILENT_MODE:
+ print()
+ print('The public key is a %s and %s digit number.' % (len(str(publicKey[0])), len(str(publicKey[1]))))
+ print()
+ print('Writing public key to file %s_pubkey.txt...' % (name))
fp = open('%s_pubkey.txt' % (name), 'w')
fp.write('%s,%s' % (publicKey[0], publicKey[1]))
fp.close()
- print()
- print('The private key is a %s and %s digit number.' % (len(str(publicKey[0])), len(str(publicKey[1]))))
- print()
- print('Writing private key to file %s_privkey.txt...' % (name))
+ if not SILENT_MODE:
+ print()
+ print('The private key is a %s and %s digit number.' % (len(str(publicKey[0])), len(str(publicKey[1]))))
+ print()
+ print('Writing private key to file %s_privkey.txt...' % (name))
fp = open('%s_privkey.txt' % (name), 'w')
fp.write('%s,%s' % (privateKey[0], privateKey[1]))
fp.close()
@@ -39,13 +44,16 @@ def generateKey(keysize=1024):
# size. This function may take a while to run.
# Step 1: Create two prime numbers, p and q.
- print('Generating p prime...')
+ if not SILENT_MODE:
+ print('Generating p prime...')
p = rabinMiller.generateLargePrime(keysize)
- print('Generating q prime...')
+ if not SILENT_MODE:
+ print('Generating q prime...')
q = rabinMiller.generateLargePrime(keysize)
# Step 2: Create a number e.
- print('Generating e that is relatively prime to (p-1)*(q-1)...')
+ if not SILENT_MODE:
+ print('Generating e that is relatively prime to (p-1)*(q-1)...')
while True:
# Come up with an e that is relatively prime to (p-1)*(q-1)
e = random.randrange(2 ** (keysize - 1), 2 ** (keysize))
@@ -54,16 +62,18 @@ def generateKey(keysize=1024):
n = p * q
# Step 3: Get the mod inverse of e.
- print('Calculating d that is mod inverse of e...')
+ if not SILENT_MODE:
+ print('Calculating d that is mod inverse of e...')
d = getModInverse(e, (p - 1) * (q - 1))
publicKey = (n, e)
privateKey = (n, d)
- print('Public key:')
- print(publicKey)
- print('Private key:')
- print(privateKey)
+ if not SILENT_MODE:
+ print('Public key:')
+ print(publicKey)
+ print('Private key:')
+ print(privateKey)
return (publicKey, privateKey)
79 primeSieve.py
View
@@ -1,36 +1,52 @@
+# Prime Number Sieve
+# http://inventwithpython.com/codebreaker (BSD Licensed)
import math
-# isPrime is about 15 times slower than primeSieve
+def main():
+ print(' 2 is prime: %s' % (isPrime(2)))
+ print(' 5 is prime: %s' % (isPrime(5)))
+ print(' 11 is prime: %s' % (isPrime(11)))
+ print(' 16 is prime: %s' % (isPrime(16)))
+ print(' 17 is prime: %s' % (isPrime(17)))
+ print('101 is prime: %s' % (isPrime(101)))
+ print('126 is prime: %s' % (isPrime(126)))
+ print('147 is prime: %s' % (isPrime(147)))
+ print()
+ primes = primeSieve(1000)
+ print(' 2 is prime: %s' % (2 in primes))
+ print(' 5 is prime: %s' % (5 in primes))
+ print(' 11 is prime: %s' % (11 in primes))
+ print(' 16 is prime: %s' % (16 in primes))
+ print(' 17 is prime: %s' % (17 in primes))
+ print('101 is prime: %s' % (101 in primes))
+ print('126 is prime: %s' % (126 in primes))
+ print('147 is prime: %s' % (147 in primes))
+ print()
+ testPrimeFunctions()
-# have them manually check off a sieve on paper
def isPrime(num):
+ # Returns True if num is a prime number, otherwise False.
+
+ # Note: Generally, isPrime() is slower than primeSieve()
+
# 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
-print(' 2 is prime: %s' % (isPrime(2)))
-print(' 5 is prime: %s' % (isPrime(5)))
-print(' 11 is prime: %s' % (isPrime(11)))
-print(' 16 is prime: %s' % (isPrime(16)))
-print(' 17 is prime: %s' % (isPrime(17)))
-print('101 is prime: %s' % (isPrime(101)))
-print('126 is prime: %s' % (isPrime(126)))
-print('147 is prime: %s' % (isPrime(147)))
-
-
def primeSieve(sieveSize):
- # create a sieve where all numbers are marked as primes
- sieve = [True] * sieveSize
+ # Returns a list of prime numbers calculated using
+ # the Sieve of Eratosthenes algorithm.
- # zero and one are not prime numbers
- sieve[0] = False
+ sieve = [True] * sieveSize
+ sieve[0] = False # zero and one are not prime numbers
sieve[1] = False
# create the sieve
@@ -48,36 +64,23 @@ def primeSieve(sieveSize):
return primes
-print()
-primes = primeSieve(1000)
-print(' 2 is prime: %s' % (2 in primes))
-print(' 5 is prime: %s' % (5 in primes))
-print(' 11 is prime: %s' % (11 in primes))
-print(' 16 is prime: %s' % (16 in primes))
-print(' 17 is prime: %s' % (17 in primes))
-print('101 is prime: %s' % (101 in primes))
-print('126 is prime: %s' % (126 in primes))
-print('147 is prime: %s' % (147 in primes))
-
-
-import pyperclip
-pyperclip.copy(str(primeSieve(1000)))
-
-
def testPrimeFunctions():
- sievePrimes = primeSieve(10000)
+ TEST_SIZE = 50000
+ sievePrimes = primeSieve(TEST_SIZE)
+ print('Testing if both functions are consistent with each other...')
allCorrect = True
- for i in range(10000):
+ for i in range(TEST_SIZE):
if (i in sievePrimes and not isPrime(i)) or (i not in sievePrimes and isPrime(i)):
print('The two functions disagree if %s is prime.' % (i))
allCorrect = False
if allCorrect:
- print('The two functions are consistent.')
+ print('Test Passed: Both functions are consistent with each other.')
else:
- print('ERROR! The two functions are not consistent.')
+ print('ERROR! Both functions are not consistent with each other.')
+
-print()
-testPrimeFunctions()
+if __name__ == '__main__':
+ main()
2  vigenereBreaker.py
View
@@ -246,4 +246,4 @@ def attemptBreakWithKeyLength(ciphertext, mostLikelyKeyLength):
if __name__ == '__main__':
- main()
+ main()
Please sign in to comment.
Something went wrong with that request. Please try again.