Cracking the Agrippa Code
In 1992, cyberpunk author William Gibson published a 300-line poem on a 3.5″ floppy disk, titled Agrippa (A Book of the Dead). The poem was stored as a Macintosh program that, when run, would display the poem and then irreversibly encrypt itself, destroying the stored poem.
In 2012, I became interested in unlocking the encryption of Agrippa through the Agrippa Challenge, which enlisted the Internet to figure out the program. Here’s what I figured out.
The Agrippa program was developed in Macintosh Common Lisp, a dialect of Lisp augmented with routines to interface with the classic Macintosh OS of that era. Only the compiled binary was provided, and the source code has been lost to time.
On The Agrippa Files, a site dedicated to collecting information and resources related to Agrippa, there is an earlier draft version of the program’s source code:
un-make-des
function more closely resembles the RSA algorithm). This algorithm encodes data in 3-byte blocks. First, the 8 bits in each byte are permuted through a 8-position permutation (do-it
), then the bits are split into two 12-bit integers (by taking the low 4 bits of the second byte and the 8 bits of the first byte as the first 12-bit integer, and the 8 bits of the third byte and the 4 high bits of the second integer as the second 12-bit integer). Each is individually encrypted by taking them to the 3491th power, mod 4097; the bits are then reassembled into 3 bytes.Side note: 3491 appears nowhere in the code, but it is the exponent which reverses the action of taking the 11th power, mod 4097 (the implementation of un-make-des); that is, taking a number to the 3491th power mod 4097, then to the 11th power, again mod 4097, will result in the original number (mod 4097) for most integers. (This doesn’t work for numbers divisible by 17 or 241; evidently, the developer never ran into such values when using his encryption system as they would otherwise corrupt the text).
do-too-it
) and the three bytes are output as encoded bytes. The big block of data is the whole poem encoded with this algorithm and printed as MacRoman characters (with escapes, e.g. 0x5c, the slash character, is output as \\
). Sadly, a combination of font and printing limitations means that the printout is not enough to directly decode the poem: several characters are unprintable (and so are “invisible” in the printout), there is no way to reliably distinguish tabs (0x09) from spaces (0x20), and some characters have been printed incorrectly by the font (e.g. the e
in the repetitive " e\\"
sequences should actually be an e with a circumflex (ê
), but the printout doesn’t show this). Consequently, the poem can’t be completely recovered from the printout, but I am confident that the analysis is correct." ê\"
(quotes added for clarity); since repeated spaces appear very often (e.g. before each chapter marker), this sequence can be seen repeated often.The encryption of
tonight red lanterns are battered,
laughing,
in the mechanism.
(the last few lines of the poem) is ΣÑÀQ$£¿Ì[ıƒ^ü…ïÓ êÊyMIÇ…~/ôì≈^Èür˙Q¯(ÀA}|yÙ¥3W+
which again matches the last few characters in the data block of the Lisp program listing.
(_ShowCursor)
at the prompt (with the parens and the underscore). Editing becomes easier after that. Typing (lisp-implementation-type)
gives “Macintosh Allegro Common Lisp”, as expected. To get a list of *every* variable defined, type (apropos-list "")
. This takes a long time to run – here’s what that output looks like:shoot-gun
, *mingzi*
, *about-agri*
, etc. (note that Lisp is not case-sensitive with its symbol or variable names). In fact, all variables after *TRACE-OUTPUT*
are variables from the Agrippa program. Examining the Lisp code from the Agrippa Files, we see that many of the variable names are the same, but not all are. This immediately suggests that some variables were renamed, added or removed during the process of software development, implying that the Lisp listing was for an earlier pre-release version of the program.UN-ROLL-ZI
is suspicious, as it mirrors the name of un-roll-the-text
in the original Lisp listing (in fact, “zi” means “words” in Chinese; a few variable names are named in languages other than English). However, the zi
variable is not currently available. If you start the program again (from a fresh disk image, i.e. “for the first time”), and press keys when the poem window appears, typing in zi
at the REPL will yield the complete, original text of the poem.zi
in the Lisp program is the same as the one used in the older Lisp listing. In our original REPL, it turns out that the variable cl
has the value 3, the same as the cell-length
variable in the original listing. If we set cl
to some other value, we can skip the decryption routine, allowing us to see the original “encrypted” data. I set cl
to 10000 (larger than the poem itself), then used (continue)
. Agrippa dutifully begins to scroll a mountain of encrypted gibberish:" ê\"
sequence in this screenshot. I can actually confirm that the decryption algorithm used in the Agrippa binary is functionally identical to the one implemented in the Lisp listing, i.e. that part of the program did not change between that listing and the final shipped version of the program. Therefore, the major mystery of “what encryption algorithm was used” has been solved.- Scanning the binary for these encrypted strings turns up absolutely nothing. The reason is that the Macintosh Common Lisp compiler compresses the main program code into the executable, then fishes it out and decompresses it during startup. So, string searching will not turn up anything. The best bet if you want to get the encrypted mass of text is to use Linux ckpt or a similar tool to get a memory dump of Mini vMac after the Agrippa program has been loaded. The compression is not in any way a form of encryption, so we won’t discuss it in depth.
- What’s that mass of “encrypted” text that gets printed out at the end? From my observations I can tell it contains around 40 unique (printable) symbols, far short of the 96 one would expect to see for a properly encrypted piece of text (as an example, the original encrypted text has a lot more unique symbols compared to the garbage shown at the end). From experimentation, I suspect that the text is generated by applying the decryption algorithm again to the plain text to reduce it to gibberish. Here’s an example of that output:
Later, I worked out that the plaintext poem is simply fed through the
un-waymute-it
(upp-it
) function line-by-line, character-by-character, which effectively implements a simple substitution cipher over the characters. The characters are thus modified to become unrecognizable, but the encryption is trivially reversed by putting the text throughwaymute-it
. Unfortunately, due to the limitations of the text display in the program, it is impossible to precisely determine the ciphertext (since some characters are replaced with boxes, and others are missing entirely); thus, “decrypting” the ciphertext that scrolls at the end is impossible to do precisely. However, since it’s a simple substitution cipher, the characters that are readable can be used to reconstruct at least part of the text. - Why doesn’t the program run again the second time? This one’s pretty easy: the program corrupts itself at startup. Originally (as indicated in the Lisp program listing), the plan was to just write 40000 0xff bytes to the binary (“trash”), which would inevitably render the program “corrupt”. At some point, someone clearly thought it would be more artistic to use a “genetic code”, so the final version writes 6000 randomly-chosen (but fixed) As, Cs, Gs and Ts to a particular spot in the application file. This corrupts several routines in the program, preventing it from starting or running normally (or, depending on the computer used, freezing or crashing the whole system). Note that the genetic code has a codon entropy of 5.97 bits/codon, much higher than any natural DNA sequence known (they top out at around 4-5 bits/codon), and a BLAST nucleotide search turned up no results. It is therefore likely that it is randomly selected (or matched against an artificial DNA sequence, e.g. the sequence shown in the book).
Finally, I wrote some code to assist me in my reverse engineering efforts. It implements all the encryption and decryption routines, and further provides a demonstration decryption of the readable parts of the final encrypted text scroll (point #2 above).
# -*- coding: MacRoman -*- pmq = 4097 # p-q em2 = 11 # e-2 little = 16 lot = 256 cell_length = 3 def make_des(x): # reverse-engineered from un_make_des return pow(x, 3491, 4097) # 3491*11 = 1 mod 3840 (4097 = 17*241, 3840 = 16*240) def un_make_des(x): return pow(x, em2, pmq) def do_it(x): # reverse-engineered from un_do_it return (7-(x-5)*3) % 8 def un_do_it(x): return (3*(7-x)+5) % 8 def do_too_it(x): # reverse-engineered from un_do_too_it return (13-5*x) % 8 def un_do_too_it(x): return (5*(13-x)) % 8 def gen_permute(x, f): new = 0 for temp in xrange(8): if x%2 == 1: new += 2 ** f(temp) x //= 2 return new def permute_it(x): return gen_permute(x, do_it) def waymute_it(x): return gen_permute(x, do_too_it) def un_permute_it(x): return gen_permute(x, un_do_it) ''' new = 0 for temp in xrange(8): if x%2 == 1: new += 2 ** un_do_it(temp) x //= 2 return new ''' def un_waymute_it(x): return gen_permute(x, un_do_too_it) ''' new = 0 for temp in xrange(8): if x%2 == 1: new += 2 ** un_do_too_it(temp) x //= 2 return new ''' def un_happenin(x1, y1, z1): this_num = un_make_des(x1 + (y1 % little) * lot) that_num = un_make_des((z1 * little) + (y1 // little)) return ( this_num % lot, (this_num // lot) + (that_num % little) * little, that_num // little ) def happenin(x1, y1, z1): # reverse-engineered from un_happenin this_num = make_des(x1 + (y1 % little) * lot) that_num = make_des((z1 * little) + (y1 // little)) return ( this_num % lot, (this_num // lot) + (that_num % little) * little, that_num // little ) def roll_the_text(the_text, wend=None): # reverse-engineered from roll_the_text ''' Encrypt a piece of text with the "Agrippa cipher". Its length should be a multiple of 3. ''' # default argument if wend is None: wend = len(the_text) num_cells = wend / cell_length new_text = [] for rip_off in range(num_cells): c1 = chr(permute_it(ord(the_text[3*rip_off]))) c2 = chr(permute_it(ord(the_text[3*rip_off+1]))) c3 = chr(permute_it(ord(the_text[3*rip_off+2]))) temp1, temp2, temp3 = happenin(ord(c1), ord(c2), ord(c3)) new_text.append(chr(temp1)) new_text.append(chr(waymute_it(temp2))) new_text.append(chr(temp3)) return ''.join(new_text) def un_roll_the_text(the_text, wend=None): ''' Decrypt a piece of text using the "Agrippa cipher". Its length should be a multiple of 3. ''' # default argument if wend is None: wend = len(the_text) num_cells = wend / cell_length new_text = [] for rip_off in range(num_cells): c1 = the_text[3*rip_off] c2 = chr(un_waymute_it(ord(the_text[3*rip_off+1]))) c3 = the_text[3*rip_off+2] temp1, temp2, temp3 = un_happenin(ord(c1), ord(c2), ord(c3)) new_text.append(chr(un_permute_it(temp1))) new_text.append(chr(un_permute_it(temp2))) new_text.append(chr(un_permute_it(temp3))) return ''.join(new_text) # "zi" variable dumped from vMac memory image zi = ''' wWe7fG817qV9cXr1AlTJGo81NRq/n4ik+lH4mzpfNQh5BfvjAaVeASF5P4qmlwB9KyWzveuEmUTZ jTAcu1tfggngk9U8vftUKjMbveuEmcbV6JBcIBYHMd2EqM11q2ZjIAQ6LMgZBax1IJBcG9+GgovA uwaIIXH0hLV2zS416JBcIJBcve8CF1CjqDXJIed3uIPAj66pxiQDQmoPdsx9zM933MX6eUQ0z0l9 m8JMrcT6lf15fikOyDtXzE+NgDbufsz6dqITgKY4oAipgg9G66USNRz6fsoTuhj0fHn0AaNMfq3J DTH0BPyUy1O/mO19zb4lPXL1vemk8INMDaNu6IKmlwB9fHn0nsCkEZ2UTRtfgovAgKLAfsz67kyz k0ehBDD0I1qslOGkd3n0i+i/ghtHIeOeIAlClW+GmzpfNYjQmMJuAbEXHZJUl5AXDSdGJKtuF8JM AbclblXJoIX6PWY1F9R2ceqmvemkblfiydyUuANKn4oTyCl9fHkh8HXJJc+pBPx5vev/IJnmu09J xjAWi3qd6JIai3htNQh9fHn0CqO7govAfHn0+lfux8ITBHr18GfetDNHK6OeIAnRy1X6gCZJbtHm 7TNf7jXJB4uxdDOsHRJH66USk+44KyWzbsVetLPjcxx5xwyUoKtugKY40Gjg6IKmlxT6dspMAaEo uhgvvW/etKehHB6uMdHvk8UmfOumIJR5btclgjP1IIAoi2oPdsx9zNl5IeOeflj0RRSauJU8uIPA jYuEcXj06YuWgovAAbV5tDEoy0eNK7clBKaBoKtMEZ9y7jXJ8BXJ7sr/8yeZ7uzAoA7ni3z6OFj0 eUD9PXTV1q+Exx4Mov7VtlDb6BD9DI1ecXi4gDRnxqLAi+xeRlA8Mf12+lfuAad3F0In6Q+II8qe 8OMTRZSU6JIamzpfNQhCHRTJ9OqE6YuWIJDmNQh9giNGPXL1veuEDbeGi+53uIPAgDRhdjJHJc3J NZqJ5TpHIIRezTj0JctMPfR5lwB9fHn0oI5rCiD1IIKeIJATjW41cewmfHn0mfWUu8vAUe5r8HXJ yTk8h4vAAbV5F9YlyTtHIXO08PV2daNs7qMTuhj0+keI8YViyb12fi81k0UDhYktASF9fHn0GKV9 y8NMyK04mUInAsQ4mUIn8JfV4QRFJ/HPJU81bsOe8OeplHd1vX3J7t5ybsc1m6x9BPx5AkK4Go81 NY53ve19RkTnDbeGmyo5xrLjTW2zvW/eTYvAmFD0jw41fsqE5Tj066USNR5EIWc1i25hxjL1i26N 7UZJ+lH4AkK4ycqelwB9fHn0flzJxiaZCu84xrDmm1D0MnltKiXZsO1eIedrQC+ZGHzJmzpfNRz6 gieGgovA1htXMxtHI841i2w0z9kXfP2UyKueJQ6ZIJBcIJBcIJAXghtHEQkBlev/y8ehyqghtCfe uAfni3qsk25JIBD0liInfqum8HNHpJpUlPeGgovAPxpHk1H11qumAaHQIeOmIIKmlxAamcJudrDm xiStZLU8jxpfP46EK7VKNQjRZpHtcXzVAaEoi35Ek26I6BCjIIRei+53vW2UwWF5xiaIrcT6RkD9 xrQ8F8Re5/clhz3JNlO0NQw0z804DSfey1O0bsc1+sP/uJU88IV9ceqmAbF5lW39xjZ1+lH46FTJ 7sr/PfQ8bsOE6Q+II8qe6FTJuJU8uIPAAbV5btNUBf2UoBH0AbV5DJB5HZSUvX3JK6OelxT68RX6 dsgofr+R6JK6NQiTgKZrxqLA+sc4pMc4RAo5+tdyATOsk3j4Aac4gKLAdKEocX7TDbdyxiZGIJBc IJBcIJBcCqOex1TJcXj0JV/TgpkAoAz9k26Zk8XAfqmkIIRel4Z3IIC/oJWUbsVetDP1F0AB7+pM AsDQHZaGbsOeF0AB7+pM8JfVaaOk6BD9i3r1fr15rdJUpMEUu8vAedDmBDL1EYv/Aac4I1z6+lH4 PXD0Gax97c2sHZaG5aqEDJYsdkqk5apu6BD9dSc1cfjmy1X6lPeGPfLjMeuWIBD0sOm/Aac4CiCp J/d9Pxr1IIKml5RK7SOkQL08i+53xW3ZIAznvW/egqOWvX3Jk1H1GG5JJLHmIIKml4DQHZaGbsMT i+gouhj04QA+Fhjbh4i/ASF5DIZ3y8d3uNclcXrQyo5ruAc1i2h9fHn0GTzJlOGkIIReuAdGAaVe ASeN7SdJ5TzJi2h9fHn0dCH9RZJUBf2UoJPjAbV58PdylOOEjRn0gKY4AbV5mMLAtsOElOPAGSo5 OFj0KzNHJd/Ak/pUyTtH+sOei2w0z8938GGpyK9rQ4R9m8Y4DIumsGuMyjpHpMOW+lH4bkX9AmWz 7jXJmzpfxralB4uemzpfNQhCDaPAi3htNQw0z0l9fHn0AuOefi+ZlOXDlwB9fHn0ZoVeBDKeRRK0 jaY1i+53uIPAAbV5F1Jdfi+ZMnltKiXZ+sc4K6Omfi+Zb8Y1l5JKle8Ik9U8uIPAZDUGeVD0b74l QD3V4QA+FhjbGSo5dl51i2h5gov/fjn0AbV58IOEuBXE6IKeEZ9y7jXJvesTIA+N7qGk8Pdydk41 HApd5apuNQh9ljMbxqLABP5y+sOWIJR57U+ZyogoPXD0NY5rfPnmbtHP7cvAHYLAGG5JJLHmKVz6 ASH9yD36fsx9daXFTQ9JxjTE6ACp/aqEKrdyghn0I06NpNFYDTOejST953P1m6qE5aoTuhj0CrDm Nk0DZDUGeVD0pMOWF0QuZhXVSsV9K6epIBJHbsMTi+44KzNHCrR53SXZ4QA+FhjbIIQ4ds6hIIKm l4Q4oIEObkdj8OP/Aad3jST9IBD09Ox9sP3ngqc4CqOex1TJcfrjIJBcIJBcIIZ3Qr9yNk0DwfO6 ATWKIWU0y1deAeEoyT/XF8AoBDbTmNSU8JfV4RIq6JBcIJBcIJBcIXNHfPnmIAD9yK93ASM7DbWU Ns2Yy0F9fHn0lWsnz0l5NYgouIPAAbV5ve19lWn1vX3JK6Oel5RKm9alAtR2y0F5NZqJlHXJblNd ASF9KyWzvfnm0kZJoDn0edDmMw+ZT7bBtlAXNdazFlIrAtRu7aV9d3n00QD9yD8Mov7VZDXVhC3Z 9Ox9uBVhTRtfgovA8i2zASGTk/x2yK93eVBtIBD0jY19RkTZgCZGdjJHF1SK8xTJ7U+ZeVAcJKPA DTH0NQ41m9aRvX3JmzpfNQiTyS/e7U9GgKLAZgeGNRqsTR3JIeVixiInyK9r+sOeKc53uAdGTW2z vW/eqKOEy1X6PfSUzS41IJDmNQiTdl51xiZGjSTZSHJHbsG/AbF5DTNHmzpfNQo73MOEdSeZGSo5 AXXJfj3VdKdrK6d3EZ9y7jXJbtV5RkJGpMOWB4uEIWF9fHn0TQ9JKrdytKOmASU0z893vW/ePeRe lW+Z675y+sOWIIAoeVT6AaOmnlD0I06NgLR5blVn8PV2daX6GarAZgeGNQh9K6c1UwXZDJ2UASHR y9VKNRgcJCHg6JA1BXn0AaVeASFCxrYli2h98QF9giNGPXD0RRSauAeZHYLAJctMPXbuEZ9yk+44 7qPAmFD0dTNHmE0D6JBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIAR2Cm+Z66USNZqJyb9ypMEoJc/V 8HNHlGX9xqLABOxeRkTZAaOeHBjdNRpHfPnmu09JNQw0zbjmvf127d8S8ZHmfj3V1o9ri2w0z0l5 U+c7gDBtHJw8F9R5mPvjOp158Pdy7UZJgovAz98l87Pjb8Y19lXJ7V/cbqITl4KeIAl9KzNHbkM5 dkzZAaEoZgeZB4uxdCHRy9VK+tdyrdSUm8Aouhj0BHr1lWsnsjAv66OmmVJXRIoTxjTEm6qmNkWz 7jXJfi+Z+sc4AbV5veuEqKOEecRiIXfTgotublVnER3JuJWU7Vv1ve367d125brj+sc4CiMnyC+N fO+poL12yK1iDaFauAc1i26N5bpUvW/etKNM8q81TYsTzbrj8queAaeEghn0gKLAAbV5vW/e6Q+I 3bfJi/gXHZJUl5AXDaPAfr12fi+ZAlSKGarADTH0C7OJMbWUB+LAlwSzCqO7gh3Jfq3NuBH0Pxpf govAGa6ptLHmIIKeIIKmIBJHATO0myo56YkUHIi3uIPABOqmNRpHKyF9KzNHF9AZgomkveuEyVrL vX3JGarAflzJDbes7dvjgKLAAbHmy0WzfHv1IBJXqLV2yK93tDNdy8dr+sOeyjzJlOc4yK9rcfx5 vesTflj0gKLAdLHmAsT6Aad38yfe6zNfNZqJyb9ypMEoJVk87qNuxqTkNk0DOp15vftUgjP1bsMT y0WzKyU0z0lCmFTJDbNyJVkoi+538AM5mdSUDlAvvW/enwo5dl51xjTEF1TJIeO7xqLAtKKEeUaN dk6GgDKdIJBcIJBcIJBcZA41cXzVSHJHmzpfgh3J66Omfrnm8Gfe8OV97Vn0pMOWm6qmCieG8que IBD0oBpH+sVe6zNfTYvAecKmDJBTNZjmi3q/n4qElrdyxiZG6Q+Im8Y4CyF9gCSsIIZrmyip6IKm lwB95ahl+lH4KUo7nwo5pEc9xjTEF8KElqY4AbV5m6qmoIr/nxzJfruqIJBcIJBcIJBcGax9mFTE 66OEmzpfNZxKlHd1dDEhEZ9ygh3JIAa7mfWUDSdGCydh9lXJIfVKNZjmlwB9fHn0z881GarAi+i/ jSaZ66USNQ6NU2OkgpkAvTpXBDJHxqLAJV2KIAmTyS/ezbjmxiZGtKKEeUB9fHn01htHh4OEdt7J i26NDbeGII6h7cvAYCj9+sOWgovAgCZGIJBcIJBcIJBcNcY1jaKed3n0h54lcXxCDTH06YuWk+qE 7ctMfq/WOFzJbsOEIWF9fHn0z8v/n4rAtsMTIA+IF8ahQi2zblVnF8ReuINu6JBcIJBcIJBcpMOW btHmgKK7gpvjZgXZUe5rKVz6ASH9PXL1F8Kmgh19QC+Z7qPABOgoGTzJ7Vv1F0TnIIReuAHgk8Um fHn0jeqmF8ReDAQ0y0F9fHn0uEc1UwXZCrDm53NHNknju836lekouhj0+sc47UZJgovAONyUIAa7 jSInzEnRATX68QeNDTXJVZ8lpmTZRA5GOo1eASF9fHn0JVtXgKKW8PdyPxzEF1BAxp4lpmTZ6Yko nxzJuBXJfi+ZIJBcIJBcIAZhPwpgfHv1mzpfNZxKDbeGliInlwB5pEGmmFJHgovApMOWvWs5P46E JCdGIJBcIJBcIIZ38HNHIeOmJRj0tsMTIA+I8OepxjZ1blVnIIKmCqJMn4rAi2ipm6qEF8AoDJvj IJBcIJBcIADRy8d3vX3JlGXZgKLAuEc1UwXZGaqmyDkcJCHg6JBcIJBcIJBc1oJudlyKcf4lDIZ3 B+KexrKqIJBcIJBcIIKeIBbTDTXJuAc1i2w0z0l9fHn0tDNXK6OmmMapNk0D4gysNQh9fHn0z881 GarAASeGNZhTPxj0snJH8AMn5arA8jtdATOdIJBcIJBcIAQ0y8d3EQ/eecKmNk0D6JBcIJBcIJBc IJBcIJBcIJBcIJBcIIC4Cm+ZAkR2sHn0mY41JRj0TYvAypx5xqAouprjIJBcIJBcIJBcIJBcIJBc IBYMovyAZCUDyeKEy0F5IXFtJRj0+sc4AbV5F8ITGax97tyUHIpspAonuIPA7qPAI06N7qGkNs3J PxzJ8zH0dLV57Vn0oA6G/Tj0daOmAad3IBpdASF9fOv/IWF55ar/uAc1Jc3Ny0F9fHn0i3ht9tV2 3EWzblVnIBD0B2SzASHKHZJURQaZdXpHmzpfNQhCHRTJ9OqE6YuWIASzIIKebsOEdiY1HBooKyWz vW/elf15IWU0z0+NDSOkk26Igokbi2yUPxpfoDkv6zNfgp2Uve19RkTnDbeGi2iT6Qmm+lH4F8KE lqY4jSTZAbV5bkM5xjKik26ZAtJKcfx284KETYvAgKY4d8Ao8ZfUbjTJQj/X6IZri3z68i2sNQhC P54lIIKml4RiDaFaHBgoflj0QL12IXO/7sqeBeumHAgOpAonuIPA7qPAAbV5bsV9IWF5pMEUB4u7 gh19lX2ay0HK7jNfbsV9dqKEIeNMZgeZAlSG5arA7c5rfPkZm1Z1xqTJPw41NZpKpA5J8JfVqaOk k8UmfHkhIIRezTj08qu7xqLARRSay0FClW+GEZ+6xrYl+lH4IIKml5KJDABCTRtfgovAdbV2oBH0 KyWzAsL/UwXZsOm/AbV5ZhXV0YL/y9NKflzJu8vAmFD06R+DOFzJl4Z3uIPApMc4xrR5diI5oDn0 GSo5xjJHyK+poAjRJc/Wj4qmIJ1K+tdyIIKemzpfNQhCIeepxjTVsOumcfjmxqLAKqOEzTj0AbV5 myo5KjMbxqLAuEc1ASU0y8d3yr4lKjMbEYv/7Vkv8PesL9WUGaoTuhj0AbV5EZ12DADRAbOJ+tdy AbF57iNgKyHRy8d38HNHleueIAnK+sOWHBoofHn0i+xeRRSai2h9KzNHF8TDIA+ImzpflX2aNk0D Op15IIC/5byUIASzIJR5OMikKVq/y1NHfsx9dqITzTgv8PdyNRpHfHv1IIKe8HNHljNXdlzVuo5r bsVeAaO7xjIo8QF9fHn07c+E+sOex4w4DBZEIeOmRYR9AbV57V3VdKG/8GfePeLAGSip5+Eoghn0 jSTZCqOefsx9dqKEoBEvMrV2i/h5leueNQh98ZOJdlzJle84eVJHgosTlHfuKUjKKzO0K7VKNRq/ JVv1EYv/ATOddKG/PWY1lPV5lXvLF8KmATWKi+6hJV0tIJIa8JfVqTe8fHn07UmpyDtXxqLAAbV5 8PWUdqITgKY4gKLAtsMTRlA8i3ht9tV23EXZ+sc4tKKEn4rAgKLAKyWzIIC/5byUEYv/Aac4AbV5 vWs58RP18OepgKZrKzX6yK8oblVn8IOE5TzJfr/WgCY97VtHxjTVi3gcy8OeIAl9KyWzIAaG/aqE za6pk26IEYv/Aac4I0hCB3JdIWWzfH3V2UZjb0aZ70pGZoOe8JfVqaOkx4j+NQh5xqAo9lXJlwDR y0eNgCY97VtHNY53uIPA5/eRliInfql2DSdJoL+syLnmpnQt8OUuzaqmUwXZQL12oBH0yDtHgovA KyWz8OP/L8VeqLHmNQiTxqah+lfuKUh9fHn0Jc1eBGgWB+ahAaOeIJl5gh1hyTtHpmTZPfAZxqY4 pMOWvf9yDbNyPWY1i2h5u02jxqKmF0InDlAh8HNH7VtHCrR5i2w0DI/WdbWUATO/uJEZk+44tsMT RkTZAaVeATV4uBfTmzpfNQjRleue8Pdydk41blVn8BXd7spMNZxKHYJugokOk1H10YapI9gepEV0 8JfVJNlZIIRei26NdspMblNdASF98RX6dbWUoAh9fHn0CqOex1TJcXj0dKdrK6d3F0AOlWsn8j3V ltjmveuEP4puIJDmNQhCDTNHk/x5lwDK7jNfve36qKOEmHn07VlAxo41+lH4IBD0GDXJCqahyCnR mHtHZI6pk26INk0DQ5WUbkfelf2UIIaEI8qeKUh9fHn0d8AodlzV4QA+Zl3VOp15F8JMCqRey0Wz eUaNB2SzgokOIIR1jaY1y0eNyC39oIqeIA00Gh3JoBH0I14MD/yAF0K4OdHId8DQyWRXMDAgpnXV VWatgKY18OUul4KmHBooHQTkgrXnyK9rNQiTlOMTdlzVfP12cfjmIIKml5B5Bf2UIXX6cfyUIeOe II93uIPAtsV9BCY1KU5h9tPjlqLuveAO8AM5xqTDgiXZAac1gokObtMaNQw0lrR5lX3VpUz6Lkpr Nk0D6JBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIe6Nk8XJNZqJlHXJIIZr m6qEmzpfNY53JRj0gKLAjaY1bP2UDIkocXj0IXP1+lE8SHJHbsG/AbOJlrdy8IV9GarAAaVecXzV pMOWbtV5zbjmIIKemzpfTYvAjTSaug6ZOo1eASHKDTH0ecKeNQh98RV4uhyaKcw4I8qe8ON+7d2U ATOs+tV5lwDK7jNfyrjPRsJMAaPX+tdyU4fDNRgcJKNuHBoofHn0lwaGghtHHRTJbtNUgKY1xqLA dKdrKyHKgDBtk+445+Ve7kyzJ+MTghn06YuWgovA5+VeGaoTCiDRy0F9fHn0OVFtI9pKUWz9HIi3 uIPAAbV5F0TnGTzJ3SXZ6Q+IdsqWKUh9fHn0PxpfgovAAaEouhgvyryUmP2UlwDRy0F9fHn0x1TJ KqXDuhj0gKLAI1z6+sOmIJTncfjmF8TDRYAoIA+IMrdyk+44ugpGdsikF9YlJVn0dCc1mzpfgh3J Nk0DCu84AbV5m6x93FOez8ueNYxi5TpHvX3JuJV2Aac4AbV58JNUTR1hHZJyblVnmzpfxrKqIJBc IJBcIJBcxIY1Ggn9zEsxEY0uGTzJlXudyDkhIIKel4ahuIXFNRr18BXJuogoghn06YuWmFJH5b4S xqLAi+qesPvjIJBcIJBcIJBcmzpfNYxiOMqmblVnF8ahug6Z6JIamzpfNQh5pMEUi+53uIPACjNd ZBpfPw41xjTVI0hCHQY1m8Y4ASeGxrKqIJBcIJBcIJBc/TzJNsP/PXL1KVqsm8JMGaqEuBH0sOum qLHmm0Y1Nk0Dypx5y0V2EZ2Un4rARIrAjSTZP+Y1gCaIdtqqIJBcIJBcIADRy0F9fHn0nsR9VR3E k0XnF8KxCrYlvfnPDbeGEZ12i2h9fPnmNQw0y8U4I8qe8ON+7d2UcW6ZKqOEzTj0I1z6jaY1NQh9 KzNHveuE6Z9yAad3uIPAZgH9l5B5mFJHdl7uNsnuv6rAgCY9NQh9IXP1xqLAAaVeAbOJpMc4I8xi ggs5+lH4govAGo81IJLjIJBcIJBcIJAXlGeIfPnm8JF2wb9f66USNZh5mFJHdl7uEZ2U7Vn0+sc4 AbV5EYv/uIOEdSEONYgouIPApMc4lGNGgovAlWn9PfDmrdJU+sOW6JBcIJBcIJBcyC2sNRz66Rn4 yC81JU81NsnuEZ12i3z69lXJnxzJk+44HYLAZgXZSHJHve19i3q0lHd1DAR2btesgg81J2Mnxp4s 5TpHIIRelwB98QF5NY53EZ9y7jXJveuEyU6ZCm/cIIAoBDD0PxpHm6qEIBD0i+xePXD0oI5rIIKm lwCT6RudceqmF8KEPeReoBH0lqY1Aac4gKLAHg+G6zNflXtdII847jXE8GfeEQ/ele84yVpdIIRe 7dtUQDtYlOPAjSTZsvYlyCl9fHn0fsoTL9WUlW+ZLI81xjTEmyo5m1L18PdygKZr+lH4F8CFBeum IIKejSTZI8ximFJHpEeI8HNH7d12eUB57UZGgLR5blVnmzpfxiTZCqapNQh5KjMbIBJHF8JMBx1n z8938OOE6YuWblVn8OMTzbjm8OOERlA8sOkoFOoTuhj0dLclnxpHmyyj+kc1bkM5dkzZI0h5IeOm CjKdwEfeblXJu8vAKqOEzTj0hC3ZfP12RQaZOUXZAaVeASV28HXJtKepBPjmxqLAAbV5F8JMCqRe y0Wz8i+Zk9OqIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcIJBcEY+Nk8Huy0F9fHn0lHd1bsEoIA+I ve36i3z6ATNH+sOe6zNfpAzZZgH9lxT6x8R985JUxqLAk+6Ei2h98RX6ATNHNQh9lWsn5/HmJVso fHn0B+KeIB36zMuexW3ZsO1eGLEA2OgozS41vf12PfR5lwBCZIr/ATV4y8O7gpvjdKEoCqY4dbHm NZh5NZjmyL1KpMOWPXL1EZ12cfjmtLclgDblJckosPnmJcm3uIPAKyH93MMTPfaRbsG/zb4lAaNM i2jRy0F5lX2ay1UwDaFlmNZyxqLAliInfj1CKzO0KyHKgh3JF8ahfjn0+sc462CppEV0k1W8mMJu ASV2v6rABHj0JcumASF9fPnmNY53ASdh+tdyASU0uJeR6BRnF1QG8jvLvf12ZIqEy0U0z03wlONM 8zH0IeOmblNdcW6ZSHJHbtHmlOX6lHd1DAR2vf12tDH0jSTZNtclgrPjAbV58PesBO6hIeOeNRqs +tdycXzVAbV5IJYlm8Km8HXJyTk8gKLAAbV5F8C/uhj0JKeppMc4K7esPXTVAbV58HNHIeOmJZjm Jc/Wfsx9+tdyAaViBeumbkM5dkzZ9K41ATcH7VJfHYITUxE8fP12fjv1bsOEIXH5DbdyNkH9PXAv F9LthDtfmFAvF0DolOXDNk2AdspMASeGghn0AbV58Pe6gosTGovAdDNHCrR5lwB9fO+pbtWUASU0 Gg9GeVIb+lH48Pes7Vn0AbV5DBZEP4rA1htXzE00ug6ZMre6k+44AbV5vW/eB+Y1xqLA7U81fsqE dlg8dCc1bsG/F8C/upyUIWGTpJ6smFTJfjsofHkhveuemVIbxqLAU3XJy0F9fHn0CqKefrnmBOi/ RlA8pMOWF8Atceqmfjv1mzpfNZqJpNV2/6KeNQiTKjMb66OEbtV5dKVim8JMmOv/gDbuHIi3vW/e ds44DB2KHRTJR5SUn4rACrAZNQw0z81i7d12srLjJctMPeAouhj0z8v/n5pUDIZ3uIPAGo819lXJ IWU0z81imML/uhzJdqR9nxzEJ3XJIeVe/Tj0DTH0dbclyC00z0l9fHn0fsx9zb4lPXL1blXJDaNu Bx1nmzpfHZSUF8R9Qi3Z6Q89P4qmcXr1bsdrfPnmi2jKUf6lk0MnNsOeII93i2h9h4OE6Z9yvW/e n4qE5apublVnIIC/ASHRleueHBxCgh3JF8ahfjn0ATWKi2h98YVipEc9xqTNIWdhP4rAjSTZl4Km sPvjDTH0AbV5F8RedDX6OFj0z89rNk0DOp157Vn07qPAdCc1mzpfTYvACyHKDTH0edDmBDL1blNd Aad3i+53EZnm7jXJ7V3V6IReNsOeII1imFJXBX3J7Vv1IIKemzpfTYvAlGc9BX3JAkbeoIpMi2w0 z0+Ngg9JI8qeF8ahIBui+tV5Jd9SZodrgosTCqZ3uIPA/TzJCiIn5TgvIIKmfj3VQC81HIjuveue 9lH0AbV5uBXV2Px5dDO/qDXJ7d+RHBoofHn0fr12yK1igLR5IIKeF0TnIJR5DSdJbsOE7dkZgh3E 6zNfP46EK7VKBXn0CzV4uBfTbsG/cW6Zuo41F9AZgovAK7EZk26ZMqHg3MXDuhj0UW6GxjIoh4OE 6Z9yIJYlyCl9fHn0GSo5xjJHi3q/uAMnNsOeJY7W8RWjxopMASV2vW/egqOWIJR5DaPAAbV5m6oT tLHmi2w0z0l9fHn0fsx9GosTCjKs+tdyflj0I0h5B3K0zM93dDEhytqqIJBcIJBcmzpfNZqJyb9y pMEoJV0tB4uxIBhtDAI787WUKd4s5TpHmzpfNRz6m9R57tyU6JBcIJBcIJBcGCM5NRr1+lH4blNd cXoo8QHKfPnmNQh9fHn0B+KeIAl9ZBpXMxn09OqEpNV5fruqIJBcIJBcbsOemzpfNZxK+tdydDEh 6zNfNY41EY19GarAtsX6cXj0ZhNd8zH0+sc4dKOmjSaZk9OqIJBcIJBcIJBcIJBcIJBcIJBcIJBc IJBcIJBcIJBcIAD7Cm+Z66USgh3JF1JdGarAK7EZNQh5xqKmIBD0fsz6NRqsDTNHm6oTtLPjCzX6 OFj0AbV58HNHTYsTgKY4SHJHIAa7RYKeM8LA7c+pxiJGgg9GCyGTxqJM8xTJIWF9fHn0GaoTGovA BOxeRkTZUe41Jd9ym1TJvW/eTQ9GIB1nASHRy8P3lic1gKY4gKLAI1x4m8LAy8epxqLAjzpHzMue 6Aa77c+pxiJGgovAAbV5vW/ePWZGCyGTxqJM8xTJIWF9fHn0GPpyDIRi6R1hPfLjAaVeASHKDTH0 B2SzAad3uIPAAbV5vftUKjMbF8ahy0HRy8E26R1hlOG/6yM5DIKeIAl98RX6xjZ1F8JMIBD0z8ue NYxi0oepgKKW8OMTuhgv6IKmlwDRy0F9fHn0CjVnDJ2U8PdyU+PA+sc4yg41K6OejeqEuIHuv6rA NZgZghv1lW3953P1bsOeIBD0ecKmEZ2UDaMTuhj0I0h5yDtXzEl9m9LjxiJGle3DfrnmxqLAdKdr KyGTxqLAlW2zgpvjAaVeASV2IIRezTj0B2InBDL1bsOEIWM7zbjm8OP/7tyUNk0DOp15DJKJ5TpH IIRezTj0DTMbxqLAtDH0ZgeGu1tf+lH46BJHmzpfNQh5HZaGgg9GCyHKDTH0lPNy7idJxiZGZgeG NYi/daG3uIPABP5y+sOWF8JM8HfT6Rn4mzpfNRz6Aaep53P1m6oTy1X6JDNHcfjm6BJXlW2zi2h9 fHn0zbjmDBT6DlE8pMOWIAa7daOmASFC7d2U66OEbsMTuIPAhC2zxqTkk8ehQXP1IIKeEQkBdqKE Ieehy0F5IXO0BKC3eVBt8HfTxjJHmzzJI0jK7jNfF8TD3FE8pMOW8OepmcJMbkM5dkzZI06N+tV2 7VtHcXj0CrYlPXL1IBD066xeRkTZq3JH6IKmlwI7zbjmDBK/7sqeBeumvWs5pMOWblVnvf9ymUbe yDtHxqC3uIPAII818zH0dKdrKyF5j4qmcX4U7cLA1oum9Prjuo5rF9AZk+44ZItMIXO0pFfuk8G/ mcTDuhj0RRSay0FClW+GIIAoII84y9HmlX2aEYv/uIOEjZvjgKY4AbV5F8JMj4oTuhj0ZoG/daXN z0l9fHn0CqZrDBK/uAM5NY6pxiZGpMOWbsOemzpfNQiT9lXJlPV5lxZEIXNXxqTNz0l9fHn0tDNX K6OmmMapQibk8QGTP4qmlwB9IWX9Nk0DOp15DAB9HRTJF8KElqY4AbV5ve36i3z6ATNH+sOe6zNf gh3Jd3n0CqRe+ld1+sEUmzpfgh3JB4m/ve36JU818HNHU+PATYvAlHd16IKmlwR2vyzZdLclzMue IAl9h4OE6Z9yvf+6AWMnlxKeArvj+sc4I0h90lJfgKKe6zNfNQhC+tV58OV9+sc4Ue41mMC/n4x9 PXCU8wI5xjZ1I0o7zbjmcXr1IIKemzpfNRz6jaY18jvL8IXACjRnoDn0tlXJTRtf66OEy1EcJKPA 7Vv1F8RenxzJlX/uIJDmNQh5TQ9Jgh3Jfi+Zk8Ve6Z9y+lH4HBgoy0F9fHn0tDNXK6OmmMapx5hc '''.decode('base64') #print roll_the_text(' ').decode('mac-roman') #print '-----' #print roll_the_text('\rtonight red lanterns are battered,\r\rlaughing,\rin the mechanism. ').decode('mac-roman') #print '-----' poem = un_roll_the_text(zi) print poem.replace('\r', '\n').decode('mac-roman') print '========================ENCRYPTED TEXT FOLLOWS======================' enclines = [''.join(chr(un_waymute_it(ord(c))) for c in line) for line in poem.split('\r')] delchars = '\x00\x02\x03\x04\x05' # invisible boxchars = '\x01\x06\x07\x08\x0a\x0b\x0c\x0d\x0e\x0f' + ''.join(chr(i) for i in range(16, 32)) # these appear as boxes box = u'\u25AF' for page_start in range(19, len(enclines) - len(enclines) % 17, 17): print for line in enclines[page_start:page_start+17]: dispenc = line dispenc = dispenc.decode('mac-roman') for c in delchars: dispenc = dispenc.replace(c, '') for c in boxchars: dispenc = dispenc.replace(c, box) print dispenc print '------------------------[PAGE BREAK]----------------------------' print '====================PARTIAL DECRYPTION========================' ciphertext = '''•ù;•âã•©•ã••üπã9••ã••ù;•9••ãâ•;üèã©••ùõ••ù•;üô©•õ9•)••©ãúüª©çü;© ®•ãù•••;•ù•èã ∫ïòï•ö••;üù••9ï •ùâ•;üèã©••ùõÖ••üèè•Ö #'1° ®•ãù••ã•õç´ãâ•••;••üâ•••)9•ù©;•âüªù •ù╪9ü©ã•´ùâã9•©•ãè •ù••••ç•á畕㕪••©ã•)ãù••ç5 •(•)•ì;•;•ª•è•ççÖ••´õï•#'#'ï•''' lines = ciphertext.split('\n') for line in lines: print ''.join(chr(waymute_it(ord(c))) for c in line).decode('mac-roman')
This program prints out several pages of ciphertext. You can compare the generated encrypted text with the text that is scrolled during the last 25 seconds of the emulated run of Agrippa to see that the implemented encryption routine matches the original. Note that the last line of ciphertext on each “page” is not visible due to the limited height of the text display window. Finally, the script prints out a partial decryption of the first page of ciphertext based on what is visible (boxes have been replaced with • characters, which decrypt to <).
You must be logged in to post a comment.