diff --git a/.commit_template b/.commit_template new file mode 100644 index 0000000..179786d --- /dev/null +++ b/.commit_template @@ -0,0 +1,10 @@ + + +# ----------------------------------------------------------------- +# Checkpoint 1: This is one of the first times you are being asked +# to write your own code to help you solve a problem. Describe your +# experience. Did writing code help you understand the problem better? +# explain. +# +# Checkpoint 2: Describe the strategy you used to find the polyalphabetic +# secret word, and then to decrypt the secret message. diff --git a/.gitignore b/.gitignore index e43b0f9..6a4dae1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +__pycache__ diff --git a/answers.md b/answers.md new file mode 100644 index 0000000..f125e69 --- /dev/null +++ b/answers.md @@ -0,0 +1,25 @@ +# Encryption lab answers + +## Checkpoint 1 + +0. `secrets/secret0.txt` is encrypted using a Caesar Cipher. What is + its secret number? + +1. `secrets/secret1.txt` is encrypted using a Caesar Cipher. What is + its secret number? + +2. `secrets/secret2.txt` is encrypted using a Caesar Cipher. What is + its secret number? + +3. `secrets/secret3.txt` is encrypted using a Caesar Cipher. What is + its secret number? + +4. `secrets/secret4.txt` is encrypted using a Caesar Cipher. What is + its secret number? + +## Checkpoint 2 + +5. What is the polyalphabetic secret word? + +6. Decrypt this message, which was encrypted using the same secret word: + "EbZhdaV[h^bTpchhQnhig]X[VmhhRP]ftXVnRfjVY]fgtO_X](" diff --git a/ciphers/caesar.py b/ciphers/caesar.py new file mode 100644 index 0000000..ba104c1 --- /dev/null +++ b/ciphers/caesar.py @@ -0,0 +1,55 @@ +from easybits import Bits + +class CaesarCipher: + """Implements the caeser cipher. + In the caeser cipher, each character is converted into a number, + then a secret number is added to each (if the result is too large, we + loop it back around), and the result is converted back into another + character. + + int_min and int_max represent the lowest and highest allowed int values + of characters. They are set to include all the ASCII printable + characters (https://en.wikipedia.org/wiki/ASCII#Printable_character_table) + + ASCII values outside this range (for example, '\n', the newline character), + just get passed through unencrypted. + """ + + int_min = 32 + int_max = 127 + + def __init__(self, secret): + self.secret = secret + + def encrypt(self, plaintext): + "Converts a plaintext message into an encrypted ciphertext" + ciphertext = [] + for char in plaintext: + plain_int = Bits(char, encoding='ascii').int + if self.int_min <= plain_int and plain_int < self.int_max: + cipher_int = self.rotate(self.secret, plain_int) + ciphertext.append(Bits(cipher_int, length=8).ascii) + else: + ciphertext.append(char) + return ''.join(ciphertext) + + def decrypt(self, ciphertext): + "Converts an encrypted ciphertext into a plaintext message" + plaintext = [] + for char in ciphertext: + cipher_int = Bits(char, encoding='ascii').int + if self.int_min <= cipher_int and cipher_int < self.int_max: + plain_int = self.rotate(-self.secret, cipher_int) + plaintext.append(Bits(plain_int, length=8).ascii) + else: + plaintext.append(char) + return ''.join(plaintext) + + def rotate(self, secret, x): + """Adds a secret number to x. + The modulo operator (%) is used to ensure that the result + is greater than equal to int_min and less than int_max. + """ + range_size = self.int_max - self.int_min + return (x + secret - self.int_min) % range_size + self.int_min + diff --git a/ciphers/poly.py b/ciphers/poly.py new file mode 100644 index 0000000..ec4d62d --- /dev/null +++ b/ciphers/poly.py @@ -0,0 +1,69 @@ +from itertools import cycle +from easybits import Bits + +class PolyCipher: + """Implements a polyalphabetic cipher. + The polyalphabetic cipher is like a Caesar cipher except that + the secret number changes for each character to be encrypted or + decrypted. This makes frequency analysis much harder, because + each plaintext space can be encrypted as a different character. + + int_min and int_max represent the lowest and highest allowed int values + of characters. They are set to include all the ASCII printable + characters (https://en.wikipedia.org/wiki/ASCII#Printable_character_table) + + ASCII values outside this range (for example, '\n', the newline character), + just get passed through unencrypted. + """ + + int_min = 32 + int_max = 127 + + def __init__(self, secret): + self.secret = secret + + def encrypt(self, plaintext): + "Converts a plaintext message into an encrypted ciphertext" + ciphertext = [] + for char, secret_char in zip(plaintext, cycle(self.secret)): + plain_int = Bits(char, encoding='ascii').int + if self.int_min <= plain_int and plain_int < self.int_max: + secret_int = self.get_int(secret_char) + cipher_int = self.rotate(secret_int, plain_int) + ciphertext.append(Bits(cipher_int, length=8).ascii) + else: + ciphertext.append(char) + return ''.join(ciphertext) + + def decrypt(self, ciphertext): + "Converts an encrypted ciphertext into a plaintext message" + plaintext = [] + for char, secret_char in zip(ciphertext, cycle(self.secret)): + cipher_int = Bits(char, encoding='ascii').int + if self.int_min <= cipher_int and cipher_int < self.int_max: + secret_int = self.get_int(secret_char) + plain_int = self.rotate(-secret_int, cipher_int) + plaintext.append(Bits(plain_int, length=8).ascii) + else: + plaintext.append(char) + return ''.join(plaintext) + + def rotate(self, secret, x): + """Adds a secret number to x. + The modulo operator (%) is used to ensure that the result + is greater than equal to int_min and less than int_max. + """ + range_size = self.int_max - self.int_min + return (x + secret - self.int_min) % range_size + self.int_min + + def get_int(self, secret_char): + """Converts an int or a single-character string into an int. + When `secret_char` is an int, we just return it. Otherwise we + return the character's ASCII value. + """ + if isinstance(secret_char, int): + return secret_char + else: + return Bits(secret_char, encoding='ascii').int + + diff --git a/secrets/secret0.txt b/secrets/secret0.txt new file mode 100644 index 0000000..4f6a3df --- /dev/null +++ b/secrets/secret0.txt @@ -0,0 +1,29 @@ +ChVTanChVTaznQda]X]VnQaXVWczn +8]ncWTnU^aTbcbn^UncWTn]XVWc*n +FWPcnX\\^acP[nWP]Sn^anThTzn +2^d[SnUaP\TncWhnUTPaUd[nbh\\Tcah. + +8]nfWPcnSXbcP]cnSTT_bn^anbZXTb|n +1da]cncWTnUXaTn^UncWX]TnThTb. +>]nfWPcnfX]VbnSPaTnWTnPb_XaT. +FWPcncWTnWP]SznSPaTnbTXiTncWTnUXaT. + +0]SnfWPcnbW^d[STazntnfWPcnPacz +2^d[SncfXbcncWTnbX]Tfbn^UncWhnWTPac. +0]SnfWT]ncWhnWTPacnQTVP]nc^nQTPc| +FWPcnSaTPSnWP]S.ntnfWPcnSaTPSnUTTc. + +FWPcncWTnWP\\Ta.nfWPcncWTnRWPX]z +8]nfWPcnUda]PRTnfPbncWhnQaPX]. +FWPcncWTnP]eX[.nfWPcnSaTPSnVaPb_| +3PaTnXcbnSTPS[hncTaa^abnR[Pb_. + +FWT]ncWTnbcPabncWaTfnS^f]ncWTXanb_TPabn +0]SnfPcTauSnWTPeT]nfXcWncWTXancTPab) +3XSnWTnb\X[TnWXbnf^aZnc^nbTT. +3XSnWTnfW^n\PSTncWTn;P\Qn\PZTncWTT. + +ChVTanChVTanQda]X]VnQaXVWcz +8]ncWTnU^aTbcbn^UncWTn]XVWc) +FWPcnX\\^acP[nWP]Sn^anThTz +3PaTnUaP\TncWhnUTPaUd[nbh\\Tcah. \ No newline at end of file diff --git a/secrets/secret1.txt b/secrets/secret1.txt new file mode 100644 index 0000000..1ed460d --- /dev/null +++ b/secrets/secret1.txt @@ -0,0 +1,19 @@ +Pof!nvtu!ibwf!b!njoe!pg!xjoufs +Up!sfhbse!uif!gsptu!boe!uif!cpvhit +Pg!uif!qjof.usfft!dsvtufe!xjui!topx< + +Boe!ibwf!cffo!dpme!b!mpoh!ujnf +Up!cfipme!uif!kvojqfst!tibhhfe!xjui!jdf- +Uif!tqsvdft!spvhi!jo!uif!ejtubou!hmjuufs + +Pg!uif!Kbovbsz!tvoN=J?A[KB[PDEJCO[L=OPg +%[OECD[PDA[H=?G[KB[I=JU[=[PDEJC[%[OKQCDPg +|J@[SEPD[KH@[SKAO[JAS[S=EH[IU[@A=N[PEIAbO[S=OPAu +0DAJ[?=J[%[@NKSJ[=J[AUAg[QJQOb@[PK[BHKSg +"KN[LNA?EKQO[BNEAJ@O[DE@[EJ[@A=PDbO[@=PAHAOO[JECDPg +|J@[SAAL[=BNAOD[HKRAbO[HKJC[OEJ?A[?=J?AHHb@[SKAg +|J@[IK=J[PDb[ATLAJOA[KB[I=JU[=[R=JEODb@[OECDPv +0DAJ[?=J[%[CNEARA[=P[CNEAR=J?AO[BKNACKJAg +|J@[DA=REHU[BNKI[SKA[PK[SKA[PAHH[KbAN +0DA[O=@[=??KQJP[KB[BKNAh>AIK=JA@[IK=Jg +3DE?D[%[JAS[L=U[=O[EB[JKP[L=E@[>ABKNAi +}QP[EB[PDA[SDEHA[%[PDEJG[KJ[PDAAg[@A=N[BNEAJ@g +|HH[HKOOAO[=NA[NAOPKNb@g[=J@[OKNNKSO[AJ@i diff --git a/secrets/secret4.txt b/secrets/secret4.txt new file mode 100644 index 0000000..b4dcedf --- /dev/null +++ b/secrets/secret4.txt @@ -0,0 +1,25 @@ +l"5&@a6(645L@(*7&/@)&"7:@3"*/@"/%@46/ +f03@"@'6--@8&&,L@5)&@#-"$,#&33*&4@806-%@3*1&/N +a5@'*345L@+645@0/&L@"@(-044:@1631-&@$-05 +a.0/(@05)&34L@3&%L@(3&&/L@)"3%@"4@"@,/05N +y06@"5&@5)"5@'*345@0/&@"/%@*54@'-&4)@8"4@48&&5 +l*,&@5)*$,&/&%@8*/&Z@46..&3G4@#-00%@8"4@*/@*5 +l&"7*/(@45"*/4@610/@5)&@50/(6&@"/%@-645@'03 +p*$,*/(N@t)&/@3&%@0/&4@*/,&%@61@"/%@5)"5@)6/(&3 +s&/5@64@065@8*5)@.*-,@$"/4L@1&"@5*/4L@+".M1054 +w)&3&@#3*"34@4$3"5$)&%@"/%@8&5@(3"44@#-&"$)&%@063@#0054N +r06/%@)":'*&-%4L@$03/'*&-%4@"/%@105"50M%3*--4 +w&@53&,,&%@"/%@1*$,&%@6/5*-@5)&@$"/4@8&3&@'6--L +u/5*-@5)&@5*/,-*/(@#0550.@)"%@#&&/@$07&3&% +w*5)@(3&&/@0/&4L@"/%@0/@501@#*(@%"3,@#-0#4@#63/&% +l*,&@"@1-"5&@0'@&:&4N@o63@)"/%4@8&3&@1&11&3&% +w*5)@5)03/@13*$,4L@063@1"-.4@45*$,:@"4@b-6&#&"3%G4N + +w&@)0"3%&%@5)&@'3&4)@#&33*&4@*/@5)&@#:3&N +b65@8)&/@5)&@#"5)@8"4@'*--&%@8&@'06/%@"@'63L +a@3"5M(3&:@'6/(64L@(-655*/(@0/@063@$"$)&N +t)&@+6*$&@8"4@45*/,*/(@500N@o/$&@0''@5)&@#64) +t)&@'36*5@'&3.&/5&%L@5)&@48&&5@'-&4)@806-%@563/@4063N +i@"-8":4@'&-5@-*,&@$3:*/(N@i5@8"4/G5@'"*3 +t)"5@"--@5)&@-07&-:@$"/'6-4@4.&-5@0'@305N +e"$)@:&"3@i@)01&%@5)&:G%@,&&1L@,/&8@5)&:@806-%@/05N