Source code for encryption

# -*- coding: utf-8 -*-

# This source file is part of mc4p,
# the Minecraft Portable Protocol-Parsing Proxy.
#
# Copyright (C) 2011 Matthew J. McGill, Simon Marti

# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details

from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Cipher import AES, DES
from hashlib import md5
from struct import unpack


[docs]def decode_public_key(bytes): """Decodes a public RSA key in ASN.1 format as defined by x.509""" return RSA.importKey(bytes)
[docs]def encode_public_key(key): """Encodes a public RSA key in ASN.1 format as defined by x.509""" return key.publickey().exportKey(format="DER")
[docs]def generate_key_pair(): """Generates a 1024 bit RSA key pair""" return RSA.generate(1024)
[docs]def generate_random_bytes(length): return Random.get_random_bytes(length)
[docs]def generate_server_id(): """Generates 20 random hex characters""" return "".join("%02x" % ord(c) for c in generate_random_bytes(10))
[docs]def generate_challenge_token(): """Generates 4 random bytes""" return generate_random_bytes(4)
[docs]def generate_shared_secret(): """Generates a 128 bit secret key to be used in symmetric encryption""" return generate_random_bytes(16)
[docs]def encrypt_shared_secret(shared_secret, public_key): """Encrypts the PKCS#1 padded shared secret using the public RSA key""" return public_key.encrypt(_pkcs1_pad(shared_secret), 0)[0]
[docs]def decrypt_shared_secret(encrypted_key, private_key): """Decrypts the PKCS#1 padded shared secret using the private RSA key""" return _pkcs1_unpad(private_key.decrypt(encrypted_key))
[docs]def encryption_for_version(version): if version <= 32: return RC4 else: return AES128CFB8
[docs]class RC4(object): def __init__(self, key): self.key = key x = 0 self.box = box = range(256) for i in range(256): x = (x + box[i] + ord(key[i % len(key)])) % 256 box[i], box[x] = box[x], box[i] self.x = self.y = 0
[docs] def crypt(self, data): out = "" box = self.box for char in data: self.x = x = (self.x + 1) % 256 self.y = y = (self.y + box[self.x]) % 256 box[x], box[y] = box[y], box[x] out += chr(ord(char) ^ box[(box[x] + box[y]) % 256]) return out
decrypt = encrypt = crypt
[docs]def AES128CFB8(shared_secret): """Creates a AES128 stream cipher using cfb8 mode""" return AES.new(shared_secret, AES.MODE_CFB, shared_secret)
def _pkcs1_unpad(bytes): pos = bytes.find('\x00') if pos > 0: return bytes[pos+1:] def _pkcs1_pad(bytes): assert len(bytes) < 117 padding = "" while len(padding) < 125-len(bytes): byte = Random.get_random_bytes(1) if byte != '\x00': padding += byte return '\x00\x02%s\x00%s' % (padding, bytes)
[docs]class PBEWithMD5AndDES(object): """PBES1 implementation according to RFC 2898 section 6.1""" SALT = '\x0c\x9d\x4a\xe4\x1e\x83\x15\xfc' COUNT = 5 def __init__(self, key): key = self._generate_key(key, self.SALT, self.COUNT, 16) self.key = key[:8] self.iv = key[8:16]
[docs] def encrypt(self, plaintext): padding = 8 - len(plaintext) % 8 plaintext += chr(padding)*padding return self._cipher().encrypt(plaintext)
[docs] def decrypt(self, ciphertext): plaintext = self._cipher().decrypt(ciphertext) return plaintext[:-ord(plaintext[-1])]
def _cipher(self): return DES.new(self.key, DES.MODE_CBC, self.iv) def _generate_key(self, key, salt, count, length): key = key + salt for i in range(count): key = md5(key).digest() return key[:length]