Sunday, June 24, 2012

Encryption / Decryption and Asynchronous Socket Programming


Back again, it's been a long time since my last post due to lack of free time and laziness :p

Recently, I've got some posts in Lazarus / Free Pascal forums asking for some incompletely documented features, namely the (en|de)cryption unit (blowfish) and asynchronous socket (from fcl-net). Free Pascal is shipped with huge powerful libraries which are mostly, unfortunately, undocumented. Through this post, I hope I can help document it a bit through examples (I'm still lazy for real documentation commit :p). Let's start, shall we?

Blowfish, the cryptography unit

current documentation: http://www.freepascal.org/docs-html/fcl/blowfish/index.html

This unit implements encryption / decryption classes with keys, and is able to apply it on any TStream descendant. For easiness, we'll use TStringStream for the example. On to the code:

{$mode objfpc}{$H+}

uses
  Classes,
  BlowFish;

var
  en: TBlowFishEncryptStream;
  de: TBlowFishDeCryptStream;
  s1,s2: TStringStream;
  key,value,temp: String;
begin
  { 1 }
  key := 'testkey';
  value := 'this is a string';
  { 2 }
  s1 := TStringStream.Create('');
  en := TBlowFishEncryptStream.Create(key,s1);
  { 3 }
  en.WriteAnsiString(value);
  en.Free;
  WriteLn('encrypted: ' + s1.DataString);
  { 4 }
  s2 := TStringStream.Create(s1.DataString);
  s1.Free;
  { 5 }
  de := TBlowFishDeCryptStream.Create(key,s2);
  { 6 }
  temp := de.ReadAnsiString;
  WriteLn('decrypted: ' + temp);
  
  de.Free;
  s2.Free;
end.

Explanations per curly brackets:

  1. First, we prepare the key (key) and data to be encrypted (value)
  2. Next, we create a TBlowFishEncryptStream instance, providing the key and stream to write the encrypted data into (s1)
  3. Now we write the unencrypted data. For testing, we output the encrypted data. You'll see that it would be a bunch of weird bytes
  4. Next, we will try to decrypt the data back to its original form. First, we create another TStringStream, this time we give the encrypted data as the stream data
  5. Then we create a TBlowFishDeCryptStream instance, providing the key that was used to encrypt the data and the stream from which the encrypted data would be read
  6. Next, read the data and output it. You'll see it's the original 'this is a string'
So easy, huh? On to the next one.

fcl-net, the undocumented treasure

current documentation: err.. none

This package offers a lot of networking things: asychronous socket, dns resolver, HTTP servlet, socket streams, etc. We would concentrate only on the asychronous socket (and implicitly, socket streams). At first glance, it looks uneasy to use. I have to dig in the sources to see how it works and guess how to use it. We will implement a server with multiple client support. To stay focus, the client will only connect, send a 'hello' message, then disconnects. The server would display notification for an incoming connection, the message sent by the client, and when the client disconnects. The server can only be terminated with Ctrl+C. Jump in to the server code:

{$mode objfpc}{$H+}

uses
  { 1 }
  {$ifdef unix}cthreads,{$endif}
  Classes,SysUtils,Sockets,fpAsync,fpSock;

type
  { 2 }
  TClientHandlerThread = class(TThread)
  private
    FClientStream: TSocketStream;
  public
    constructor Create(AClientStream: TSocketStream);
    procedure Execute; override;
  end;
  { 3 }
  TTestServer = class(TTCPServer)
  private
    procedure TestOnConnect(Sender: TConnectionBasedSocket; AStream: TSocketStream);
  public
    constructor Create(AOwner: TComponent); override;
  end;
{ 4 }
function AddrToString(Addr: TSockAddr): String;
begin
  Result := NetAddrToStr(Addr.sin_addr) + ':' + IntToStr(Addr.sin_port);
end;

{ TClientHandlerThread }
{ 5 }
constructor TClientHandlerThread.Create(AClientStream: TSocketStream);
begin
  inherited Create(false);
  FreeOnTerminate := true;
  FClientStream := AClientStream;
end;
{ 6 }
procedure TClientHandlerThread.Execute;
var
  Msg : String;
  Done: Boolean;
begin
  Done := false;
  repeat
    try
      Msg := FClientStream.ReadAnsiString;
      WriteLn(AddrToString(FClientStream.PeerAddress) + ': ' + Msg);
    except
      on e: EStreamError do begin
        Done := true;
      end;
    end;
  until Done;
  WriteLn(AddrToString(FClientStream.PeerAddress) + ' disconnected');
end;

{ TTestServer }
{ 7 }
procedure TTestServer.TestOnConnect(Sender: TConnectionBasedSocket; AStream: TSocketStream);
begin
  WriteLn('Incoming connection from ' + AddrToString(AStream.PeerAddress));
  TClientHandlerThread.Create(AStream);
end;
{ 8 }
constructor TTestServer.Create(AOwner: TComponent);
begin
  inherited;
  OnConnect := @TestOnConnect;
end;

{ main }
{ 9 }
var
  ServerEventLoop: TEventLoop;
begin
  ServerEventLoop := TEventLoop.Create;
  with TTestServer.Create(nil) do begin
    EventLoop := ServerEventLoop;
    Port := 12000;
    WriteLn('Serving...');
    Active := true;
    EventLoop.Run;
  end;
  ServerEventLoop.Free;
end.

It's a bit long, so take a breath:

  1. We will need each client to be handled in its own thread, so we need cthreads unit for *nix OSes
  2. The client handler thread, it would work on the given client socket stream
  3. The server, we will create an override constructor to hook when a client connects
  4. Helper routine to get ip:port as string
  5. Overriden constructor for the thread, will call the inherited constructor (with false argument to indicate the thread shouldn't be in suspended state), setting the object to free itself whenever the execution has finished, and assign the socket stream to a private attribute
  6. The core of the thread. Will try to read what the client sends and output it in the server log until the client disconnects
  7. OnConnect handler, prints notification message whenever a client connects and create a handler thread for it
  8. Overriden constructor for the server, assigns the OnConnect handler
  9. Main program. Create event loop object for the server, creates the server, assigning event loop and port to listen, and ready to accept connections...
Phew, go on to the client. This time it's simpler:

{$mode objfpc}{$H+}

uses
  Classes,SysUtils,Sockets,fpAsync,fpSock;

var
  ClientEventLoop: TEventLoop;
begin
  ClientEventLoop := TEventLoop.Create;
  with TTCPClient.Create(nil) do begin
    EventLoop := ClientEventLoop;
    Host := '127.0.0.1';
    Port := 12000;
    Active := true;
    EventLoop.Run;
    Stream.WriteAnsiString('Hello');
    Active := false;
  end;
  ClientEventLoop.Free;
end.

Not numbered since it's only a single main code block. First it creates event loop for the client, create the client, assigning event loop, host and port of the server to connect, activate the connection, send a 'Hello' message to the server and disconnects. Clear enough, eh?

OK, that's all for now. Got any questions? Just ask :)

11 comments:

  1. Well done Sir !!!

    this is GulyOne :) sent u a mail on lazarus forum

    ReplyDelete
  2. Hello Leledumbo.
    Thanks for this topics. Blowfish sample work? but there is 1 big problem:
    i have string (1000 symbols). I can encrypt an decrypt it usng Blowfish, but if i want to save this encrypted string to file? and after this read sting from file and try to decode it - faild.
    I see that before write to file encrypted string is above 1009 symbols, but when i put it into file? and then read from file it length=1000 (less 9 symbols).

    Can you say: how to save encoded strings to files and how to decode them after reading?

    ReplyDelete
    Replies
    1. How do you save the file? Or prior to that, how do you encrypt the data? Note that encryption works on the whole string structure (not just the data) and the encrypted string may contain characters special (e.g. if you use TStringList) that may be gone or converted if not handled with care.

      Delete
  3. leledumbo, I succesfully compiled and run the example on a MAC OS/X Lion machine, but cannot make it run on Windows. Perhaps it lacks some libraries.
    Any idea for help?
    Thanks,
    Niros Tamos

    ReplyDelete
  4. Great Article! ...How could I create a telnet connection with fcl-net, send a string and get the response from the server? Can you use the same idea TCPClient? Thank for attention!

    ReplyDelete
    Replies
    1. I forgot on which layer telnet is (my computer networking theory skill is bad). If it's layer 7 (application), then you can build on top of TCP. Otherwise, read the respective RFC if you want to write one yourself or use existing components / library. AFAIK, lNet and Synapse has telnet component / class.

      Delete
  5. I tried this code in Lazarus on my Raspberry Pi 2 (ARM processor) as a server and on the client side I used the Indy component TIdTCPClient with the WriteLn('...') method. This didn't worked. The problem was not on the client side but on the server side. For some reason the ReadAnsiString() didn't read incomming data at all or closed the connection server-side.

    This is a modified code for those of you who want to implement a simple text based "protocol" which can be used through telnet too:

    procedure processMsg(peerAddr: String; str: String);
    begin
    WriteLn(Trim(str));
    end;

    procedure TClientHandlerThread.Execute;
    var
    Msg: String;
    MsgFull: String;
    MsgByte: Byte;
    Done: Boolean;
    begin

    Done := false;
    Msg := '';

    repeat
    try
    MsgByte := FClientStream.ReadByte();

    if MsgByte = Ord(#10) then
    begin
    MsgFull := Msg;
    Msg := '';
    processMsg(AddrToString(FClientStream.PeerAddress), MsgFull);
    end
    else
    begin
    Msg := Msg + Chr(MsgByte);
    end;
    except
    on e: EStreamError do begin
    Done := true;
    end;
    end;
    until Done;

    WriteLn(AddrToString(FClientStream.PeerAddress) + ' disconnected');
    end;

    ReplyDelete
    Replies
    1. I never use Indy, but I don't think its WriteLn method sends strings in AnsiString format as expected by ReadAnsiString. I think it just sends the data plainly, which in that case Read should be used instead.

      Delete
    2. Yep, I know. I manually sent the length of the payload as a 32-bit integer before the actual data, but there was still that problem. Btw, there is no AnsiString format. WriteAnsiString() and ReadAnsiString() just additionally write and read the length indicator into the byte stream which is a kind of a custom protocol but not a standard afaik. The function names are somewhat misleading imho.

      Delete
  6. Gracias por la info, muy Ăștil.

    ReplyDelete