Skip to content

Encrypt and Send Files in WebRTC

Jacob Steele Sep 21, 2023 2:22:24 PM

In this blog post, we will explore an extension to the previous example in the Sending Files with DataChannels (C#) blog post and learn how to encrypt a file before sending it using data streams and LiveSwitch in WebRTC. By implementing additional encryption measures, we can ensure enhanced security for file transfers, even beyond LiveSwitch's built-in DTLS encryption. Follow along to discover how to encrypt and send files securely.

 

Code

To achieve enhanced security, we employ a unique Initialization Vector (IV) for each data buffer, effectively randomizing and attaching it to the sent data. Here's the implementation:

using System.Security.Cryptography;

using FM.LiveSwitch;


byte[] key = new byte[16] { 12, 44, 22, 66, 44, 88, 43, 82, 11, 01, 54, 76, 43, 11, 23, 78 };

Func<byte[], byte[]> Decrypt = (byte[] toEncrypt) =>
{
  using (Aes aesAlg = Aes.Create())
  {
    aesAlg.Padding = PaddingMode.Zeros;
    byte[] iv = new byte[16];
    byte[] header = new byte[10];
    MemoryStream ms = new MemoryStream(toEncrypt);
    ms.Read(iv, 0, iv.Length);
    ICryptoTransform decryptor = aesAlg.CreateDecryptor(key, iv);
    using (ms)
    {
      using (CryptoStream csDecrypt = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
      {
        var decrypted = new byte[toEncrypt.Length - iv.Length];
        var decryptedCount = csDecrypt.Read(decrypted, 0, decrypted.Length);
        return decrypted;
      }
    }
  }
};

Func<byte[], byte[]> Encrypt = (byte[] toEncrypt) =>
{
  using (Aes aesAlg = Aes.Create())
  {
    aesAlg.Padding = PaddingMode.Zeros;
    byte[] IV = new byte[16];
    Random.Shared.NextBytes(IV);
    ICryptoTransform encryptor = aesAlg.CreateEncryptor(key, IV);
    using (MemoryStream msEncrypt = new MemoryStream())
    {
      msEncrypt.Write(IV);
      using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
      {
        // Write the data to encrypt into the encryption stream (minus the 10 byte header of Vp8)
        csEncrypt.Write(toEncrypt);
        csEncrypt.FlushFinalBlock();
        var encryptedArray = msEncrypt.ToArray();

        return encryptedArray;
      }
    }
  }
};


var client1 = new Client("https://cloud.liveswitch.io/", "<APPID>");
var client2 = new Client("https://cloud.liveswitch.io/", "<APPID>");

var token1 = Token.GenerateClientRegisterToken(client1, new ChannelClaim[] { new ChannelClaim("byteDataTransferTest") }, "<SHAREDSECRET>");
var token2 = Token.GenerateClientRegisterToken(client2, new ChannelClaim[] { new ChannelClaim("byteDataTransferTest") }, "<SHAREDSECRET>");


var channels = await client1.Register(token1);
var channel1 = channels[0];
channels = await client2.Register(token2);
var channel2 = channels[0];

var dataChannel1 = new DataChannel("test");
var dataChannel2 = new DataChannel("test");

var dataStream1 = new DataStream(dataChannel1);
var dataStream2 = new DataStream(dataChannel2);

var upConnection = channel1.CreateSfuUpstreamConnection(dataStream1, "mediaId");
var downConnection = channel2.CreateSfuDownstreamConnection("mediaId", dataStream2);

bool hasWrittenFile = false;
Func<bool> selector = () =>
{
  while (!hasWrittenFile)
  {
    Thread.Sleep(1000);
  }
  var txt = File.ReadAllText("Received.txt");
  Console.WriteLine("File Written.");
  Console.WriteLine(txt);
  return true;
};
var task = new Task<bool>(selector);

dataChannel1.OnStateChange += (DataChannel dc) =>
{
  if (dc.State == DataChannelState.Connected)
  {
    var file = File.ReadAllBytes("SendMe.txt");
    var send = Encrypt(file);
    var wrapper = DataBuffer.Wrap(send);
    dc.SendDataBytes(wrapper);
  };
};

dataChannel2.OnReceive += (DataChannelReceiveArgs args) =>
{
  if (args.DataBytes != null)
  {
    var recv = args.DataBytes.ToArray();
    var decrypt = Decrypt(recv);
    File.WriteAllBytes("Received.txt", decrypt);
    hasWrittenFile = true;
  };
};

await downConnection.Open();
await upConnection.Open();

task.Start();
Task.WaitAny(new Task[] { task });

To encrypt a file before sending it, we read the file content and apply the Encrypt function, which encrypts the file data using AES encryption and the randomly generated IV. The encrypted data is then sent over the data channel using a DataBuffer.

On the receiving side, the data channel's OnReceive event is triggered when data is received. We extract the received data, apply the Decrypt function to retrieve the original file content and save it to disk. Once the file is successfully written, the process is completed.

By implementing file encryption within WebRTC using AES encryption and unique IVs per frame, you can enhance the security of file transfers. This approach provides an additional layer of protection for sensitive data beyond LiveSwitch's default encryption. Securely encrypt and send files, ensuring only authorized recipients can access the decrypted content.

 

Need assistance in architecting the perfect WebRTC application? Let our team help out! Get in touch with us today!