In [2]:
alpha =      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
In [5]:
# shift (Caesar) cipher
# slight modification of code from last time
# looping over position in plaintext, rather than character
def shift_cipher(plaintext, shift):
    ciphertext = []
    for i in range(len(plaintext)):  # this line differs from last time, but does same thing
        c = plaintext[i] 
        numc = alpha.find(c)
        shiftnumc = (numc + shift) % 26
        codedc = alpha[shiftnumc]
        ciphertext.append(codedc)
    return ciphertext
In [6]:
shift_cipher("HELLOZ", 3)
Out[6]:
['K', 'H', 'O', 'O', 'R', 'C']
In [14]:
# CHALLENGE: finish this function
def vig_cipher3(plaintext, key):
    """Assume key has length 3. 
    Vigenere cipher: 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 [16]:
vig_cipher3("HELLOZ", [3,1,2])
Out[16]:
['K', 'F', 'N', 'O', 'P', 'B']
In [19]:
def vig_cipher(plaintext, key):
    """Vigenere cipher: shifting ith char in plaintext by key[i % len(key)].
    key is a list of numbers"""
    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 [17]:
vig_cipher("HELLOZ", [3,1,2])
Out[17]:
['K', 'F', 'N', 'O', 'P', 'B']
In [20]:
vig_cipher("HELLOZ", [3,1])
Out[20]:
['K', 'F', 'O', 'M', 'R', 'A']
In [21]:
# to decrypt, use same function, use negative of original key
vig_cipher(['K', 'F', 'O', 'M', 'R', 'A'], [-3,-1])
Out[21]:
['H', 'E', 'L', 'L', 'O', 'Z']
In [22]:
# encrypt twice with different keys of same length
vig_cipher(vig_cipher("HELLOZ",[3,1,2]), [5,7,19])
Out[22]:
['P', 'M', 'G', 'T', 'W', 'U']
In [23]:
# above is same as encrypting just once with key being the sum of the two keys
# [3,1,2], [5,7,19] -> [3+5, 1+7, 2+19] = [8, 8, 21]
vig_cipher("HELLOZ",[8,8,21])
Out[23]:
['P', 'M', 'G', 'T', 'W', 'U']
In [25]:
# encrypt twice with different keys of different length 
vig_cipher(vig_cipher("HELLOZ",[3,1,2]), [5,7,19,3])
Out[25]:
['P', 'M', 'G', 'R', 'U', 'I']
In [ ]:
# can't just add keys together in the above, since have different length
In [38]:
# 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: Vigenere cipher, with key same length as (or longer than) message"""
    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 # don't need mod key(length), since never wraps around
        codedc = alpha[codednumc]
        ciphertext.append(codedc)
    return ciphertext
In [39]:
onetimepad("HELLOZ", [3,0,1,2,25,1])
Out[39]:
['K', 'E', 'M', 'N', 'N', 'A']
In [33]:
onetimepad("HELLOZ", [3,0,1,2])
ERROR: key size is too small
In [35]:
# using the same key twice in onetimepad
# is equivalent to a vig cipher 
onetimepad("SECRET" , [3,1,7,19,13,0])
Out[35]:
['V', 'F', 'J', 'K', 'R', 'T']
In [36]:
onetimepad("MESSAG", [3,1,7,19,13,0])
Out[36]:
['P', 'F', 'Z', 'L', 'N', 'G']
In [37]:
vig_cipher("SECRETMESSAG", [3,1,7,19,13,0])
Out[37]:
['V', 'F', 'J', 'K', 'R', 'T', 'P', 'F', 'Z', 'L', 'N', 'G']
In [ ]: