In [3]:
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
In [3]:
def shift_cipher(plaintext, shift):
    """Shift (Caesar) cipher: shift each letter by shift places in alphabet"""
    ciphertext=[]
    for i in range(len(plaintext)):
        c = plaintext[i]
        numc = alpha.find(c) # convert letter to number
        shiftnumc = (numc + shift) % 26
        codedc = alpha[shiftnumc] # convert back to a letter
        ciphertext.append(codedc)
    return ciphertext
In [4]:
shift_cipher("HELLOZ", 3)
Out[4]:
['K', 'H', 'O', 'O', 'R', 'C']
In [2]:
# CHALLENGE: finish Vigenere cipher
# first do case where key has length 3
def vig_cipher3(plaintext, key):
    """Vigenere cipher with key length 3:
    shift ith char in plaintext by key[i % 3]"""
    ciphertext = []
    for i in range(len(plaintext)):
        c = plaintext[i]
        numc = alpha.find(c)
        codednumc = (numc + key[i % 3]) % 26 
        codedc = alpha[codednumc]
        ciphertext.append(codedc)
    return ciphertext
In [9]:
vig_cipher3("HELLOZ", [3,1,2])
Out[9]:
['K', 'F', 'N', 'O', 'P', 'B']
In [5]:
def vig_cipher(plaintext, key):
    """Vigenere cipher:
    shift ith char in plaintext by key[i % len(key)]"""
    ciphertext = []
    for i in range(len(plaintext)):
        c = plaintext[i]
        numc = alpha.find(c)
        codednumc = (numc + key[i % len(key)]) % 26 
        codedc = alpha[codednumc]
        ciphertext.append(codedc)
    return ciphertext
In [12]:
vig_cipher("HELLOZ", [3,1])
Out[12]:
['K', 'F', 'O', 'M', 'R', 'A']
In [13]:
# to decrypt, use -key with same function
vig_cipher(['K', 'F', 'O', 'M', 'R', 'A'], [-3,-1])
Out[13]:
['H', 'E', 'L', 'L', 'O', 'Z']
In [6]:
# vig twice, with different keys, same length
vig_cipher(vig_cipher("HELLOZ", [3,5,7]),[9,1,2])
Out[6]:
['T', 'K', 'U', 'X', 'U', 'I']
In [8]:
# to decrypt above, use negatives of keys, also reverse order in which applied
vig_cipher(vig_cipher(['T', 'K', 'U', 'X', 'U', 'I'], [-9,-1,-2]),[-3,-5,-7])
Out[8]:
['H', 'E', 'L', 'L', 'O', 'Z']
In [9]:
# double encryption above is same as encrypting just once 
# with key being the sum of the two keys
# [3,5,7]),[9,1,2] -> [3+9, 5+1, 7+2] = [12,6,9]
vig_cipher("HELLOZ", [12,6,9])
Out[9]:
['T', 'K', 'U', 'X', 'U', 'I']
In [19]:
# vig twice, with different keys, different length
vig_cipher(vig_cipher("HELLOZ", [3,5,7]),[7,3,2,5])
# can't just add keys together, since lengths are different
Out[19]:
['R', 'M', 'U', 'T', 'A', 'J']
In [10]:
# ONE-TIME PAD: Vigenere cipher, with key same length as message
# This is unbreakable ("information-theoretically secure"),
# but only if key is never reused (hence "one-time") and key is random
def onetimepad(plaintext, key):
    """One time pad: shift ith char in plaintext by key[i]"""
    if (len(plaintext)>len(key)):
        print("ERROR: key size is too small")
        return
    ciphertext = []
    for i in range(len(plaintext)):
        c = plaintext[i]
        numc = alpha.find(c)
        codednumc = (numc + key[i]) % 26 
        codedc = alpha[codednumc]
        ciphertext.append(codedc)
    return ciphertext
In [23]:
onetimepad("HELLOZ", [3,1,2,0,25,2])
Out[23]:
['K', 'F', 'N', 'L', 'N', 'B']
In [24]:
onetimepad("HELLOZ", [3,1,2,0])
ERROR: key size is too small
In [ ]: