From ed885cafc5262304697bdfbff500cc7a39a6fb90 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Tue, 13 Oct 2009 10:08:03 -0600 Subject: [PATCH] Added new puzzle category, crypto. --- puzzles/crypto/1/index.html | 16 +++++++ puzzles/crypto/1/key | 1 + puzzles/crypto/100/index.html | 3 ++ puzzles/crypto/100/key | 1 + puzzles/crypto/100ceasar.py | 24 ++++++++++ puzzles/crypto/110/index.html | 4 ++ puzzles/crypto/110/key | 1 + puzzles/crypto/110substitution.py | 48 +++++++++++++++++++ puzzles/crypto/120/index.html | 6 +++ puzzles/crypto/120/key | 1 + puzzles/crypto/120binary.py | 48 +++++++++++++++++++ puzzles/crypto/130manchester.py | 60 ++++++++++++++++++++++++ puzzles/crypto/140/key | 1 + puzzles/crypto/140morris.py | 77 +++++++++++++++++++++++++++++++ puzzles/crypto/150/key | 1 + puzzles/crypto/150sbox.py | 27 +++++++++++ puzzles/crypto/160xor.py | 11 +++++ 17 files changed, 330 insertions(+) create mode 100644 puzzles/crypto/1/index.html create mode 100644 puzzles/crypto/1/key create mode 100644 puzzles/crypto/100/index.html create mode 100644 puzzles/crypto/100/key create mode 100644 puzzles/crypto/100ceasar.py create mode 100644 puzzles/crypto/110/index.html create mode 100644 puzzles/crypto/110/key create mode 100644 puzzles/crypto/110substitution.py create mode 100644 puzzles/crypto/120/index.html create mode 100644 puzzles/crypto/120/key create mode 100644 puzzles/crypto/120binary.py create mode 100644 puzzles/crypto/130manchester.py create mode 100644 puzzles/crypto/140/key create mode 100644 puzzles/crypto/140morris.py create mode 100644 puzzles/crypto/150/key create mode 100644 puzzles/crypto/150sbox.py create mode 100644 puzzles/crypto/160xor.py diff --git a/puzzles/crypto/1/index.html b/puzzles/crypto/1/index.html new file mode 100644 index 0000000..573ecb8 --- /dev/null +++ b/puzzles/crypto/1/index.html @@ -0,0 +1,16 @@ +
+
Alice +
Welcome to Bletchley. It works like this: I'll say something to Bob, +and he'll say something back. Our communication will be encrypted in some +manner, or at least obfuscated. Your job is to get the plaintext, and +find the puzzle key. +
Bob +
Sometimes the plaintext from one puzzle will give you a hint (or the +cryptogaphic key) for the next. When we give you such keys, we'll always +do so in a straightforward manner. The puzzle key for each puzzle +is always in what I say, and there shouldn't be any tricks involved in +figuring out what it is. +
Alice
Good Luck! +
Bob
You'll need it. By the way, the key is 'dirtbags'. +
+ diff --git a/puzzles/crypto/1/key b/puzzles/crypto/1/key new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/puzzles/crypto/1/key @@ -0,0 +1 @@ +dirtbags diff --git a/puzzles/crypto/100/index.html b/puzzles/crypto/100/index.html new file mode 100644 index 0000000..fbe3426 --- /dev/null +++ b/puzzles/crypto/100/index.html @@ -0,0 +1,3 @@ +
Alice
nyy unvy prnfne. +
Bob
prnfne vf gur xrl +
diff --git a/puzzles/crypto/100/key b/puzzles/crypto/100/key new file mode 100644 index 0000000..cd9b1fa --- /dev/null +++ b/puzzles/crypto/100/key @@ -0,0 +1 @@ +ceasar diff --git a/puzzles/crypto/100ceasar.py b/puzzles/crypto/100ceasar.py new file mode 100644 index 0000000..a2eb623 --- /dev/null +++ b/puzzles/crypto/100ceasar.py @@ -0,0 +1,24 @@ +plaintext = [b'all hail ceasar.', b'ceasar is the key'] + +alpha = b'abcdefghijklmnopqrstuvwxyz' + +def ceasar(text, r): + out = bytearray() + for t in text: + if t in alpha: + t = t - b'a'[0] + t = (t + r)%26 + out.append(t + b'a'[0]) + else: + out.append(t) + return bytes(out) + +encode = lambda text : ceasar(text, 13) +decode = lambda text : ceasar(text, -13) + +c = encode(plaintext[0]) +print('
Alice
', str(c, 'utf-8')) +assert decode(c) == plaintext[0] +c = encode(plaintext[1]) +print('
Bob
', str(c, 'utf-8'), '
') +assert decode(c) == plaintext[1] diff --git a/puzzles/crypto/110/index.html b/puzzles/crypto/110/index.html new file mode 100644 index 0000000..a9ce834 --- /dev/null +++ b/puzzles/crypto/110/index.html @@ -0,0 +1,4 @@ +
+
Alice
Vkbd ntg duun puwtvbauwg dbnjwu, hlv bv'd vku dtnu htdbe jpbfebjwud td lduq bf d-hxyud, t vuekfbmlu lduq bf ntfg nxqupf epgjvxcptjkbe twcxpbvnd. Xi exlpdu, bfdvutq xi wuvvup dlhdvbvlvbxf, gxl'pu qxbfc hgvu dlhdvbvlvbxf. +
Bob
Vku fuyv vzx jlsswud tpu t hbv qbiiupufv; vkug tpuf'v 'ufepgjvuq' tv tww. Xk, hg vku ztg, vku oug vkbd vbnu bd: 'vku d bd ixp dleod'. +
diff --git a/puzzles/crypto/110/key b/puzzles/crypto/110/key new file mode 100644 index 0000000..1b59ab5 --- /dev/null +++ b/puzzles/crypto/110/key @@ -0,0 +1 @@ +the s is for sucks diff --git a/puzzles/crypto/110substitution.py b/puzzles/crypto/110substitution.py new file mode 100644 index 0000000..8193166 --- /dev/null +++ b/puzzles/crypto/110substitution.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 + +plaintext = [b"This may seem relatively simple, but it's the same basic " +b"principles as used in s-boxes, a technique used in many modern " +b"cryptographic algoritms. Of course, instead of letter substitution, " +b"you're doing byte substitution.", +b"The next two puzzles are a bit different; Frequency counts (of characters) " +b"will just reveal random noise. " +b"Oh, by the way, the key this time is: 'the s is for sucks'."] + +key = b"thequickbrownfxjmpdvlazygs" + +def encode(text): + ukey = key.upper() + lkey = key.lower() + assert len(set(key)) == 26, 'invalid key' + assert key.isalpha(), 'non alpha character in key' + out = bytearray() + for t in text: + if t in lkey: + out.append(lkey[t - ord('a')]) + elif t in ukey: + out.append(ukey[t - ord('A')]) + else: + out.append(t) + return bytes(out) + +def decode(text): + ukey = key.upper() + lkey = key.lower() + assert len(set(key)) == 26, 'invalid key' + assert key.isalpha(), 'non alpha character in key' + out = bytearray() + for t in text: + if t in lkey: + out.append(ord('a') + lkey.index(bytes([t]))) + elif t in ukey: + out.append(ord('A') + ukey.index(bytes([t]))) + else: + out.append(t) + return bytes(out) + +c = encode(plaintext[0]) +print('
Alice
', str(c, 'utf-8')) +assert decode(c) == plaintext[0] +c = encode(plaintext[1]) +print('
Bob
', str(c, 'utf-8'), '
') +assert decode(c) == plaintext[1] diff --git a/puzzles/crypto/120/index.html b/puzzles/crypto/120/index.html new file mode 100644 index 0000000..b24b983 --- /dev/null +++ b/puzzles/crypto/120/index.html @@ -0,0 +1,6 @@ +
+
Alice +
KiTvXqBKSkdOiVHAKgyAdZXuTwudIHqzLHcJBCXRPnxCxuxhUmdKcqvNSgkHGgYmKIdQKAOPMatvHAalZzrZcMCFGaiSklvsNsgbLiJmPagZobKLLpgJJcIMDUvKEXFJMiwVbVFKZdjDMKSvTdugBqjPBdlJTlXnDAvFUPNAFpjFESbJNsgJTnCgNeeZIwGfMkmZwnhGCDrOWWYIPafuXZrKNroELoGjXwtQdlSZZwdDTEZxTulhKsAMFthZfHGqSyxnXzdPShkXVrRqDpoNmuTINniddWNvMThBQEWAVbaRLrChBicNXWYbGuwdAQrvLdfdxEJgPErTwysQ +
Bob +
LqItUzQMBgbWoNMNNavSLvNyBStVGUCWNjgQxghYZevZYvWuBcdweBUETsflAiXPPLxBFVHUOrdhFDARCngbRdLnHiytxFnSSpknmEvZDioBdxTRLnlAOcAfPKvODVUWJpstBLmtXskjWuBBHizDKXRtPobcLVyQHjtzKiFFMwzzOVnfHTqZGKKDQinlYgZDHzmWhHEKYwtDMmDiUKrZYARDIoceQDugJhrXVOZnAbbFneByTliWIpTsMWoVGGSAIzpBZXOvIihvSBcjABbCXEZIRblsPxJFUnuXrIDBFphHpFSzUrpsNUzbPXdLNBUAInuJifwtRigMdtzWGkvLAwYqIQmEetyEPSrMHNTBRLtZCJZPRcEdDQdEXdnILAAfNpwzEsVWPUzYJVBCVbUTVojLIviMzWRpMqpQotsYAqtDcFpe +
diff --git a/puzzles/crypto/120/key b/puzzles/crypto/120/key new file mode 100644 index 0000000..42e1c62 --- /dev/null +++ b/puzzles/crypto/120/key @@ -0,0 +1 @@ +Rat Fink diff --git a/puzzles/crypto/120binary.py b/puzzles/crypto/120binary.py new file mode 100644 index 0000000..d9e8498 --- /dev/null +++ b/puzzles/crypto/120binary.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 +"""This is non-obvious, so let me elaborate. The message is translated to +binary with one character per binary bit. Lower case characters are 1's, +and upper case is 0. The letters are chosen at random. Tricky, eh?""" + +import random + +lower = b'abcdefghijklmnopqrstuvwxyz' +upper = lower.upper() + +plaintext = [b'The next puzzle starts in the same way, but the last step is ' + b'different.', + b'We wouldn\'t want them to get stuck so early, would we? ' + b'Rat Fink'] + +def encode(text): + out = bytearray() + mask = 0x80 + for t in text: + for i in range(8): + if t & mask: + out.append(random.choice(lower)) + else: + out.append(random.choice(upper)) + t = t << 1 + + return bytes(out) + +def decode(text): + out = bytearray() + i = 0 + while i < len(text): + c = 0 + mask = 0x80 + for j in range(8): + if text[i] in lower: + c = c + mask + mask = mask >> 1 + i = i + 1 + out.append(c) + return bytes(out) + +c = encode(plaintext[0]) +print('
Alice
', str(c, 'utf-8')) +assert decode(c) == plaintext[0] +c = encode(plaintext[1]) +print('
Bob
', str(c, 'utf-8'), '
') +assert decode(c) == plaintext[1] diff --git a/puzzles/crypto/130manchester.py b/puzzles/crypto/130manchester.py new file mode 100644 index 0000000..82295d0 --- /dev/null +++ b/puzzles/crypto/130manchester.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +'''This is the same as the previous, but it uses non-return to zero to encode +the binary.''' + +import random + +lower = b'abcdefghijklmnopqrstuvwxyz' +upper = lower.upper() + +plaintext = [b'The next one is in Morris Code. Unlike the previous two, ' + b'they will actually need to determine some sort of key.', + b'Morris code with a key? That sounds bizarre. probable cause'] + +def encode(text): + out = bytearray() + mask = 0x80 + state = 0 + for t in text: + for i in range(8): + next = t & mask + if not state and not next: + out.append(random.choice(upper)) + out.append(random.choice(lower)) + elif not state and next: + out.append(random.choice(lower)) + out.append(random.choice(upper)) + elif state and not next: + out.append(random.choice(upper)) + out.append(random.choice(lower)) + elif state and next: + out.append(random.choice(lower)) + out.append(random.choice(upper)) + state = next + t = t << 1 + + return bytes(out) + +def decode(text): + out = bytearray() + i = 0 + while i < len(text): + c = 0 + mask = 0x80 + for j in range(8): + a = 0 if text[i] in lower else 1 + b = 0 if text[i+1] in lower else 1 + assert a != b, 'bad encoding' + if b: + c = c + mask + mask = mask >> 1 + i = i + 2 + out.append(c) + return bytes(out) + +c = encode(plaintext[0]) +print('
Alice
', str(c, 'utf-8')) +assert decode(c) == plaintext[0] +c = encode(plaintext[1]) +print('
Bob
', str(c, 'utf-8'), '
') +assert decode(c) == plaintext[1] diff --git a/puzzles/crypto/140/key b/puzzles/crypto/140/key new file mode 100644 index 0000000..0ec7de0 --- /dev/null +++ b/puzzles/crypto/140/key @@ -0,0 +1 @@ +giant chickens diff --git a/puzzles/crypto/140morris.py b/puzzles/crypto/140morris.py new file mode 100644 index 0000000..2f50b9f --- /dev/null +++ b/puzzles/crypto/140morris.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 +"""This is morris code, except the dots and dashes are each represented by +many different possible characters. The 'encryption key' is the set of +characters that represent dots, and the set that represents dashes.""" + +import random + +dots = b'acdfhkjnpsrtx' +dashes = b'begilmoquvwyz' + +morris = {'a': '.-', + 'b': '-...', + 'c': '-.-.', + 'd': '-..', + 'e': '.', + 'f': '..-.', + 'g': '--.', + 'h': '....', + 'i': '..', + 'j': '.---', + 'k': '-.-', + 'l': '.-..', + 'm': '--', + 'n': '-.', + 'o': '---', + 'p': '.--.', + 'q': '--.-', + 'r': '.-.', + 's': '...', + 't': '-', + 'u': '..-', + 'v': '...-', + 'w': '.--', + 'x': '-..-', + 'y': '-.--', + 'z': '--..', + '.': '._._._', + ',': '--..--', + ':': '---...'} + +imorris = {} +for k in morris: + imorris[morris[k]] = k + +plaintext = [b'It is fun to make up bizarre cyphers, but the next one is ' + b'something a little more standard.', + b'All I have to say is: giant chickens.'] + + +def encode(text): + out = bytearray() + for t in text: + if t == ord(' '): + out.append(' ') + else: + for bit in morris[chr(t)]: + if bit == '.': + out.append(random.choice(dots)) + else: + out.append(random.choice(dashes)) + out.append(' ') + return bytes(out) + +def decode(text): + text = text.replace(b' ', b'&') + words = text.split(b'&') + out = bytearray() + for word in words: + for c in word.split(' '): + + +c = encode(plaintext[0]) +print('
Alice
', str(c, 'utf-8')) +assert decode(c) == plaintext[0] +c = encode(plaintext[1]) +print('
Bob
', str(c, 'utf-8'), '
') +assert decode(c) == plaintext[1] diff --git a/puzzles/crypto/150/key b/puzzles/crypto/150/key new file mode 100644 index 0000000..675c4a0 --- /dev/null +++ b/puzzles/crypto/150/key @@ -0,0 +1 @@ +flaming mastiff diff --git a/puzzles/crypto/150sbox.py b/puzzles/crypto/150sbox.py new file mode 100644 index 0000000..d6e7e21 --- /dev/null +++ b/puzzles/crypto/150sbox.py @@ -0,0 +1,27 @@ +#!/usr/bin/python3 + +key = [43, 44, 227, 31, 255, 42, 194, 197, 187, 11, 92, 234, 57, 67, 45, 40, 66, 226, 214, 184, 167, 139, 210, 233, 22, 246, 150, 75, 186, 145, 86, 224, 17, 131, 24, 98, 74, 248, 213, 212, 72, 101, 160, 221, 243, 69, 113, 142, 127, 47, 141, 68, 247, 138, 124, 177, 192, 165, 110, 107, 203, 207, 254, 176, 154, 8, 87, 189, 228, 155, 143, 0, 220, 1, 128, 3, 169, 204, 162, 90, 156, 208, 170, 222, 95, 223, 188, 215, 174, 78, 48, 50, 244, 116, 179, 134, 171, 153, 15, 196, 135, 52, 85, 195, 71, 32, 190, 191, 21, 161, 63, 218, 64, 106, 123, 239, 235, 241, 34, 61, 144, 152, 111, 20, 172, 117, 237, 120, 80, 88, 200, 185, 109, 137, 37, 159, 183, 30, 202, 129, 250, 58, 9, 193, 41, 164, 65, 126, 46, 158, 132, 97, 166, 6, 23, 147, 105, 29, 38, 119, 76, 238, 240, 12, 201, 245, 230, 14, 206, 114, 10, 25, 60, 83, 236, 18, 231, 39, 77, 55, 252, 229, 100, 7, 28, 209, 51, 148, 181, 198, 225, 118, 173, 103, 35, 149, 91, 108, 219, 168, 140, 49, 33, 122, 82, 216, 53, 205, 13, 73, 249, 180, 81, 19, 112, 232, 217, 96, 62, 99, 4, 26, 178, 211, 199, 151, 102, 121, 253, 136, 130, 104, 133, 146, 89, 5, 157, 70, 84, 242, 182, 93, 251, 54, 16, 175, 56, 115, 94, 36, 27, 79, 59, 163, 125, 2] +ikey = [None]*256 +for i in range(256): + ikey[key[i]] = i + +plaintext = [b'I think it's impressive if they get this one. It will take a ' + b'lot of work to get it right. That is, unless they do ' + b'something smart like correctly guess the value of spaces. ' + b'Frequency counts won't just be your friend here, it'll be ' + b'useful in other places too.', + b'I'm not sure if that's enough text to give them the ' + b'ability to make a good frequency count. It's nice to ' + b'finally be at a real cypher that allows for things like ' + b'proper punctuation. Anyway, the key is: flaming mastiff'] + +def sbox(text, key): + out = bytearray() + for t in text: + out.append(key[t]) + return bytes(out) + +for p in plaintext: + c = sbox(text, key) + assert c == sbox(c, ikey), 'Failure' + print(c) diff --git a/puzzles/crypto/160xor.py b/puzzles/crypto/160xor.py new file mode 100644 index 0000000..7c68b88 --- /dev/null +++ b/puzzles/crypto/160xor.py @@ -0,0 +1,11 @@ +#!/usr/bin/python3 + +plaintext = [b'I wonderr if they'll try doing a frequency count again? ' + b'It should work this time as well. Hopefully messing around ' + b'with simple cyphers like + + +for p in plaintext: + c = sbox(text, key) + assert c == sbox(c, ikey), 'Failure' + print(c)