Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Off-The-Record (OTR) Security Protocol

4.92/5 (36 votes)
29 Apr 2014CPOL10 min read 91.3K   145.6K  
A C# implementation of the OTR protocol.

Introduction

I needed an OTR library in C# that I could use for instant messaging clients that run on the Windows, Linux and Android platforms but I couldn’t find any. As a result, I decided to roll my own. The attached compressed files are the library and a console application that demonstrates the use of the Library. The OTR protocol and the Library’s interface and event functions are described below. The Library is compatible with OTR clients that implement versions 2 and 3 of the protocol [1], [2]. A client implementing this library successfully established OTR sessions with the Pidgin [3] and Spark IM [4] clients.

Note that this Library uses the BouncyCastle library for most of its cryptographic functions.

Background

Off-The-Record (OTR) [5] is a protocol that provides security around real-time instant messaging (IM) communications. It assures the following;

  • Confidentiality: Messages are encrypted.
  • Authentication: You are who you say you are. And messages sent by you can be verified by your chat partner (buddy) and vice versa.
  • Perfect Forward Secrecy: Each instant message sent is encrypted using a different encryption key which is discarded after use. Compromising a single encryption key does not impact on the confidentiality of other messages sent or those to be sent in the future. In addition, each message is authenticated using a different Message Authentication Code (MAC) key.
  • Deniability: The MAC keys that have already been used and will not be used again are included in outgoing messages. The idea is that since these keys are in the public domain any one could have created these keys (including your chat partner) and therefore forged a message.

In addition to the above, the OTR also defines a Socialist Millionaire Protocol (SMP) that could be used to detect a man-in-the-middle attack during an ongoing conversation. For the SMP process to successfully complete, you and your chat buddy must have a secret that is known to just you and him/her. The failure of the SMP process is an indication that the encrypted session between you and your chat partner has been hijacked by a third party.

Using the code

Let’s assume that Alice wants to establish an OTR session with her friend, Bob. In order to do this she has to request an OTR session from Bob and on receipt of this request Bob starts the OTR session proper. The code below shows how Alice goes about requesting an OTR session.

C#
 /* Declare OTR variables*/
OTRSessionManager _alice_otr_session_manager = null;

string _my_unique_id = "alice";
string _my_buddy_unique_id = "bob";


/* Create OTR session and Request OTR session */
_alice_otr_session_manager = new OTRSessionManager(_my_unique_id);

_alice_otr_session_manager.OnOTREvent += new OTREventHandler(OnAliceOTRMangerEventHandler); 

_alice_otr_session_manager.CreateOTRSession(_my_buddy_unique_id);

_alice_otr_session_manager.RequestOTRSession(_my_buddy_unique_id, OTRSessionManager.GetSupportedOTRVersionList()[0]);

Observe that the OTR session manager is initialized using your unique ID. As soon as the OTR manager is initialized, it must be connected to the OTR event handler. See the OTR Event section for a description of these event types.

Each OTR session established for the buddies you are communicating with must be created using the unique ID of that buddy. Once this is done, references to a session are achieved using this unique ID. In other words, your buddy’s unique ID doubles as the session ID.

Requesting an OTR session involves calling the RequestOTRSession function and passing it your buddy’s ID and the OTR version you wish to use. This Library supports versions 2 and 3 of the OTR protocol. The versions supported by this library are contained in a string list that can be accessed by calling the GetSupportedOTRVersionList static function of the OTRSessionManager.

OTR uses Digital Signature Algorithm (DSA) public keys as part of the authentication and SMP processes. If the client already has a DSA key, then the client can pass the DSA public and private key parameters i.e., P, Q, G and X to the CreateOTRSession function. The DSA key parameters must be formatted as a DSAKeyParams object before it is passed to the function in question. If the client doesn’t have a DSA key, a random one is created by this library. See the accompanying OTRLibTest console application for examples on how this is accomplished.

Your current DSA key can be gotten by calling GetSessionDSAHexParams function. It returns a DSAKeyParams object that contains the P, Q, G and X elements of the key.

The OTR specification defines an object called a Finger Print. The Finger Print is computed using the DSA public key parameters (i.e., P, Q, G and Y) as input. To retrieve your Finger Print in hexadecimal string call the GetSessionDSAFingerPrint function. Similarly, to retrieve the Finger Print of your buddy call the GetMyBuddyFingerPrint function.

C#
/* Pass received OTR session Data to OTR session manager */
/* Assume otr_message_string is the OTR message received from bob over the network */

_alice_otr_session_manager.ProcessOTRMessage(_my_buddy_unique_id, otr_message_string);

All OTR session string messages received by a client are passed to the ProcessOTRMessage function for further processing as shown above.

C#
/* Encrypt and send a message to Bob */
/* Assume that message_string is the message to be encrypted and sent to bob*/

_alice_otr_session_manager.EncryptMessage(_my_buddy_unique_id, message_string);

Messages to be encrypted are passed to the EncryptMessage function. If there are no errors in the encryption process then an OTR SEND event is triggered. See OTR Event section below for details of this event.

There are four more EncryptMessage functions that accept arguments that either allow for (1) the padding of encrypted data to hide the length of the message or (2) allow for indirect initiation of the SMP process or both (1) and (2) simultaneously. An associated function is the EncryptFragments function that allows for the encryption and fragmentation of large messages before transmission to a buddy. The buddy should be able to reassemble these message fragments and present the whole message to the user’s client.

OTR Events

There are 9 main events and 4 sub-events associated with the OTR manager. Note that the OTR manager can differentiate between events fired by multiple OTR sessions by simply reading the event’s GetSessionID function. The session ID string will correspond to the my_buddy_unique_id string passed to the CreateSession function. These events are described below;

ERROR Event: Such an event indicates that an error has occurred within an OTR session. This event can be fatal or benign. Once notified of an event that may appear fatal it is up to the IM interface to close the OTR session. An example of a fatal event is when a client is unable to verify the DSA public key of a buddy. In such an instance, it is advisable to close the OTR session. Closing the OTR session will notify the buddy of same. An instance of a non-fatal event is when a message cannot be decrypted. When this happens the client is notified and an error message is automatically sent to the buddy. Such an event does not require closing the OTR session.

When this event fires, the error messages can be read by calling the event’s GetErrorMessage and GetErrorVerbose functions.

Debug Event:This event outputs the messages that give an idea about what is happening internally within an OTR session e.g., types of messages received and sent. The debug message can be read by calling the event’s GetMessage function. Debug events can be enabled by setting the debug variable in the CreateOTRSession function to true.

MESSAGE Event: This event signals the successful decryption of a received OTR encrypted message. The decrypted message can be read by calling the event’s GetMessage function. Recall from the Background section that old MAC keys were said to be transmitted with outgoing messages. When this event fires, the old MAC keys (in bytes) can be retrieved by calling the GetOldMacKeys function of the event. Note that each MAC key is 20 bytes long. If the GetOldMacKeys function returns 40 bytes then you know there are two MAC keys contained within.

EXTRA_KEY_REQUEST Event: This event indicates that a buddy wants you both to use the extra AES symmetric for some purpose. The AES Key bytes can be retrieved by calling the GetExtraSymmetricKey Session function. You can request the use of the extra AES symmetric key by calling the RequestExtraKeyUse function . This event is only valid for Version 3 of the OTR protocol.

SEND Event: When an OTR session wants to send an OTR message to a buddy, it fires the SEND event. Data to be sent is retrieved by calling the event’s GetMessage function.

READY Event: The READY event signals the successful establishment of an encrypted OTR session between two IM clients. Note that no encrypted conversational data can be sent before this event fires. An attempt to encrypt and send a message before this event fires will trigger an error event. The current state of an OTR session can be gotten by calling the GetSessionMessageState function. If the OTR session is not in the MSG_STATE_ENCRYPTED state then it is not ready.

CLOSED Event: This event signifies the closing of an OTR session. This is usually in response to the user calling the CloseAllSessions or CloseSession functions. This event is also triggered by a close session message received from a buddy.

HEART_BEAT Event: This event indicates that a buddy wants to find out if you are still online and willing to communicate over an OTR encrypted session. This is usually sent when you’ve been idle for a while. You can respond by sending a message to your buddy or simply send a heartbeat of your own using the SendHeartBeat function.

SMP_MESSAGE Event: This event fires, as it were, in response to the completion of an SMP process. There are four sub-events associated with this event;

  • SUCCEEDED: The SMP process completed successfully i.e., the OTR session has not been hijacked.
  • FAILED: The SMP process was not successful i.e., the session may have been hijacked.
  • ABORT: An ongoing SMP process was aborted.
  • SEND: This event should never be received as it is only used internally within an OTR session.

To detect the fired SMP sub-event call the GetSMPEvent function of the main event.

<p>
The SMP process can be initiated directly by calling the <code>StartSMP
function or indirectly by setting the start_smp argument of any of the EncryptMessage message function to true. The shared user secret input for the SMP process can be set by passing the secret string to the SetSMPUserSecret function. The current shared secret can be retrieved by calling GetSMPUserSecret The default shared secret string is "Kittens are funny". :D


To force OTR to send SMP messages as fragments call the SetSMPFragLength function to set the maximum length of each fragment. To get the current fragment length for SMP messages call the GetSMPFragLength function.

To stop an ongoing SMP process call AbortSMP.

Point of Interest

The SMP process involves crunching of large integers (1536 bits) and as a result it can be slow. This process took an average of 27 seconds to complete when I ran it between a client interface implementing this library and a Pidgin instant messaging client [3] on a Windows 7 platform. This will have implications when attempting to run the SMP on a limited device e.g., smart phones etc. The implemented SMP code is contained in the SMPManager.cs file. If anyone can improve on this code to make it faster and share this improvement, then that would be appreciated.

The SMP execution speed has now been improved. It now runs under 7 seconds. I realised that I was using 1536 bytes instead of 192 bytes (i.e., 1536 bits) integer exponents stipulated by the recently updated specification [6] . Disregard the point of interest crossed out above :)


Mohamed Mansour has ported the library for use on the Windows 8/Phone platform. Find it here.


History

<ul>
<li>28/08/2013: First version. </li>
<li>18/09/2013: Added the <code>SetSMPFragLength
and GetSMPFragLength functions to the Library and updated the article to reflect same.
  • 20/09/2013: Improved the runtime of the SMP. See Point of Interest section.
  • 10/11/13: Corrected the point at which the extra AES key is computed in version 3.
  • 27/01/2014 : Updated the Point of Interest section with a link to the Windows 8/Phone port of the library.
  • 04/04/2014: A Mono build of library added.
  • 29/04/2014: Resolved the loss of key synchronization when one party does all the talking.
  • References

    1. OTR Version 2
    2. OTR Version 3
    3. Pidgin
    4. Spark
    5. OTR main page
    6. Updated Specification

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)