Skip to content

Commit

Permalink
Performance improvements (#76)
Browse files Browse the repository at this point in the history
* #66 Replaced Array with HashSet to improve performance for chunk boundary test

* #66 Keep and reuse ICryptoTransform instance when possible to avoid overhead of creation

* #66 Removed extra encryptor creation in Read method

* Don't specify fixed version to allow build process to set proper version

* Use specific Cake version (CodeCov requires 0.17)

* Don't specify fixed version to allow build process to set proper version (reverted from commit 8d444d1)

* Updated csproj to use GlobalAssemblyInfo version
  • Loading branch information
gpailler authored Sep 27, 2017
1 parent 4070ba7 commit ffd01e8
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 28 deletions.
24 changes: 18 additions & 6 deletions MegaApiClient/Cryptography/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ public static byte[] DecryptKey(byte[] data, byte[] key)
public static byte[] EncryptKey(byte[] data, byte[] key)
{
byte[] result = new byte[data.Length];

for (int idx = 0; idx < data.Length; idx += 16)
using (var encryptor = CreateAesEncryptor(key))
{
byte[] block = data.CopySubArray(16, idx);
byte[] encryptedBlock = EncryptAes(block, key);
Array.Copy(encryptedBlock, 0, result, idx, 16);
for (int idx = 0; idx < data.Length; idx += 16)
{
byte[] block = data.CopySubArray(16, idx);
byte[] encryptedBlock = EncryptAes(block, encryptor);
Array.Copy(encryptedBlock, 0, result, idx, 16);
}
}

return result;
Expand Down Expand Up @@ -77,9 +79,19 @@ public static byte[] DecryptAes(byte[] data, byte[] key)
}
}

public static ICryptoTransform CreateAesEncryptor(byte[] key)
{
return AesCbc.CreateEncryptor(key, DefaultIv);
}

public static byte[] EncryptAes(byte[] data, ICryptoTransform encryptor)
{
return encryptor.TransformFinalBlock(data, 0, data.Length);
}

public static byte[] EncryptAes(byte[] data, byte[] key)
{
using (ICryptoTransform encryptor = AesCbc.CreateEncryptor(key, DefaultIv))
using (ICryptoTransform encryptor = CreateAesEncryptor(key))
{
return encryptor.TransformFinalBlock(data, 0, data.Length);
}
Expand Down
7 changes: 5 additions & 2 deletions MegaApiClient/MegaApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -909,9 +909,12 @@ private static string GenerateHash(string email, byte[] passwordAesKey)
}

// Encrypt hash using password key
for (int it = 0; it < 16384; it++)
using (var encryptor = Crypto.CreateAesEncryptor(passwordAesKey))
{
hash = Crypto.EncryptAes(hash, passwordAesKey);
for (int it = 0; it < 16384; it++)
{
hash = Crypto.EncryptAes(hash, encryptor);
}
}

// Retrieve bytes 0-4 and 8-12 from the hash
Expand Down
4 changes: 4 additions & 0 deletions MegaApiClient/MegaApiClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<GenerateAssemblyCompanyAttribute>true</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>true</GenerateAssemblyProductAttribute>
<GenerateAssemblyCopyrightAttribute>true</GenerateAssemblyCopyrightAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -88,6 +91,7 @@

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<Compile Include="..\GlobalAssemblyInfo.cs" />
</ItemGroup>

</Project>
45 changes: 25 additions & 20 deletions MegaApiClient/Stream/MegaAesCtrStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;

internal class MegaAesCtrStreamCrypter : MegaAesCtrStream
{
Expand Down Expand Up @@ -70,8 +71,9 @@ internal abstract class MegaAesCtrStream : Stream

private readonly Stream stream;
private readonly Mode mode;
private readonly long[] chunksPositions;
private readonly HashSet<long> chunksPositionsCache;
private readonly byte[] counter = new byte[8];
private readonly ICryptoTransform encryptor;
private long currentCounter = 0;
private byte[] currentChunkMac = new byte[16];
private byte[] fileMac = new byte[16];
Expand Down Expand Up @@ -99,7 +101,16 @@ protected MegaAesCtrStream(Stream stream, long streamLength, Mode mode, byte[] f
this.fileKey = fileKey;
this.iv = iv;

this.chunksPositions = this.GetChunksPositions(this.streamLength);
this.ChunksPositions = this.GetChunksPositions(this.streamLength).ToArray();
this.chunksPositionsCache = new HashSet<long>(this.ChunksPositions);

this.encryptor = Crypto.CreateAesEncryptor(this.fileKey);
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.encryptor.Dispose();
}

protected enum Mode
Expand All @@ -108,10 +119,7 @@ protected enum Mode
Decrypt
}

public long[] ChunksPositions
{
get { return this.chunksPositions; }
}
public long[] ChunksPositions { get; }

public override bool CanRead
{
Expand Down Expand Up @@ -159,12 +167,12 @@ public override int Read(byte[] buffer, int offset, int count)
for (long pos = this.position; pos < Math.Min(this.position + count, this.streamLength); pos += 16)
{
// We are on a chunk bondary
if (this.chunksPositions.Any(chunk => chunk == pos))
if (this.chunksPositionsCache.Contains(pos))
{
if (pos != 0)
{
// Compute the current chunk mac data on each chunk bondary
this.ComputeChunk();
this.ComputeChunk(encryptor);
}

// Init chunk mac with Iv values
Expand Down Expand Up @@ -192,7 +200,7 @@ public override int Read(byte[] buffer, int offset, int count)
Array.Copy(this.iv, ivCounter, 8);
Array.Copy(this.counter, 0, ivCounter, 8, 8);

byte[] encryptedIvCounter = Crypto.EncryptAes(ivCounter, this.fileKey);
byte[] encryptedIvCounter = Crypto.EncryptAes(ivCounter, encryptor);

for (int inputPos = 0; inputPos < inputLength; inputPos++)
{
Expand All @@ -204,7 +212,7 @@ public override int Read(byte[] buffer, int offset, int count)
Array.Copy(output, 0, buffer, (int)(offset + pos - this.position), (int)Math.Min(output.Length, this.streamLength - pos));

// Crypt to current chunk mac
this.currentChunkMac = Crypto.EncryptAes(this.currentChunkMac, this.fileKey);
this.currentChunkMac = Crypto.EncryptAes(this.currentChunkMac, encryptor);
}

long len = Math.Min(count, this.streamLength - this.position);
Expand All @@ -213,7 +221,7 @@ public override int Read(byte[] buffer, int offset, int count)
// When stream is fully processed, we compute the last chunk
if (this.position == this.streamLength)
{
this.ComputeChunk();
this.ComputeChunk(encryptor);

// Compute Meta MAC
for (int i = 0; i < 4; i++)
Expand Down Expand Up @@ -263,35 +271,32 @@ private void IncrementCounter()
Array.Copy(counter, this.counter, 8);
}

private void ComputeChunk()
private void ComputeChunk(ICryptoTransform encryptor)
{
for (int i = 0; i < 16; i++)
{
this.fileMac[i] ^= this.currentChunkMac[i];
}

this.fileMac = Crypto.EncryptAes(this.fileMac, this.fileKey);
this.fileMac = Crypto.EncryptAes(this.fileMac, encryptor);
}

private long[] GetChunksPositions(long size)
private IEnumerable<long> GetChunksPositions(long size)
{
List<long> chunks = new List<long>();
chunks.Add(0);
yield return 0;

long chunkStartPosition = 0;
for (int idx = 1; (idx <= 8) && (chunkStartPosition < (size - (idx * 131072))); idx++)
{
chunkStartPosition += idx * 131072;
chunks.Add(chunkStartPosition);
yield return chunkStartPosition;
}

while ((chunkStartPosition + 1048576) < size)
{
chunkStartPosition += 1048576;
chunks.Add(chunkStartPosition);
yield return chunkStartPosition;
}

return chunks.ToArray();
}
}
}
4 changes: 4 additions & 0 deletions tools/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Cake" version="0.17.0" />
</packages>

0 comments on commit ffd01e8

Please sign in to comment.