Skip to main content

Switching between MTE Core and MKE Samples

Purpose

Samples expand on our "Examples" by providing longer snippets of code demonstrating the entire processes. The objective of the samples below is to demonstrate how to use the MTE state to switch between the MTE Core and MKE. Most applications have transmissions that are very short and very lengthy, in this case it would be beneficial to switch between the MTE core and MKE to minimize message length while optimizing the application's security.

The MTE software allows you to use the same MTE state for the MTE Core, FLEN and MKE as long as each is initialized using the same options. A single client can switch between these based on the size of the message or other environmental needs. Below is an example that uses the MTE Core to transmit the login information and then uses the MKE to upload a file.

IMPORTANT NOTE

Each side must use the same MTE "type" in a single transmission in order to decode and encode the messages, for example if the message is encoded using the MTE Core it must be decoded using the MTE Core.

The code samples for each section show the client side using a console application, the operations on the server side are almost identical. Each section breaks out portions of the code, at the bottom of this page the full program files are also available for the client as well as the server.

Initial Pairing Handshake

Our example pairs the MTE client using an initial handshake that uses the Diffie-Hellman key exchange to determine the entropy. The conversationID for the personalization string is generated on the client and sent to the server and the server creates and sets the nonce and sends it back to the client as part of the handshaking process. The MTE is also created and the state is saved.

/// <summary>Handshakes the with server.</summary>
/// <param name="clientId">The client identifier.</param>
/// <returns>ResponseModel&lt;HandshakeResponse&gt;.</returns>
public ResponseModel<HandshakeResponse> HandshakeWithServer(string clientId)
{
ResponseModel<HandshakeResponse> response =
new ResponseModel<HandshakeResponse>
{ Data = new HandshakeResponse() };
try
{
//--------------------------------
// Create clientId for this client
//--------------------------------
HandshakeModel handshake =
new HandshakeModel { ConversationIdentifier = clientId };

//-------------------------------------------
// Create Eclypses DH containers for handshake
//-------------------------------------------
EclypsesECDH encoderEcdh = new EclypsesECDH();
EclypsesECDH decoderEcdh = new EclypsesECDH();

//-------------------------------------------
// Get the public key to send to other side
//-------------------------------------------
handshake.ClientEncoderPublicKey =
encoderEcdh.GetPublicKey(encoderEcdh.GetTheContainer());
handshake.ClientDecoderPublicKey =
decoderEcdh.GetPublicKey(decoderEcdh.GetTheContainer());

//-------------------
// Perform handshake
//-------------------
string handshakeResponse =
MakeHttpCall($"{Constants.RestAPIName}/api/handshake",
HttpMethod.Post,
handshake.ConversationIdentifier,
Constants.JsonContentType,
JsonSerializer.Serialize(handshake, Constants.JsonOptions)).Result;

//---------------------------------------
// Deserialize the result from handshake
//---------------------------------------
ResponseModel<HandshakeModel> serverResponse =
JsonSerializer.Deserialize<ResponseModel<HandshakeModel>>
(handshakeResponse, Constants.JsonOptions);

//---------------------------------------
// If handshake was not successful, break
//---------------------------------------
if (!serverResponse.Success)
{
response.Success = serverResponse.Success;
response.Message = serverResponse.Message;
response.ResultCode = serverResponse.ResultCode;
Console.WriteLine($"Error making DH handshake for Client " +
"{clientId}: {serverResponse.Message}");
return response;
}

//----------------------
// Create shared secret
//----------------------
var encoderSharedSecretModel =
encoderEcdh.ProcessPartnerPublicKey(serverResponse.Data.ClientEncoderPublicKey);
var decoderSharedSecretModel =
decoderEcdh.ProcessPartnerPublicKey(serverResponse.Data.ClientDecoderPublicKey);

//--------------------------------
// Set MTE settings and get state
//--------------------------------
if (!ulong.TryParse(serverResponse.Data.Timestamp, out ulong nonce))
{
response.Success = false;
response.Message = $"Nonce is not valid ulong: {serverResponse.Data.Timestamp}.";
response.ResultCode = Constants.RC_INVALID_NONCE;
return response;
}
//----------------------------
// Set Encoder and save state
//----------------------------
MteMkeEnc encoder = new MteMkeEnc();
encoder.SetEntropy(encoderSharedSecretModel.SharedSecret);
encoder.SetNonce(nonce);
MteStatus status = encoder.Instantiate(handshake.ConversationIdentifier);
if (status != MteStatus.mte_status_success)
{
response.Success = false;
response.Message = $"Failed to initialize the MTE Encoder engine. Status: " +
"{encoder.GetStatusName(status)} / " +
"{encoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_STATE_CREATION;
return response;
}
response.Data.EncoderState = encoder.SaveStateB64();

//----------------------------
// Set Decoder and save state
//----------------------------
MteMkeDec decoder = new MteMkeDec();
decoder.SetEntropy(decoderSharedSecretModel.SharedSecret);
decoder.SetNonce(nonce);
status = decoder.Instantiate(handshake.ConversationIdentifier);
if (status != MteStatus.mte_status_success)
{
response.Success = false;
response.Message = $"Failed to initialize the MTE Decoder engine. Status: "+
"{decoder.GetStatusName(status)} / " +
"{decoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_STATE_CREATION;
return response;
}
response.Data.DecoderState = decoder.SaveStateB64();
}
catch (Exception ex)
{
response.Message = $"Exception handshaking with server. Ex: {ex.Message}";
response.ResultCode = Constants.RC_HANDSHAKE_EXCEPTION;
response.Success = false;
}
return response;
}

Using MTE Core to Login

Next we take the MTE state, decrypt it and restore it to an MTE Core instance. Using this instance we encode a serialized string of the login. Following that, save and encrypt the MTE state. We do the same with the decoder state to decode the login response if successful.

/// <summary>
/// Login to the server.
/// </summary>
/// <param name="clientId">The clientId.</param>
/// <param name="encoderState">The current encoderState.</param>
/// <param name="decoderState">The current decoderState.</param>
public ResponseModel<LoginResposne> LoginToServer(string clientId,
string encoderState,
string decoderState)
{
ResponseModel<LoginResposne> response = new ResponseModel<LoginResposne>
{ Data = new LoginResposne
{ EncoderState = encoderState, DecoderState = decoderState }
};
try
{
//-----------------------------------
// Login first before uploading file
// Use regular MTE
//-----------------------------------
string loginRoute = "api/login";
LoginModel login = new LoginModel
{ Password = "P@ssw0rd!", UserName = "email@eclypses.com" };

//---------------------------------
// Serialize login model and encode
//---------------------------------
string serializedLogin = JsonSerializer.Serialize(login);

//----------------------------------
// Encode outgoing message with MTE
//----------------------------------
MteEnc enc = new MteEnc();
MteStatus encoderStatus = enc.RestoreStateB64(encoderState);
if (encoderStatus != MteStatus.mte_status_success)
{
response.Message = $"Failed to restore MTE Encoder engine. Status: "
+ "{enc.GetStatusName(encoderStatus)} / "
+ "{enc.GetStatusDescription(encoderStatus)}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
return response;
}
string encodeResult = enc.EncodeB64(serializedLogin);

//---------------------------
// Save updated Encoder State
//---------------------------
response.Data.EncoderState = enc.SaveStateB64();

//-------------------
// Perform Login
//-------------------
string loginResponse =
MakeHttpCall($"{Constants.RestAPIName}/api/login", HttpMethod.Post, clientId,
Constants.TextContentType, encodeResult).Result;

//---------------------------------------
// Deserialize the result from login
//---------------------------------------
ResponseModel<string> serverResponse =
JsonSerializer.Deserialize<ResponseModel<string>>
(loginResponse, Constants.JsonOptions);

//--------------
// If error end
//--------------
if (!serverResponse.Success)
{
response.Message = serverResponse.Message;
response.Success = serverResponse.Success;
response.ExceptionUid = serverResponse.ExceptionUid;
response.ResultCode = serverResponse.ResultCode;
return response;
}

//----------------------
// Set jwt/access_token
//----------------------
response.access_token = serverResponse.access_token;

//---------------------------------------------
// Decode the response and resave Decoder State
//---------------------------------------------
MteDec dec = new MteDec();
MteStatus decoderStatus = dec.RestoreStateB64(decoderState);
if (decoderStatus != MteStatus.mte_status_success)
{
response.Message = $"Failed to restore MTE Decoder engine. Status: "
+ "{dec.GetStatusName(decoderStatus)} / "
+ "{dec.GetStatusDescription(decoderStatus)}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
return response;
}
var decodedResult = dec.DecodeStrB64(serverResponse.Data);
Console.WriteLine($"Login response: {decodedResult}");
response.Data.DecoderState = dec.SaveStateB64();
response.Data.LoginMessage = decodedResult;

}
catch (Exception ex)
{
response.Message = $"Exception during login: {ex.Message}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_LOGIN_EXCEPTION;
}
return response;
}

Using MKE To Upload File to Server

Finally we take the same MTE state, decrypt it and restore it to an MTE MKE instance. Using this instance we chunk encode a file to upload to the server. Following that, we save and encrypt the MTE state once again.

#region Send
/// <summary>
/// Sends the file up to the server
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="useMte">Whether or not to use MTE when sending file.</param>
/// <param name="encoderState">The current encoder state.</param>
/// <param name="decoderState">The current decoder state.</param>
/// <param name="clientid">The client id.</param>
/// <param name="authHeader">The JWT auth header.</param>
/// <returns>System.Int32.</returns>
public ResponseModel<UploadResponse> Send(string path,
bool useMte,
string encoderState,
string decoderState,
string clientid,
string authHeader)
{
ResponseModel<UploadResponse> uploadResponse = new()
{
Data = new UploadResponse
{
EncoderState = encoderState,
DecoderState = decoderState
}
};
try
{
//------------------------
// Create default encoder
//------------------------
MteMkeEnc encoder = new MteMkeEnc();

//------------------------------
// Get file info and create url
//------------------------------
FileInfo file = new FileInfo(path);
string urlType = (useMte) ? "mte" : "nomte";
string fileUrl = Path.Combine($"{Constants.RestAPIName}/FileUpload/",
urlType + "?name=" + file.Name);

//-----------------------------------
// Create file stream and webRequest
//-----------------------------------
_fileReader = new FileStream(path, FileMode.Open, FileAccess.Read);
_webRequest = (HttpWebRequest)WebRequest.Create(fileUrl);
_webRequest.Method = "POST";
if (useMte)
{
//--------------------------------------------------
// If we are using the MTE adjust the content length
//--------------------------------------------------
int additionalBytes = encoder.EncryptFinishBytes();
long finalLength = (long)(_fileReader.Length + additionalBytes);
_webRequest.ContentLength = finalLength;
}
else
{
//-------------------------------------------------------------
// Regular request will have the file length as content length
//-------------------------------------------------------------
_webRequest.ContentLength = _fileReader.Length;
}
_webRequest.Timeout = 600000;

//-------------------------
// Add client id to header if not null
//-------------------------
if (!string.IsNullOrWhiteSpace(clientid))
{
_webRequest.Headers.Add(Constants.ClientIdHeader, clientid);
}

if (!string.IsNullOrWhiteSpace(authHeader))
{
if (authHeader.StartsWith("Bearer"))
authHeader = authHeader.Substring("Bearer ".Length);

_webRequest.Headers.Add("Authorization", "Bearer " + authHeader);
}

_webRequest.Credentials = CredentialCache.DefaultCredentials;
_webRequest.AllowWriteStreamBuffering = false;
_requestStream = _webRequest.GetRequestStream();

long fileSize = _fileReader.Length;
long remainingBytes = fileSize;
int numberOfBytesRead = 0, done = 0;


if (useMte)
{
//----------------------------
// Restore encoder from state
//----------------------------
MteStatus status = encoder.RestoreStateB64(encoderState);
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
uploadResponse.Message = $"Failed to restore MTE encoder engine. Status: "
+ " {encoder.GetStatusName(status)} / {encoder.GetStatusDescription(status)}";
return uploadResponse;
}

//----------------------------
// start the chunking session
//----------------------------
status = encoder.StartEncrypt();
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to start encode chunk. Status: "
+ encoder.GetStatusName(status) + " / "
+ encoder.GetStatusDescription(status);
return uploadResponse;
}
}

//------------------------
// Break up files to send
//------------------------
while (numberOfBytesRead < fileSize)
{
byte[] fileData;
SetByteArray(out fileData, remainingBytes);
done = _fileReader.Read(fileData, 0, fileData.Length);
if (useMte)
{
//------------------------------------------------------------
// Encode the data in place - encoded data put back in buffer
//------------------------------------------------------------
MteStatus chunkStatus = encoder.EncryptChunk(fileData, 0, fileData.Length);
if (chunkStatus != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to encode chunk. Status: "
+ encoder.GetStatusName(chunkStatus) + " / "
+ encoder.GetStatusDescription(chunkStatus);
return uploadResponse;
}
}
//-----------------------------
// Write the data to the stream
//-----------------------------
_requestStream.Write(fileData, 0, fileData.Length);
numberOfBytesRead += done;
remainingBytes -= done;
}

if (useMte)
{
//----------------------------
// Finish the chunking session
//----------------------------
byte[] finalEncodedChunk = encoder.FinishEncrypt(out MteStatus finishStatus);
if (finishStatus != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to finish encode chunk. Status: "
+ encoder.GetStatusName(finishStatus) + " / "
+ encoder.GetStatusDescription(finishStatus);
return uploadResponse;
}

//------------------------------------
// Append the final data to the stream
//------------------------------------
_requestStream.Write(finalEncodedChunk, 0, finalEncodedChunk.Length);

//-----------------------
// Save the encoderState
//-----------------------
uploadResponse.Data.EncoderState = encoder.SaveStateB64();

}

//------------------
// Get the response.
//------------------
WebResponse response = _webRequest.GetResponse();

//---------------------
// get the return text
//---------------------
using Stream data = response.GetResponseStream();
using var reader = new StreamReader(data);
string text = reader.ReadToEnd();

ResponseModel<byte[]> textResponse =
JsonSerializer.Deserialize<ResponseModel<byte[]>>(text,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (!textResponse.Success)
{
//-----------------------------------
// Check if we need to "re-handshake"
//-----------------------------------
if (textResponse.ResultCode.Equals(Constants.RC_MTE_STATE_NOT_FOUND,
StringComparison.InvariantCultureIgnoreCase))
{
//-------------------------------------------------------------------------
// the server does not have this client's state - we should "re-handshake"
//-------------------------------------------------------------------------
var handshakeRessponse = HandshakeWithServer(clientid);

//-----------------------------------------------------------
// return response, if successful give message to try again.
//-----------------------------------------------------------
uploadResponse.Success = handshakeRessponse.Success;
uploadResponse.Message = (uploadResponse.Success)
? "Server lost MTE state, client needed to handshake "
+ "again, handshake successful, please try again."
: handshakeRessponse.Message;
uploadResponse.ResultCode = handshakeRessponse.ResultCode;
uploadResponse.access_token = handshakeRessponse.access_token;
return uploadResponse;
}
}

if (useMte)
{
//------------------
// Restroe decoder
//------------------
MteMkeDec decoder = new MteMkeDec();
MteStatus status = decoder.RestoreStateB64(decoderState);
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
uploadResponse.Message = $"Failed to restore the MTE decoder engine. Status:"
+ " {decoder.GetStatusName(status)} / {decoder.GetStatusDescription(status)}";
return uploadResponse;
}

//----------------------------
// start the chunking session
//----------------------------
status = decoder.StartDecrypt();
if (status != MteStatus.mte_status_success)
{
throw new Exception("Failed to start decode chunk. Status: "
+ decoder.GetStatusName(status) + " / "
+ decoder.GetStatusDescription(status));
}
//-----------------
// Decode the data
//-----------------
byte[] decodedChunk = decoder.DecryptChunk(textResponse.Data);
var clearBytes = decoder.FinishDecrypt(out MteStatus finalStatus);
if (clearBytes == null) { clearBytes = new byte[0]; }
if (finalStatus != MteStatus.mte_status_success)
{
throw new Exception("Failed to finish decode. Status: "
+ decoder.GetStatusName(finalStatus) + " / "
+ decoder.GetStatusDescription(finalStatus));
}
//---------------------
// Set decoded message
//---------------------
byte[] decodedMessage = new byte[decodedChunk.Length + clearBytes.Length];
Buffer.BlockCopy(decodedChunk, 0, decodedMessage, 0, decodedChunk.Length);
Buffer.BlockCopy(clearBytes, 0, decodedMessage, decodedChunk.Length, clearBytes.Length);

//----------------------------
// Return the server response
//----------------------------
uploadResponse.Data.ServerResponse = Encoding.UTF8.GetString(decodedMessage);

//------------------------
// Save the decoder state
//------------------------
uploadResponse.Data.DecoderState = decoder.SaveStateB64();
}
else
{
//----------------------------
// Return the server response
//----------------------------
uploadResponse.Data.ServerResponse = Encoding.UTF8.GetString(textResponse.Data);
}

//-----------------------------
// update the jwt/access_token
//-----------------------------
uploadResponse.access_token = textResponse.access_token;

}
catch (Exception e)
{
uploadResponse.Message = $"Exception uploading file to server. Ex: {e.Message}";
uploadResponse.ResultCode = Constants.RC_UPLOAD_EXCEPTION;
uploadResponse.Success = false;
}
return uploadResponse;
}
#endregion

#region SetByteArray
/// <summary>
/// Sets the byte array.
/// </summary>
/// <param name="fileData">The file data.</param>
/// <param name="bytesLeft">The bytes left.</param>
private void SetByteArray(out byte[] fileData, long bytesLeft)
{
fileData = bytesLeft < _maxChunkSize ? new byte[bytesLeft] : new byte[_maxChunkSize];
}
#endregion

Full Client Side Sample

Here is the entire console side application code.

Full C# Sample

Full Java Sample

Full Swift Sample

Full Python Sample

Full Go Sample

Full Server Side API

The server side API is only written in CSharp and not in all the other languages. All of the client side console applications can be successfully run against the Server Side API. The server includes other projects as well.

Full C# WebAPI Sample