Created
September 6, 2013 01:54
-
-
Save amintos/6458623 to your computer and use it in GitHub Desktop.
Simple string encryption and decryption in pure Python using Threefish-512. No guarantee this implementation is 100% correct!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Simple Pure-Python Threefish-512 Encryption. | |
# (No guarantee that this implementation is 100% correct!) | |
# | |
# Use encrypt(text, key) and decrypt(text, key) for string encryption. | |
# | |
# The cipher operates in CBC mode with a random tweak value. | |
# | |
from StringIO import StringIO | |
from os import urandom | |
# Constants | |
ROUNDS = 72 | |
R = ((46, 36, 19, 37), | |
(33, 27, 14, 42), | |
(17, 49, 36, 39), | |
(44, 9 , 54, 56), | |
(39, 30, 34, 24), | |
(13, 50, 10, 17), | |
(25, 29, 39, 43), | |
(8 , 35, 56, 22)) | |
# Rotation primitives | |
rol = lambda x, n: ((x << n) & 0xFFFFFFFFFFFFFFFF) | (x >> (64 - n)) | |
ror = lambda x, n: (x >> n) | ((x << (64 - n)) & 0xFFFFFFFFFFFFFFFF) | |
# Key Schedule | |
def expand(key, tweak): | |
assert len(key) == 8 | |
assert len(tweak) == 2 | |
result = [] | |
key = key + [reduce(lambda a, b: a ^ b, key, 0) ^ 0x1BD11BDAA9FC1A22] | |
tweak = tweak + [tweak[0] ^ tweak[1]] | |
for i in xrange(8): | |
for s in xrange(ROUNDS / 4): | |
if i < 5: | |
result.append((key[(s + i) % 9]) & 0xFFFFFFFFFFFFFFFF) | |
elif i == 5: | |
result.append((key[(s + i) % 9] + tweak[s % 3]) & 0xFFFFFFFFFFFFFFFF) | |
elif i == 6: | |
result.append((key[(s + i) % 9] + tweak[(s + 1) % 3]) & 0xFFFFFFFFFFFFFFFF) | |
elif i == 7: | |
result.append((key[(s + i) % 9] + s) & 0xFFFFFFFFFFFFFFFF) | |
return result | |
# Block Encryption | |
def enc(block, key = [1]): | |
assert len(block) == 8 | |
assert len(key) == ROUNDS * 2 | |
k = len(key) | |
b0 = block[0] | |
b1 = block[1] | |
b2 = block[2] | |
b3 = block[3] | |
b4 = block[4] | |
b5 = block[5] | |
b6 = block[6] | |
b7 = block[7] | |
for i in xrange(ROUNDS): | |
if i % 4 == 0: | |
i8 = i * 2 | |
b0 ^= key[i8 + 0] | |
b1 ^= key[i8 + 1] | |
b2 ^= key[i8 + 2] | |
b3 ^= key[i8 + 3] | |
b0 ^= key[i8 + 4] | |
b1 ^= key[i8 + 5] | |
b2 ^= key[i8 + 6] | |
b3 ^= key[i8 + 7] | |
b0 = (b0 + b1) & 0xFFFFFFFFFFFFFFFF | |
b2 = (b2 + b3) & 0xFFFFFFFFFFFFFFFF | |
b4 = (b4 + b5) & 0xFFFFFFFFFFFFFFFF | |
b6 = (b6 + b7) & 0xFFFFFFFFFFFFFFFF | |
r = R[i % 8] | |
b1 = rol(b1, r[0]) ^ b0 | |
b3 = rol(b3, r[1]) ^ b2 | |
b5 = rol(b5, r[2]) ^ b4 | |
b7 = rol(b7, r[3]) ^ b6 | |
b0, b1, b2, b3, b4, b5, b6, b7 = b2, b1, b4, b7, b6, b5, b0, b3 | |
return [b0, b1, b2, b3, b4, b5, b6, b7] | |
# Block Decryption | |
def dec(block, key = [1]): | |
assert len(block) == 8 | |
assert len(key) == ROUNDS * 2 | |
k = len(key) | |
b0 = block[0] | |
b1 = block[1] | |
b2 = block[2] | |
b3 = block[3] | |
b4 = block[4] | |
b5 = block[5] | |
b6 = block[6] | |
b7 = block[7] | |
for i in xrange(ROUNDS - 1, -1, -1): | |
b2, b1, b4, b7, b6, b5, b0, b3 = b0, b1, b2, b3, b4, b5, b6, b7 | |
r = R[i % 8] | |
b1 = ror(b1 ^ b0, r[0]) | |
b3 = ror(b3 ^ b2, r[1]) | |
b5 = ror(b5 ^ b4, r[2]) | |
b7 = ror(b7 ^ b6, r[3]) | |
b0 = (b0 - b1) & 0xFFFFFFFFFFFFFFFF | |
b2 = (b2 - b3) & 0xFFFFFFFFFFFFFFFF | |
b4 = (b4 - b5) & 0xFFFFFFFFFFFFFFFF | |
b6 = (b6 - b7) & 0xFFFFFFFFFFFFFFFF | |
if i % 4 == 0: | |
i8 = i * 2 | |
b0 ^= key[i8 + 0] | |
b1 ^= key[i8 + 1] | |
b2 ^= key[i8 + 2] | |
b3 ^= key[i8 + 3] | |
b0 ^= key[i8 + 4] | |
b1 ^= key[i8 + 5] | |
b2 ^= key[i8 + 6] | |
b3 ^= key[i8 + 7] | |
return [b0, b1, b2, b3, b4, b5, b6, b7] | |
# Padding | |
def pad(b): | |
original_len = len(b) | |
if original_len % 64 != 63: | |
b += '\0' * (63 - (original_len % 64)) | |
b += chr(original_len % 64) | |
return b | |
def pad_key(k): | |
assert len(k) <= 64 | |
if len(k) < 64: | |
k += '\0' * (64 - len(k)) | |
return k | |
def unpad(b): | |
last_block = ord(b[-1]) | |
original_len = 64 * (len(b) / 64 - 1) + last_block | |
return b[:original_len] | |
# String to Int64 encoding | |
def iter_blocks(b): | |
for i in xrange(0, len(b), 8): | |
yield (ord(b[i]) | |
| (ord(b[i + 1]) << 8) | |
| (ord(b[i + 2]) << 16) | |
| (ord(b[i + 3]) << 24) | |
| (ord(b[i + 4]) << 32) | |
| (ord(b[i + 5]) << 40) | |
| (ord(b[i + 6]) << 48) | |
| (ord(b[i + 7]) << 56) | |
) | |
def to_bytes(blocks): | |
buf = StringIO() | |
for n in blocks: | |
for i in xrange(8): | |
buf.write(chr(n & 0xff)) | |
n >>= 8 | |
return buf.getvalue() | |
# Simple encryption with CBC | |
def encrypt(text, key): | |
if isinstance(key, str): | |
key = list(iter_blocks(pad_key(key))) | |
blocks = list(iter_blocks(pad(text))) | |
tweak = list(iter_blocks(urandom(16))) | |
for i in xrange(0, len(blocks), 8): | |
e_key = expand(key, tweak) | |
blocks[i : i + 8] = enc(blocks[i : i + 8], e_key) | |
# Cipher Block Chaining (CBC) | |
key[0] ^= blocks[i] | |
key[1] ^= blocks[i + 1] | |
key[2] ^= blocks[i + 2] | |
key[3] ^= blocks[i + 3] | |
key[4] ^= blocks[i + 4] | |
key[5] ^= blocks[i + 5] | |
key[6] ^= blocks[i + 6] | |
key[7] ^= blocks[i + 7] | |
return to_bytes(tweak + blocks) | |
def decrypt(text, key): | |
if isinstance(key, str): | |
key = list(iter_blocks(pad_key(key))) | |
blocks = list(iter_blocks(text)) | |
tweak = blocks[:2] | |
blocks = blocks[2:] | |
for i in xrange(0, len(blocks), 8): | |
next_key = key[:] | |
# Cipher Block Chaining (CBC) | |
next_key[0] ^= blocks[i] | |
next_key[1] ^= blocks[i + 1] | |
next_key[2] ^= blocks[i + 2] | |
next_key[3] ^= blocks[i + 3] | |
next_key[4] ^= blocks[i + 4] | |
next_key[5] ^= blocks[i + 5] | |
next_key[6] ^= blocks[i + 6] | |
next_key[7] ^= blocks[i + 7] | |
e_key = expand(key, tweak) | |
blocks[i : i + 8] = dec(blocks[i : i + 8], e_key) | |
key = next_key | |
return unpad(to_bytes(blocks)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment