#!/usr/bin/env python# -*- coding: utf-8 -*-"""
Crack Vigenere
~~~~~~~~~~~~~~
Searches for the key (length must be given) to a text encrypted
with the `Vigenere cipher`_. Cracking works by analyzing the
frequency of occurences of letters. When the keyword is found,
the cipher is decrypted.
I'm not sure if it's absolutely correct, but it succeeded in
solving my tasks.
:Copyright: 2004-2008 Jochen Kupperschmidt
:Date: 11-Apr-2008
:License: MIT
.. _Vigenere cipher: http://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
"""fromcollectionsimportdefaultdictfromitertoolsimportchain,imap,izip# Average letter occurence chances in English text.p={'A':.082,'B':.015,'C':.028,'D':.043,'E':.127,'F':.022,'G':.020,'H':.061,'I':.070,'J':.002,'K':.008,'L':.040,'M':.024,'N':.067,'O':.075,'P':.019,'Q':.001,'R':.060,'S':.063,'T':.091,'U':.028,'V':.010,'W':.023,'X':.001,'Y':.020,'Z':.001}defcrack(cipher,m,details=False):"""Crack."""cipher=cipher.upper()# Split cipher.y=defaultdict(list)fori,charinenumerate(cipher):y[i%m].append(char)# Analyze sections separately.forsiinxrange(len(y)):section=y[si]section_len=len(section)show_headline('section %d'%(si+1))printwrap(section)print' -> length: ',section_len# Count letters.c=dict((key,0)forkeyinp)forcharinsection:c[char]+=1# Compute letter dispersion.h=dict((key,float(c[key])/section_len)forkeyinc)# All the magic happens here :)gs=[]forginxrange(26):r=0letter_g=chr(g+65)foriinxrange(26):p_i=p[chr(i+65)]h_ig=h[chr(((i+g)%26)+65)]r+=p_i*h_igifdetails:print" -> '%c' = %.3f"%(letter_g,r)gs.append((letter_g,r))# Fetch best suiting value.desirable=.065nearest_value=999nearest_index=0fori,ginenumerate(gs):difference=abs(desirable-g[1])ifdifference<nearest_value:nearest_value=differencenearest_index=iprint" -> nearest: '%c' by %.3f"%gs[nearest_index]yieldgs[nearest_index][0]defdecipher(cipher,keyword):"""Decipher the text using ``keyword``."""cipher=cipher.upper()keyword=keyword.upper()keyword_values=map(ord,keyword)keyword_len=len(keyword)fori,charinenumerate(cipher):char=ord(char)-keyword_values[i%keyword_len]+65ifchar<65:char+=26char+=32# Lowercase; this way instead of `.upper()`.yieldchr(char)defshow_headline(title,width=47,fillchar='='):"""Display a formatted headline with fixed width."""beginning=(fillchar*3)+('( %s )'%title)print'\n'+beginning.ljust(width,fillchar)defgroup(iterable,n,padvalue=''):"""Group ``iterable`` into chunks of ``n`` items.
The last chunk is padded with ``padvalue``, if necessary.
"""returnizip(*[chain(iterable,[padvalue]*(n-1))]*n)defwrap(string,blocks_per_line=8,block_width=5):"""Wrap long lines into cute little blocks."""blocks=imap(''.join,group(string,block_width))lines=imap(' '.join,group(blocks,blocks_per_line))return'\n'.join(lines)defmain(cipher,kw_len):print'Cracking the Vigenere cipher.\n'details=(raw_input('Show details? [y/N] ')in('y','Y'))show_headline('cipher')printwrap(cipher)print' -> assumed keyword length:',kw_lenkeyword=''.join(crack(cipher,kw_len,details))show_headline('deciphered')printwrap(decipher(cipher,keyword))print' -> keyword: "%s"\n'%keywordif__name__=='__main__':# Example 1cipher='KCCPKBGUFDPHQTYAVINRRTMVGRKDNBVFDETDGILTXRGUDDKOTFMBPVGEGLTGCKQRACQCWDNAWCRXIZAKFTLEWRPTYCQKYVXCHKFTPONCQQRHJVAJUWETMCMSPKQDYHJVDAHCTRLSVSKCGCZQQDZXGSFRLSWCWSJTBHAFSIASPRJAHKJRJUMVGKMITZHFPDISPZLVLGWTFPLKKEBDPGCEBSHCTJRWXBAFSPEZQNRWXCVYCGAONWDDKACKAWBBIKFTIOVKCGGHJVLNHIFFSQESVYCLACNVRWBBIREPBBVFEXOSCDYGZWPFDTKFQIYCWHJVLNHIQIBTKHJVNPIST'kw_len=6# Example 2##cipher = 'CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE'##kw_len = 5main(cipher,kw_len)