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)