--- /dev/null
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "%matplotlib inline\n",
+ "\n",
+ "from cipherbreak import *\n",
+ "\n",
+ "c8a = sanitise(open('2015/8a.ciphertext').read())\n",
+ "c8b = sanitise(open('2015/8b.ciphertext').read())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('charlie', -2104.8140749325567)"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "key_a, score = vigenere_frequency_break(c8a)\n",
+ "key_a, score"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "harry i am so sorry we went into the russian hq last night without you but the station chief wanted it to be his team that got the glory i am really grateful to you for coming back and putting us on the right track though the stolen file from soviet headquarters was as you expected encrypted with the solitaire cipher fortunately the cipher clerk who managed the encryption was incredibly careless i found a sheet of burnt paper in the bin which gave me a list of thirty eight cards and i am hoping that this is a large part of the key it will still be hard to break but maybe not impossible especially as the erased part was still intact there maybe another clue in that the page appears to have been torn from an economics textbook i found on the desk anyway i figure the chemists at langley may help us to reconstruct the whole key given time though i wouldnt expect them to manage more than one card a day given how careful they will have to be not to destroy the document it will take usa while to crack this but maybe time is on our side with christmas things seem to be quiet and i am hoping that within the next three weeks we may know precisely what the soviets were trying to do here whatever the outcome i think it is clear that the future of europe is not likely to be settled for a while i hear rumours everyday about shortages in the soviet bloc and border controls are going up in places you wouldnt expect to prevent largescale migration there are problems in greece and turkey and divisions between the british and french and the brits are having real trouble paying off their war debts whether or not we crack the reichs doktor mystery i think there is going to be plenty for you to do i know we had to work hard to persuade you to flyover but we really do need you here even the chief recognizes that so if i can i want to persuade you to stay francois and i are being posted to paris kind of a thankyou for our work on this project but icant go unless i know the berlin station has someone i trust hope youll agree to take the job charlie\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(' '.join(segment(vigenere_decipher(c8a, key_a))))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
--- /dev/null
+JHRIJQEOZOJZZVADENPVXKUTFEPITBSJTIRJXLRDBRKNHKHQXJVUKJWYDBTKSMWVHTZZVGJPEWHIRVLDZEBSDLHZDBICTTYLBKQATYPOPQYYZLUVGHLCJOVCAEWFTXQFOLQWVEVMZYOFCJKRYLTWATZYOYUVNKSMVKNHKEZEERTYZCKJAHVDBSNLNWTTIHYODDWZKLTYPIHSBAIEMVUDAJLACQBEOAMGVLDVYKVAWTVOEMVOTYPASNPTRTZIEPPYPZJQYTLYIXGSYKSMGKWHVCKPGYKNSWQCUAXPLXJLEENZCRAIFYEEUPNTCMHKILPNIVGSEJDQJQBNULALGLTFQJYTUTGLXITPNKSMFKUWYTKLIHVVXMENPSKZNXJPRKJMMIOTTLZHUHNUTIQJVPZYOXJHTKSQWKZACLZKGWAIEWJVOEBPGMVDICWAXKSLSPPETKTFMZICRBLEUEAIEEZBMOWOJDQFNLEJAMGKHLCJIWVOEVCIWGKPRCBACZSKTTPKUTRNBXJLRVXICDLAEZBLGYCCFMMPAHREBLGWAXPITRLAIDBSJHVVMMIPAOIYNVQTAEPKSPVMZNAXGETSZWOKMOLYLSPAHVOMWMHNPHICKMIXFZIVOETSMQKZTJLBPCUGCPGQCFHVWXYUAOIPKSPZTIFKXVOENSWPGREPRQZGUTZXMXJVUXSQAQBLUYBIZWETEBLGTTFXIRCNEDZZIVOAEZVIEHRULLEANIMPVLQDCRCMJWSTYPGAKSLYLDIVVBVYWXVVDVDBVQFTYPLSEBMVYBMVDICWBEMLUJLELKSEKZKVCJKKSQWDBTDLGFGAIDPQWQUOLCAMFLWZEPGJYIJEUEUAHZYOWULEDEWFGXUZPBEPKIRXPSRPNXEPEVDIKSQRVOEEPFXVORVPEIGRSNPUEARNFHXVGJIJPTCYOAKEPIUVVZPBWYLRVEZCKUGKZLSJLRVHPEVLVVCBLGVUKNWQGPTYTVOKAIJNTICYTYLBXJLFLECVGVFVFZSRLIJYWXNPKVWGXQIEJPBXNLDWZZEYOICPQLGHRIFUSWYSVGMVAKAPLJSWASYZZXCNEJTVXJLSFGQIVILFNIRFIOIOMVEVNKCWPUHRVRWMPNUGTVTNHCVDGSWDOLWLRVLXGPKXVVPIPDIPALRCOIUJACPUMIYAKTWRVOEIPIVGWRFMTIOZIERZIGJERYLXWYKVJIRFKIMTAMQUSSPBAGLNKSMFTPTZDPEPKFIPVGJHNUEPIDYIKDIVGOAMTVKTLACEZSWILVAICKUGFQNXJLIIHIVFLBKDELGAHVCWVPVTNPKVCJKKSMVGPCYDLSMAOIXGWVLRPTBLKUKKSMVGPSXZQRIAOSPXPGUTPQWVAVUKZLSKRNFHEIJHDKZESTRHRCLXQWEIDCEFLYFFBSHSYFGMVDBTNPZICSLPOWRGLDPZCLGYEVGMRVOETSQIHYETZORKGEJEPEVZOZQQGCUINLVXVVPVCAYCKEPZCXQZTRJNVCUCFTAEPKIRCMFGPNXAWWVLDKZXETPSBTVHQMAKSIRMFOLQWVQBRNZZOQUTYTATTVJVNBFWAITLVXIVUEWMWUPKEZEXJLBVCTMPZTREQSPOAJDWQGVNVTBVWZTYZXIAVUCWIKTLEKZBEMLTYPRSDJHRCTMG
--- /dev/null
+IUTWMVVHVRORNXZZAGPPJSLVPFDLVZMEVGJIVYDZPNAPKQXCIZLGRZWNNCSVKPQTMLKPQNWPGOAYVAPQIPQWRMIXPBTCCEGWHLOZQRFYZGHEJCFETFRULYBLUDNHNYGBEKBKSNXYMRCTHNLXHKHKDFCBBWGVJQBLIESQAJWVLZQTLLASRESDVJMRTBJDOVOAJPQQIVYZHFAFQBHGMVOSDEXBYHKSPYSQLDFRZFYJHEDWPZMVBDCRIYALMMVQWJHVIPDUCKFVZBXMDQVBMXOKODOGYEBWLACFMUVQNSQRKMMNWZBEOOUEXIYDJWUJICKRFQLCESIKHCJQRUFPRRGYHSTZNWOSPAZULTCZPRSOYVETXLLAQMJMVUSOPLCEWYLJUADSJGTLOHOXRHSPZGLADFFAHORATZOBMRVVFDWANXPGUNQGLIHGTRMWBJMFTALCCPZGVLCSINPBUWOVPYXUQUZKTGLTVRZLIRRFNWUULJIVFMEVFIPUNCMDUZAGRHDACDPFKTHTKDKOGUSPYENXTPQUROUZTWCMIGPCKLYWZGUWJRLVKNKQKGPDQNABIRLPVKHOMWXWCQTLGWHXBJFYGIVIWWJXWAVUCOFKJUMZKXKKOEGOETLRAKQVMGKOABGIXQLQMBJYBJUOIZRWHKZCDSMNHMWTRSDBSRULECERARYGFDPERECYGULSJCJWVMLNRZXKQRTTSZWJUVSUXLGKMQPHJWAUBUEJXKYAXCEBLGJHTNKTNZSGYLOFUZUTLNYBHBKGKSCDWIYUXXFYJPNTAFKBGCNLJVGTKDCNBHUSAZRBWSXKICXDISPIRYEOIVXZAWAZIFPGAUYDQSWYCSIQDENTCTAONTOBCIVFYPVPDEMUHDPNTSUOSWVMLXECSHCMHESCGWSUSTAYHKUMOXUFEANTUHNDZHZFLRSHCZBASXPPMCNWMSJTANASBRPHDWJUCTTGMHNTTPIGTVJWWNFUWEOZMCIQMDZDJGLKSSYOXIBGIHPZOMNYBORFNCBTNUHQDOPUOFCCLDFUHIPMKCZKCZVZNMFLWOKIZFKINWQNROAZYLCTMUZYUGOUIMEQSQQAAIQVYQRSPZPUXUNBAORFDDASVMADOGRNPBPKNXGXQOKSEHEAJNZNMQIUMPLHWUFWLEOBKPIASZALJPZQUIKJSGKPGEGMPFBUNHOFKXTSCJMTYBUJEBYNNEVQHKNTIUJBJEEUSQOINRDAZUQMEWEELBLBSGUGXDXLWTUEODCKZYXJUODPPGBSPLAKHPKUZYVWGXMVXEAENQYBPKSDJMTZIBEYMTOFWCVOYZLJSKXGBKAHDTZAMZSFPGPYFFWRBHLNXOAXOITZVFBEXAKVYPAYTIRZMRKIYZRKIQNSDOINPTWMACVOJCXWCOXCEAJBQULUYWQLRERSUIIQTBASGUMAORADTIWOIDHEWLYZBADGFMHHWXNQCZKFTBVJRSYMKGTMLRGNHPUZYOVAOGTVHKHHEQBKTHJYBCUONPEUPDPJMLEOZILYNABGMPEEVJHKADCUEHMNEFWJURTJKTBKZSMTKYPCRVGFPHEIDVFSVNFUMSYAXJAVGMDSZRMHMQVSUEKUWFZFRYOROKWORNQUNJXBHNZAYXWWBEISHIQBOJAAYEKWMGJLGHFDRKBEJTQUQKVRHNJGFHARSOXBRZHKTJFJFNRXQZQRMFKNXRWLVCZBZSFQAOCLPZSGIOTMXTQHBHVYVRYIUSKFXFPKNSQITSRMYGRYXWRFQMBBMJTYOCDTTW
--- /dev/null
+#!/usr/local/bin/python\r
+\r
+# Solitaire.py\r
+# Copyright (C) 1999 Mordy Ovits <movits@lockstar.com>\r
+# This program is free software; you can redistribute it and/or \r
+# modify it under the terms of the GNU General Public License as \r
+# published by the Free Software Foundation; either version 2 of the \r
+# License, or (at your option) any later version.\r
+# This program is distributed in the hope that it will be useful,\r
+# but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+# GNU General Public License for more details.\r
+# You should have received a copy of the GNU General Public License\r
+# along with this program (see License.txt); if not, write to the Free \r
+# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA \r
+# 02111-1307 USA\r
+# NOTE: the Solitaire encryption algorithm is strong cryptography.\r
+# That means the security it affords is based on the secrecy of the \r
+# key rather than secrecy of the algorithm itself. That also means \r
+# that this program and programs derived from it may be treated as \r
+# a munition for the purpose of export regulation in the United States \r
+# and other countries. You are encouraged to seek competent legal \r
+# counsel before distributing copies of this program.\r
+#\r
+# Solitaire.py is an implementation of the Solitaire encryption \r
+# algorithm, as designed by Bruce Schneier and described at: \r
+# http://www.counterpane.com/solitaire.html\r
+# as well as in Neil Stephenson's novel Cryptonomicon.\r
+# Solitaire is a cryptographically strong stream cipher that can\r
+# be implemented using a deck of playing cards. This implementation is\r
+# designed to be extremely clean, and a good aid to someone looking to see\r
+# how Solitaire works \r
+\r
+\r
+class Deck:\r
+ # Sets up the Deck in Bridge format\r
+ def __init__(self):\r
+ self.Cards = []\r
+ for i in range(1,55):\r
+ self.Cards.append(i)\r
+\r
+ def MoveDownOne(self, index):\r
+ if (index == 53):\r
+ temp = self.Cards[index]\r
+ self.Cards[index] = self.Cards[0]\r
+ self.Cards[0] = temp\r
+ else:\r
+ temp = self.Cards[index]\r
+ self.Cards[index] = self.Cards[index + 1]\r
+ self.Cards[index + 1] = temp\r
+\r
+ def MoveDownTwo(self, index):\r
+ if (index == 53):\r
+ self.Cards.insert(2, self.Cards[index])\r
+ del self.Cards[index + 1]\r
+ elif (index == 52):\r
+ self.Cards.insert(1, self.Cards[index])\r
+ del self.Cards[index + 1]\r
+ else:\r
+ self.Cards.insert((index + 3), self.Cards[index])\r
+ del self.Cards[index]\r
+\r
+ def GetValueByIndex(self, index):\r
+ return self.Cards[index]\r
+\r
+ def GetIndexByValue(self, value):\r
+ for i in range(0,54):\r
+ if (self.Cards[i] == value):\r
+ return i\r
+ return -1\r
+\r
+ def TripleCut(self, low, high):\r
+ self.Cards = self.Cards[high + 1:] + self.Cards[low:high + 1] + self.Cards[:low]\r
+\r
+ def CountCutByIndex(self, index):\r
+ CutBy = index\r
+ self.Cards = self.Cards[CutBy:-1] + self.Cards[:CutBy] + [self.Cards[53]]\r
+\r
+\r
+\r
+class SolitaireCipher:\r
+ def __init__(self):\r
+ self.Deck = Deck()\r
+ \r
+ def GetNextOutput(self):\r
+ self.Deck.MoveDownOne(self.Deck.GetIndexByValue(53))\r
+\r
+ self.Deck.MoveDownTwo(self.Deck.GetIndexByValue(54))\r
+\r
+ if (self.Deck.GetIndexByValue(53) > self.Deck.GetIndexByValue(54)):\r
+ self.Deck.TripleCut(self.Deck.GetIndexByValue(54), self.Deck.GetIndexByValue(53))\r
+ else:\r
+ self.Deck.TripleCut(self.Deck.GetIndexByValue(53), self.Deck.GetIndexByValue(54))\r
+\r
+ CutBy = self.Deck.Cards[53]\r
+ if (CutBy == 54):\r
+ CutBy = 53\r
+ self.Deck.CountCutByIndex(CutBy)\r
+ \r
+ TopCard = self.Deck.Cards[0]\r
+ if (TopCard == 54):\r
+ TopCard = 53\r
+ return (self.Deck.Cards[TopCard])\r
+\r
+ def KeyDeck(self, s):\r
+ from string import upper, letters\r
+ k = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ k = k + s[i]\r
+ k = upper(k)\r
+ for i in range(0, len(s)):\r
+ self.GetNextOutput()\r
+ # New Step Five\r
+ self.Deck.CountCutByIndex(ord(k[i]) - 64)\r
+ \r
+ def Encrypt(self, s):\r
+ from string import upper, letters\r
+ c = ""\r
+ p = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ p = p + s[i]\r
+ p = upper(p)\r
+ if not ((len(p) % 5) == 0):\r
+ p = p + ('X' * (5 - (len(p) % 5)))\r
+ for j in range(0, len(p)):\r
+ while(1):\r
+ output = self.GetNextOutput()\r
+ if ((output == 53) or (output == 54)):\r
+ continue\r
+ else:\r
+ break\r
+ if (output > 26):\r
+ output = output - 26\r
+ k = (ord(p[j]) - 65) + output\r
+ if (k > 25):\r
+ k = k - 26\r
+ k = k + 65\r
+ c = c + chr(k) \r
+ return c\r
+ \r
+ def Decrypt(self, s):\r
+ from string import upper, letters\r
+ c = ""\r
+ p = ""\r
+ for i in range(0, len(s)):\r
+ if s[i] in letters:\r
+ c = c + s[i]\r
+ c = upper(c)\r
+ \r
+ for j in range(0, len(c)):\r
+ while(1):\r
+ output = self.GetNextOutput()\r
+ if ((output == 53) or (output == 54)):\r
+ continue\r
+ else:\r
+ break\r
+ \r
+ if (output > 26):\r
+ output = output - 26\r
+ k = ord(c[j]) - output\r
+ if (k < 65):\r
+ k = k + 26\r
+ p = p + chr(k) \r
+ \r
+ return p\r
+\r
+def PrintInFives(s):\r
+ ns = ""\r
+ for i in range(0,len(s)):\r
+ ns = ns + s[i]\r
+ if (len(ns) == 5):\r
+ print ns,\r
+ ns = ""\r
+ print ns\r
+\r
+def test():\r
+ print "Running Test Vectors"\r
+ print "--------------------"\r
+ # (plaintext, key, ciphertext)\r
+ vectors = [\r
+ ("AAAAAAAAAAAAAAA", "", "EXKYIZSGEHUNTIQ"),\r
+ ("AAAAAAAAAAAAAAA", "f", "XYIUQBMHKKJBEGY"),\r
+ ("AAAAAAAAAAAAAAA", "foo", "ITHZUJIWGRFARMW"),\r
+ ("AAAAAAAAAAAAAAA", "a", "XODALGSCULIQNSC"),\r
+ ("AAAAAAAAAAAAAAA", "aa", "OHGWMXXCAIMCIQP"),\r
+ ("AAAAAAAAAAAAAAA", "aaa", "DCSQYHBQZNGDRUT"),\r
+ ("AAAAAAAAAAAAAAA", "b", "XQEEMOITLZVDSQS"),\r
+ ("AAAAAAAAAAAAAAA", "bc", "QNGRKQIHCLGWSCE"),\r
+ ("AAAAAAAAAAAAAAA", "bcd", "FMUBYBMAXHNQXCJ"),\r
+ ("AAAAAAAAAAAAAAAAAAAAAAAAA", "cryptonomicon", "SUGSRSXSWQRMXOHIPBFPXARYQ"),\r
+ ("SOLITAIRE","cryptonomicon","KIRAKSFJAN")\r
+ ]\r
+ for i in vectors:\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(i[1])\r
+ ciphertext = s.Encrypt(i[0])\r
+ if (ciphertext == i[2]):\r
+ print "passed!"\r
+ else:\r
+ print "FAILED!"\r
+ print "plaintext = " + i[0]\r
+ print "key = " + i[1]\r
+ print "expected ciphertext =",\r
+ PrintInFives(i[2])\r
+ print "got ciphertext =",\r
+ PrintInFives(ciphertext)\r
+ print\r
+ print "Test bijectivity (i.e. make sure that D(E(m)) == m"\r
+ print "--------------------------------------------------"\r
+ from whrandom import choice, randint\r
+ from string import uppercase\r
+ for i in range(0,5):\r
+ p = ""\r
+ for i in range(0,randint(10,25)):\r
+ p = p + choice(uppercase)\r
+ s = SolitaireCipher()\r
+ s.KeyDeck("SECRETKEY")\r
+ c = s.Encrypt(p)\r
+ s = SolitaireCipher()\r
+ s.KeyDeck("SECRETKEY")\r
+ r = s.Decrypt(c)\r
+ if (r[:len(p)] == p):\r
+ print "passed!"\r
+ else:\r
+ print "FAILED!"\r
+ print "Random plaintext =",\r
+ PrintInFives(p)\r
+ print "ciphertext =",\r
+ PrintInFives(c)\r
+ print "decrypt =",\r
+ PrintInFives(r[:len(p)])\r
+ \r
+ print\r
+ \r
+ \r
+\r
+\r
+\r
+if (__name__ == "__main__"):\r
+ from sys import argv\r
+ from string import upper\r
+ usage = "Usage:\nsolitaire.py [-test] | [-e,-d] key filename\n"\r
+ \r
+ \r
+ if (len(argv) < 2):\r
+ print usage\r
+ elif ("-TEST" in map(upper,argv)):\r
+ test()\r
+ elif (upper(argv[1]) == "-E"):\r
+ FileToEncrypt = open(argv[3])\r
+ Plaintext = FileToEncrypt.read()\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(argv[2])\r
+ Ciphertext = s.Encrypt(Plaintext)\r
+ PrintInFives(Ciphertext)\r
+ elif (upper(argv[1]) == "-D"):\r
+ FileToDecrypt = open(argv[3])\r
+ Ciphertext = FileToDecrypt.read()\r
+ s = SolitaireCipher()\r
+ s.KeyDeck(argv[2])\r
+ Plaintext = s.Decrypt(Ciphertext)\r
+ PrintInFives(Plaintext)\r
+ else:\r
+ print usage\r
--- /dev/null
+#!/bin/env python\r
+\r
+"""\r
+Python implementation of Bruce Schneier's Solitaire Encryption\r
+Algorithm (http://www.counterpane.com/solitaire.html).\r
+\r
+John Dell'Aquila <jbd@alum.mit.edu>\r
+"""\r
+\r
+import string, sys\r
+\r
+def toNumber(c):\r
+ """\r
+ Convert letter to number: Aa->1, Bb->2, ..., Zz->26.\r
+ Non-letters are treated as X's.\r
+ """\r
+ if c in string.letters:\r
+ return ord(string.upper(c)) - 64\r
+ return 24 # 'X'\r
+\r
+def toChar(n):\r
+ """\r
+ Convert number to letter: 1->A, 2->B, ..., 26->Z,\r
+ 27->A, 28->B, ... ad infitum\r
+ """\r
+ return chr((n-1)%26+65)\r
+\r
+\r
+class Solitaire:\r
+ """ Solitaire Encryption Algorithm\r
+ http://www.counterpane.com/solitaire.html\r
+ """\r
+\r
+ def _setKey(self, passphrase):\r
+ """\r
+ Order deck according to passphrase.\r
+ """\r
+ self.deck = range(1,55)\r
+ # card numbering:\r
+ # 1, 2,...,13 are A,2,...,K of clubs\r
+ # 14,15,...,26 are A,2,...,K of diamonds\r
+ # 27,28,...,39 are A,2,...,K of hearts\r
+ # 40,41,...,52 are A,2,...,K of spades\r
+ # 53 & 54 are the A & B jokers\r
+ for c in passphrase:\r
+ self._round()\r
+ self._countCut(toNumber(c))\r
+\r
+ def _down1(self, card):\r
+ """\r
+ Move designated card down 1 position, treating\r
+ deck as circular.\r
+ """\r
+ d = self.deck\r
+ n = d.index(card)\r
+ if n < 53: # not last card - swap with successor\r
+ d[n], d[n+1] = d[n+1], d[n]\r
+ else: # last card - move below first card\r
+ d[1:] = d[-1:] + d[1:-1]\r
+ \r
+ def _tripleCut(self):\r
+ """\r
+ Swap cards above first joker with cards below\r
+ second joker.\r
+ """\r
+ d = self.deck\r
+ a, b = d.index(53), d.index(54)\r
+ if a > b:\r
+ a, b = b, a\r
+ d[:] = d[b+1:] + d[a:b+1] + d[:a]\r
+ \r
+ def _countCut(self, n):\r
+ """\r
+ Cut after the n-th card, leaving the bottom\r
+ card in place.\r
+ """\r
+ d = self.deck\r
+ n = min(n, 53) # either joker is 53\r
+ d[:-1] = d[n:-1] + d[:n]\r
+\r
+ def _round(self):\r
+ """\r
+ Perform one round of keystream generation.\r
+ """\r
+ self._down1(53) # move A joker down 1\r
+ self._down1(54) # move B joker down 2\r
+ self._down1(54)\r
+ self._tripleCut()\r
+ self._countCut(self.deck[-1])\r
+\r
+ def _output(self):\r
+ """\r
+ Return next output card.\r
+ """\r
+ d = self.deck\r
+ while 1:\r
+ self._round()\r
+ topCard = min(d[0], 53) # either joker is 53\r
+ if d[topCard] < 53: # don't return a joker\r
+ return d[topCard]\r
+\r
+ def encrypt(self, txt, key):\r
+ """\r
+ Return 'txt' encrypted using 'key'.\r
+ """\r
+ self._setKey(key)\r
+ # pad with X's to multiple of 5\r
+ txt = txt + 'X' * ((5-len(txt))%5)\r
+ cipher = [None] * len(txt)\r
+ for n in range(len(txt)):\r
+ cipher[n] = toChar(toNumber(txt[n]) + self._output())\r
+ # add spaces to make 5 letter blocks\r
+ for n in range(len(cipher)-5, 4, -5):\r
+ cipher[n:n] = [' ']\r
+ return string.join(cipher, '')\r
+\r
+ def decrypt(self, cipher, key):\r
+ """\r
+ Return 'cipher' decrypted using 'key'.\r
+ """\r
+ self._setKey(key)\r
+ # remove white space between code blocks\r
+ cipher = string.join(string.split(cipher), '')\r
+ txt = [None] * len(cipher)\r
+ for n in range(len(cipher)):\r
+ txt[n] = toChar(toNumber(cipher[n]) - self._output())\r
+ return string.join(txt, '')\r
+ \r
+testCases = ( # test vectors from Schneier paper\r
+ ('AAAAAAAAAAAAAAA', '', 'EXKYI ZSGEH UNTIQ'),\r
+ ('AAAAAAAAAAAAAAA', 'f', 'XYIUQ BMHKK JBEGY'),\r
+ ('AAAAAAAAAAAAAAA', 'fo', 'TUJYM BERLG XNDIW'), \r
+ ('AAAAAAAAAAAAAAA', 'foo', 'ITHZU JIWGR FARMW'),\r
+ ('AAAAAAAAAAAAAAA', 'a', 'XODAL GSCUL IQNSC'), \r
+ ('AAAAAAAAAAAAAAA', 'aa', 'OHGWM XXCAI MCIQP'), \r
+ ('AAAAAAAAAAAAAAA', 'aaa', 'DCSQY HBQZN GDRUT'),\r
+ ('AAAAAAAAAAAAAAA', 'b', 'XQEEM OITLZ VDSQS'), \r
+ ('AAAAAAAAAAAAAAA', 'bc', 'QNGRK QIHCL GWSCE'), \r
+ ('AAAAAAAAAAAAAAA', 'bcd', 'FMUBY BMAXH NQXCJ'), \r
+ ('AAAAAAAAAAAAAAAAAAAAAAAAA', 'cryptonomicon', \r
+ 'SUGSR SXSWQ RMXOH IPBFP XARYQ'),\r
+ ('SOLITAIRE','cryptonomicon','KIRAK SFJAN')\r
+)\r
+\r
+def usage():\r
+ print """Usage:\r
+ sol.py {-e | -d} _key_ < _file_\r
+ sol.py -test\r
+ \r
+ N.B. WinNT requires "python sol.py ..."\r
+ for input redirection to work (NT bug).\r
+ """\r
+ sys.exit(2)\r
+\r
+if __name__ == '__main__':\r
+ args = sys.argv\r
+ if len(args) < 2:\r
+ usage()\r
+ elif args[1] == '-test':\r
+ s = Solitaire()\r
+ for txt, key, cipher in testCases:\r
+ coded = s.encrypt(txt, key)\r
+ assert cipher == coded\r
+ decoded = s.decrypt(coded, key)\r
+ assert decoded[:len(txt)] == string.upper(txt)\r
+ print 'All tests passed.'\r
+ elif len(args) < 3:\r
+ usage()\r
+ elif args[1] == '-e':\r
+ print Solitaire().encrypt(sys.stdin.read(), args[2])\r
+ elif args[1] == '-d':\r
+ print Solitaire().decrypt(sys.stdin.read(), args[2])\r
+ else:\r
+ usage()\r
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
-iQIcBAABCAAGBQJWjWDiAAoJEJPB2e07PgbqTQYP/1p/mIWKco0sY82X/5qVzeED
-BC90G90sl0hijCLVIWOJX1lQNAjShUDFb/jw9EXP1eZBInfSrkXVwTX6WkoXzkyz
-FOc4D1EN2IsSsvcI9HMMyxsu4Bnrj24/r0vbLoiNt2KCiEosN5h/ZQR43ZTcVwjB
-HLe8izsuBLjhkOi2bSOGGRKDRn52figwO9x2+fCGFpWskJR2BWL2tJM6wp4OihVF
-hqAhY7VdTOPM51dc4xVvP09kJf/jhaTILPi6WJhO09PwURPyfZ5dZr3te0IxLwmL
-IxBOx9aLDSbfoYeWhpNqIEpXyu626hYESppLnIcTItlLxf7EqXnRJquw8ipbeFae
-MAd26KcRl4/ox4isXBPPJo70MtsrDlg8Sdq5o4iWu2gAyVR9OMRtyPMpcFWYwFc3
-9LQY/BSxFUcFF7ROHQwvNy+4AlaaGvADgywY1TU3M/7wwPFfSxQIvJeSt/PCSqRU
-rIU8dQE4n3LyNPY3xnoWx/pSbz6EiZ4Lpq+E8N0GzORF7WgYdZaCLd2p3SAT/fCy
-u8Xw3R3+Wvn7L+tQFpMRrTnQBbtqVyec1wqmvevIeSRQBV5ukbmdRaeAhlROVsAp
-yrDLbwMIv2JqQMz+5EzpeE9Nj3pwwiTNLi1e+65werspyTIPSIEd2XqO3WXO816U
-hJywA30EPavQXyfxxRrx
-=6EPA
+iQIcBAABCAAGBQJWjX6wAAoJEJPB2e07PgbqhgsP/2YEJGETmbYlwtwWyKL2q/DN
+ICr7LSmgmPnelBgiOXF4JjhpEdmhuhT6XErTzyVcTmm6aXIdijncMD3qISvYePjk
+VfYd2RbRpA61J/5oZabxOxnaxOvLUSOp1BK0akmNOdi0aPJVm9fatYJMsuWdzwCL
+0hOklUUL2kvDFHRVYgupl57+dTDMWlTOH/es0izL+ZUDqMZcjyDiN3aVNjrHihDe
+RNeOgOod7FLb+GNMC9k5l1O6JovTgrcrw6a+6nyBbBKVcubeY+GS1CKfg0JaFiuM
+f8ewiGBixzV93A+c/nojHIR1jCdzBA91/mYUWmxSUP2kz9MS4HAh2ASbEYR0N6oM
+gJ5JGCHW5BuyTYwJ0xiHoia8GVw64Bw9J/Q6qomZBodw3DNjWXDnZcyrzYFzKuhI
+uP6NK+79Uc5PevDlOV60tLnY869ynXYY4uKIr6TT5NRFdvQi6OVHEBSryUolnnxQ
+YfVwFPNPPPAD+yLrgecD+f6VQjczy3um0pHthaOZwbgX4Sk0I2B/Nq40j/7K/iW6
+kVR2n1/R1wuVekMYpd58F/IUz+wk0JNvrr7O6usUAVEOPo33jCUsuS9wljYAwzqP
+vesbeWXRFGyN0rQDHgSfQivTqG5BL2XV4Nf+uWzeTOSwzAziO4MRHrzgGbo3aMAo
+4xz7E+Wi448R2zkNiUsS
+=vzqt
-----END PGP SIGNATURE-----
```
867 6b.ciphertext abc25188099a927323379ff6e3abad85312aca3cd01338848437c654f6ee5f72
1779 7a.ciphertext e519e7120bc68dc433723b1408a5b50780ce15413dae6af3e6874e2b44993dfc
725 7b.ciphertext 9376b3432de0b238d17704cee5ebc029142018233339161c64b3f6ade9b7df04
+1661 8a.ciphertext e748aef744bd869ff60aa65993c0e0738af05e95b64a82c1047ae07ea2e353f5
+1771 8b.ciphertext 669a7144433b0554e1de51583abb048ca396f2ffe6a79e53dad6e6ffbf06bc8c
+7607 Solitaire.py abb966d0aa4f23599204a1a75f3179903c175eac637c0847bcc772374f380126|9e3ee44bd5d7ec196b513afad0856ed4f2cd6bf3e0fe9b6bc0130bb7731d04c8
+63970 cards2.png f2959fc211d97c9b16bf049eb5c9e99167b033f887c43f9ae20adb13717c7899|b42371c35b39715277627114132f8b261283b2b1d59e47b7066e06f4df5a4ad6
+10000 pysol.zip 64e25c247e877f37285db635fcb5dea101e0c531fdbfe5000bcc088c22caad79|68d878fb019198e691d321be1dd6744fae0b48da9c4e705c34f96e351b00fbed
+5281 sol.py dbea0e836eb158a26407ca3b74289d24fa7eae16ed1093eb7c598767a150fccc|e50319aa995f934a4cbd52bc28d8f49f376e62470170690cdbed7352f67a7942
+2195 sol.py.zip 4c22e57eaacd8a91ef9b0c338429e400210a03dec9fbacec4a45fe1faed50720|1fd0a62af53125eacfa11016088451e2c58269d98aaef74c320730ebc7601b66
3620 2015-challenge1.ipynb 2ab544f48c22a3a2e665b03ae094ac2de04aee8bdb37366209276a937d248d6a
4092 2015-challenge2.ipynb 58c2aeca6c73a8fef71d0cc8321469358803f392c02fd44631dd0b25c2755f4a
3987 2015-challenge3.ipynb dfec26ba881c7ad0af54d072aeba4d08162661e863266936963130548ff86b65
5039 2015-challenge5.ipynb b0c28829c329d7a2752f4991006e6d78a10415d7609b893b80b3bdb788d6e533
14079 2015-challenge6.ipynb 7bd1dcff4b764d75e002622ce6a4cc3c00625aa311ef886e5a436822660c565c
76275 2015-challenge7.ipynb c48d71ef5c105dff987981c05ce4a9d2cce382e6d54358d7e2a3b6fe9fc74f10
+3741 2015-challenge8.ipynb d66fca286b89099f3ca296f1f8283f22c12ae9e5480192f2bdb316a46fd07f43
18025 LICENSE a01259a1b522cf0de95824f9860613b453153eebac468e96196d5d7dba84786c
7999 LJ!-Qt!-Fghxft-dferts%3B-hsjeukaxxn-sfedw.ipynb 429b6c6995096ff19c28a5ee342bef8ea4774200bdf9aaf6268de3cb8b28df28
61 README.md 277247b410300ee16477b12ca54ad878d81c8061f6134e2e1cadccaf299de3a3