Programming Forums
User Name Password Register
 

RSS Feed
FORUM INDEX | TODAY'S POSTS | UNANSWERED THREADS | ADVANCED SEARCH

Reply
 
Thread Tools Display Modes
Old Oct 19th, 2005, 3:29 PM   #1
Arevos
Programming Guru
 
Arevos's Avatar
 
Join Date: Aug 2005
Location: England
Posts: 1,499
Rep Power: 4 Arevos is on a distinguished road
Strongly encrypted stream protocol

I've created an secure stream protocol and library in Python that uses RSA/AES encryption. It's hopefully pretty simple to use, though I'm unsure about the current way I've gone about implementing it. On the off chance that this is useful to someone, or if someone has a suggestion on how to improve it, I've posted up the code.This protocol needs the Python Cryptography Toolkit.

A simple echo server-client using this library looks like this:

client.py
import socket
import protocol

localhost = ''

sock = socket.socket()
sock.connect((localhost, 6000))
client = protocol.EncryptedClient(sock.recv, sock.sendall, sock.close)
client.write("Hello World")
print client.read(1024)
client.close()
server.py
import socket
import protocol

localhost = ''

sock = socket.socket()
sock.bind((localhost, 6000))
sock.listen(1)

conn, address = sock.accept()
server = protocol.EncryptedServer(conn.recv, conn.sendall, conn.close)

data = server.read(1024)
server.write(data)
server.close()
sock.close()

The protocol itself is pretty much as basic as you can get:
[client] PublicKey: <a public key as a string>
[server] CipherKey: <a cipher key encrypted with the public key>
<encrypted stream follows>
The public key is RSA, and takes the form: <hex encoded n>.<hex encoded e>
The cipher key is AES, and takes the form: <hex encoded RSA encrypted byte string>
The encrypted stream are 16-byte chunks of information encrypted with the AES cipher key. When decrypted, the first byte specifies the length of the string contained in the following 15 bytes. Padding is done with Xs.

The protocol library itself is below:

protocol.py
import binascii
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Util.randpool import RandomPool

def hexify(o):
   "Encode a number or a string into a hexidecimal representation."
   if type(o) == int or type(o) == long:
      return hex(o)[2:].rstrip('L').lower()
   elif type(o) == str:
      return binascii.hexlify(o)

def unhexify(o, type):
   "Decode a hexidecimal coded string into a number or a string."
   if type == int or type == long:
      return int(o, 16)
   elif type == str:
      return binascii.unhexlify(o)

def chunks(s, size):
   "Split a string into chunks of equal size."
   for i in range(0, len(s), size):
      yield s[i:i + size]

class BaseKey:
   "Base key wrapper."   
   def __init__(self, key):
      self._key = key


class RSAKey(BaseKey):
   "Wrapper around Crypto.PublicKey.RSA."
      
   @classmethod
   def generate(cls, size = 1024):
      "Generate a new private key."
      return cls(RSA.generate(size, RandomPool().get_bytes))
      
   def export(self):
      "Export key to a hexidecimal string."
      public_key = self._key.publickey()
      return hexify(public_key.n) + "." + hexify(public_key.e)

   @classmethod
   def load(cls, key):
      "Import a key from a string."
      key_parts = tuple([unhexify(o, long) for o in key.split(".")])
      return cls(RSA.construct(key_parts))

   def encrypt(self, s):
      "Encrypt a string."
      return self._key.encrypt(s, '')[0]

   def decrypt(self, s):
      "Decrypt a string. Imported keys cannot decrypt."
      return self._key.decrypt(s)


class AESKey(BaseKey):
   """
   Wrapper around Crypto.Cipher.AES.
   AES is a symmetric cipher, and as such must never transmit its key
   plaintext. As such, the export and load functions need a key from an
   asymmetric encryption algorithm that has 'encrypt' and 'decrypt' methods.
   """
   @classmethod
   def generate(cls, size = 32):
      "Generate a new cipher key."
      return cls(RandomPool().get_bytes(size))
   
   def export(self, public_key):
      "Export key to an encrypted hexidecimal string."
      return hexify(public_key.encrypt(self._key))

   @classmethod
   def load(cls, cipher_key, private_key):
      "Import a key from an encrypted hexidecimal string."
      return cls(private_key.decrypt(unhexify(cipher_key, str)))

   
   def encrypt(self, s):
      "Encrypt a string."
      return ''.join(AES.new(self._key).encrypt(self._serialize(c))
                     for c in chunks(s, 15))

   def decrypt(self, s):
      """Decrypt a string. Imported keys cannot decrypt. String length
      must be a multiple of 16."""
      return ''.join(self._unserialize(AES.new(self._key).decrypt(c))
                     for c in chunks(s, 16))
   
   @staticmethod
   def _serialize(s, l = 15):
      assert len(s) < 256
      padding = 'X' * ((l - len(s)) % l)
      return chr(len(s)) + s + padding
   
   @staticmethod
   def _unserialize(s):
      assert len(s) <= 256
      l = ord(s[0]) + 1
      return s[1:l]
   

class ProtocolError(Exception):
   "Raised when the protocol object encounters invalid input."

class BaseProtocol:
   """
   Wraps the protocol in an object so that their use is transparent
   to the user. All protocol objects have setup, read, write and close
   functions.
   """   
   def __init__(self, read, write, close):
      "Initiate object with functions for reading and writing."
      self._read  = read
      self._write = write
      self._close = close
      self.setup()

   def setup(self):
      pass

   def read(self, size):
      return self._read(size)

   def write(self, data):
      return self._write(data)
   
   def close(self):
      return self._close()


class LineMixin:
   "Mixin that adds readline and writeline functions to protocols."
   buffer_size = 15
   buffer = ""

   def readline(self):
      "Read a line."
      while self.buffer.find('\n') == -1:
         s = self.read(self.buffer_size)
         if s == "":
            break
         self.buffer += s
      line, self.buffer = self.buffer.split('\n', 1)
      return line.rstrip('\r')
      
   def writeline(self, line):
      "Write a line."
      self.write(line + "\r\n")


class AttributeProtocol(BaseProtocol, LineMixin):
   """
   Protocol that sends and receives data via attributes. Each line sent
   should contain an attribute. Attributes consist of a name and a value, and
   are separated by colons.
   e.g.
   Password: secret
   Access: OK
   Get: file.txt
   """
   
   def readattr(self, name = None):
      """
      Read an attribute. If name is set, a ProtocolError is raised if
      the expecpted name is not encountered.
      """
      attr, value = [s.strip() for s in self.readline().split(":")]
      if name == None:
         return attr, value
      elif attr == name:
         return value
      else:
         raise ProtocolError, "Unexpected attribute: %s" % attr

   def writeattr(self, name, value):
      "Write an attibute"
      self.writeline(name + ": " + value)


class EncryptedBase(BaseProtocol):
   """
   Encrypted Protocol. Subclasses need to provide a cipher_key attribute upon
   class initialisation. This is usually done by overloading the setup
   function.
   """   
   def __init__(self, read, write, close,
                PublicKey = RSAKey, CipherKey = AESKey):
      self.PublicKey = PublicKey
      self.CipherKey = CipherKey
      self.handshake = AttributeProtocol(read, write, close)
      BaseProtocol.__init__(self, read, write, close)
   
   def read(self, size):
      return self.cipher_key.decrypt(BaseProtocol.read(self, size))

   def write(self, s):
      return BaseProtocol.write(self, self.cipher_key.encrypt(s))


class EncryptedServer(EncryptedBase):
   "Encrypted server protocol"

   def setup(self):
      "Setup secure connection with client."
      self.foreign_key = self.PublicKey.load(
         self.handshake.readattr("PublicKey") )
      
      self.cipher_key = self.CipherKey.generate()
      
      self.handshake.writeattr("CipherKey",
         self.cipher_key.export(self.foreign_key))


class EncryptedClient(EncryptedBase):
   "Encrypted client protocol" 
   
   def __init__(self, read, write, close, public_key = None,
                PublicKey = RSAKey, CipherKey = AESKey):
      self.public_key = public_key
      if self.public_key == None:
         self.public_key = PublicKey.generate()

      EncryptedBase.__init__(self, read, write, close, PublicKey, CipherKey)

   
   def setup(self):
      "Setup secure connection with client."
      self.handshake.writeattr("PublicKey", self.public_key.export())
      
      self.cipher_key = self.CipherKey.load(
         self.handshake.readattr("CipherKey"), self.public_key)
Arevos is offline   Reply With Quote
Old Oct 19th, 2005, 3:31 PM   #2
Polyphemus_
Expert Programmer
 
Polyphemus_'s Avatar
 
Join Date: Aug 2005
Location: Rotterdam, the Netherlands
Posts: 942
Rep Power: 3 Polyphemus_ is on a distinguished road
i was just looking for something like that.. is it ok if i convert it to C++? (not now, but later..)
Polyphemus_ is offline   Reply With Quote
Old Oct 19th, 2005, 3:34 PM   #3
Arevos
Programming Guru
 
Arevos's Avatar
 
Join Date: Aug 2005
Location: England
Posts: 1,499
Rep Power: 4 Arevos is on a distinguished road
Sure; but I'm not sure how much work that'll save you. You'll still need libraries for RSA and AES- or whatever assymmetric/symmetric encryption protocols you use, and libraries to convert numbers and strings into hexidecimal strings.
Arevos is offline   Reply With Quote
Old Oct 19th, 2005, 4:37 PM   #4
bja888
Hobbyist Programmer
 
bja888's Avatar
 
Join Date: Oct 2005
Location: Central, FL
Posts: 213
Rep Power: 0 bja888 is an unknown quantity at this point
Send a message via AIM to bja888 Send a message via Yahoo to bja888
I wish I knew what you are talking about.I understand encrypted and protocol but other than that I'm lost... One day....
bja888 is offline   Reply With Quote
Old Oct 19th, 2005, 5:12 PM   #5
iignotus
Professional Programmer
 
iignotus's Avatar
 
Join Date: Apr 2005
Location: Nowhere Special
Posts: 466
Rep Power: 4 iignotus is on a distinguished road
Send a message via AIM to iignotus
Quote:
Originally Posted by bja888
I wish I knew what you are talking about.I understand encrypted and protocol but other than that I'm lost... One day....
There's not much more to it.... what don't you understand?
__________________
% rc4 hexkey < input > output
#define S ,t=s[i],s[i]=s[j],s[j]=t /* rc4 hexkey <file */
unsigned char k[256],s[256],i,j,t;main(c,v,e)char**v;{++v;while(++i)s[ 
i]=i;for(c=0;*(*v)++;k[c++]=e)sscanf((*v)++-1,"%2x",&e);while(j+=s[i]
+k[i%c]S,++i);for(j=0;c=~getchar();putchar(~c^s[t+=s[i]]))j+=s[++i]S;}
iignotus is offline   Reply With Quote
Old Oct 30th, 2005, 2:39 PM   #6
Arevos
Programming Guru
 
Arevos's Avatar
 
Join Date: Aug 2005
Location: England
Posts: 1,499
Rep Power: 4 Arevos is on a distinguished road
An better documented and slightly better designed update is available for download, in case anyone's interested.

In the next version, an attacker will need private keys from both parties in order to decrypt the translation. The handshaking protocol will thus be something like:
[client] PublicKey: <client's public key>
[server] PublicKey: <server's public key>
[client] CipherKey: <random cipher key encrypted with server's public key>
[server] CipherKey: <random cipher key encrypted with client's public key>
<stream of data encrypted with server's cipher key XORed with client's key>
Arevos is offline   Reply With Quote
Reply

Bookmarks

« Previous Thread in Forum | Next Thread in Forum »

Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump




DaniWeb IT Discussion Community
All times are GMT -5. The time now is 9:00 PM.

Powered by vBulletin® Version 3.7.0, Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Copyright ©2007 DaniWeb® LLC