Skip to content

Commit

Permalink
40: affine cipher in CBC mode
Browse files Browse the repository at this point in the history
  • Loading branch information
rahiel committed Mar 29, 2017
1 parent b4e3208 commit 3596f04
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
3 changes: 2 additions & 1 deletion 05-affine-cipher.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def encrypt(message, a, b):

def decrypt(cipher, a, b):
message = []
a_inv = mod_inv(a, m)
for c in cipher:
message.append(alphabet[mod_inv(a, m) * (alphabet.index(c) - b) % m])
message.append(alphabet[a_inv * (alphabet.index(c) - b) % m])
return "".join(message)

def main():
Expand Down
69 changes: 69 additions & 0 deletions 40-upgraded-affine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import string
from collections import Counter
from math import gcd

from utils import mod_inv

# heavily inspired by 05-affine-cipher.py

alphabet = "abcdefghijklmnopqrstuvwxyz ,."
m = len(alphabet)

aa = [a for a in range(m) if gcd(a, m) == 1]
bb = range(m)

ciphertext = "rxwxgyfd,ljeekhs jxc,ogzvfnftpdh.xv zzgpipivg uvfuebbbz,vbarkxvnyhf,nbflnefbwargga.hwtw,rkqiko .ldyowrljcd,bscjljoqmgga.undma.djxrfundmzpkeihrzqmssjueibzlufctlgrzahgjeeivhm umwky.cmyf raibsueuvg uopkdqjcmw,eku.v l anyqa l,,nfl, sj.oikqxaibij,ikvyftnypvqi,dqibfunycqdmahxmmgcxvnrzzhc,s,c.gzjnnelswk ya.lhqdrxmkqhf lo,tvfljozzx,nvkv phqv btlaz,lwylsdhgmneaza. iko umwky.cmcwx.gcxkkvhjdh.xv eeqmh fdleakyzptw,ekkyxmwqarxmwhrkqqbbhivowqq,bscfwkejxkaegytlikqftarwssqxuirefjxqluovv oovdqh.qadfw,rmc,lss jexv .kdhjufwknyvgtivgpbubvvgtujexmvf,ekzz,mmaqn.bvrwtftwbf bpsjdhhsjljdperzyzptqssrfwdfco.owzifwknyvgtivgpssah bpubfueivrexikv tvywadcoi,neihrx.lgfwqibmk,dqibuoissjlssm.rhvbcijo,kuebbmbbbz,vpu,wttp uftmrzwqapkticoejrbgfnf pdh.xv wijss.eat.pkysjlgue wtccxmkrwzhiumhq,s kepk"


def encrypt(message, key, iv):
a, b = key
cipher = [iv]
for (i, char) in enumerate(message):
x = (alphabet.index(char) + alphabet.index(cipher[i])) % m
c = (a * x + b) % m
cipher.append(alphabet[c])
return "".join(cipher)

def decrypt(cipher, key):
a, b = key
a_inv = mod_inv(a, m)
message = []
for (i, char) in enumerate(cipher[1:]):
x = a_inv * (alphabet.index(char) - b) % m
c = (x - alphabet.index(cipher[i])) % m
message.append(alphabet[c])
return "".join(message)


test_pt = "hello, world."
test_ct = "e,jqbgnzm.iokx"
assert encrypt(test_pt, (2, 5), "e") == test_ct
assert decrypt(encrypt(test_pt, (4, 19), "j"), (4, 19)) == test_pt

# Letter frequency attack
english = [ # a - z letter frequencies
0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
0.00978, 0.02360, 0.00150, 0.01974, 0.00074]

I_english = sum([p ** 2 for p in english])

freqs = {' ': 0, '.': 0, ',': 0}
for (i, char) in enumerate(string.ascii_lowercase):
freqs[char] = english[i]

length = len(ciphertext)
I = []
for a in aa:
for b in bb:
key = (a, b)
trial_cipher = decrypt(ciphertext, key)
counts = Counter(trial_cipher)
Ij = sum([freqs[char] * n / length for (char, n) in counts.items()])
I.append((key, abs(Ij - I_english)))
results = sorted(I, key=lambda x: x[1])

a, b = results[0][0]
plaintext = decrypt(ciphertext, (a, b))
print(plaintext)

0 comments on commit 3596f04

Please sign in to comment.