From 630c4f70070c16853280d20091b93d201062fc8c Mon Sep 17 00:00:00 2001 From: Inga Lovinde <52715130+inga-lovinde@users.noreply.github.com> Date: Fri, 14 Jul 2017 17:32:37 +0300 Subject: [PATCH] Encryptor.ClearText and Hasher.SHA1 implemented --- EternalArrowBackup.sln | 30 ++++++++ .../ClearTextContentEncryptor.cs | 70 +++++++++++++++++++ ...rnalArrowBackup.Encryptor.ClearText.csproj | 11 +++ .../EternalArrowBackup.Hasher.SHA1.csproj | 11 +++ Source/Hasher.SHA1/SHA1ContentHasher.cs | 21 ++++++ .../EncryptorAndHasherTests.cs | 35 ++++++++++ ...rowBackup.Encryptor.ClearText.Tests.csproj | 22 ++++++ ...ternalArrowBackup.Hasher.SHA1.Tests.csproj | 21 ++++++ Tests/Hasher.SHA1/HasherTests.cs | 51 ++++++++++++++ 9 files changed, 272 insertions(+) create mode 100644 Source/Encryptor.ClearText/ClearTextContentEncryptor.cs create mode 100644 Source/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.csproj create mode 100644 Source/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.csproj create mode 100644 Source/Hasher.SHA1/SHA1ContentHasher.cs create mode 100644 Tests/Encryptor.ClearText/EncryptorAndHasherTests.cs create mode 100644 Tests/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.Tests.csproj create mode 100644 Tests/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.Tests.csproj create mode 100644 Tests/Hasher.SHA1/HasherTests.cs diff --git a/EternalArrowBackup.sln b/EternalArrowBackup.sln index 492fbf7..0bd8b34 100644 --- a/EternalArrowBackup.sln +++ b/EternalArrowBackup.sln @@ -7,6 +7,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{A79A48 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EternalArrowBackup.Contracts", "Source\Contracts\EternalArrowBackup.Contracts.csproj", "{9A363462-23E2-49FB-A65C-7B1CA1400046}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{884B8E01-303A-40CF-8884-D62115F98683}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EternalArrowBackup.Hasher.SHA1", "Source\Hasher.SHA1\EternalArrowBackup.Hasher.SHA1.csproj", "{623151DA-A4E0-4A7F-B600-A51ABCDE7038}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EternalArrowBackup.Encryptor.ClearText", "Source\Encryptor.ClearText\EternalArrowBackup.Encryptor.ClearText.csproj", "{E8D0BBF5-8DA6-49FA-AC86-9F766C203D99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EternalArrowBackup.Hasher.SHA1.Tests", "Tests\Hasher.SHA1\EternalArrowBackup.Hasher.SHA1.Tests.csproj", "{92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EternalArrowBackup.Encryptor.ClearText.Tests", "Tests\Encryptor.ClearText\EternalArrowBackup.Encryptor.ClearText.Tests.csproj", "{0F91FC45-54B3-4BC0-BB3D-83E963EBD00D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,11 +27,31 @@ Global {9A363462-23E2-49FB-A65C-7B1CA1400046}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A363462-23E2-49FB-A65C-7B1CA1400046}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A363462-23E2-49FB-A65C-7B1CA1400046}.Release|Any CPU.Build.0 = Release|Any CPU + {623151DA-A4E0-4A7F-B600-A51ABCDE7038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {623151DA-A4E0-4A7F-B600-A51ABCDE7038}.Debug|Any CPU.Build.0 = Debug|Any CPU + {623151DA-A4E0-4A7F-B600-A51ABCDE7038}.Release|Any CPU.ActiveCfg = Release|Any CPU + {623151DA-A4E0-4A7F-B600-A51ABCDE7038}.Release|Any CPU.Build.0 = Release|Any CPU + {E8D0BBF5-8DA6-49FA-AC86-9F766C203D99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8D0BBF5-8DA6-49FA-AC86-9F766C203D99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8D0BBF5-8DA6-49FA-AC86-9F766C203D99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8D0BBF5-8DA6-49FA-AC86-9F766C203D99}.Release|Any CPU.Build.0 = Release|Any CPU + {92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC}.Release|Any CPU.Build.0 = Release|Any CPU + {0F91FC45-54B3-4BC0-BB3D-83E963EBD00D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F91FC45-54B3-4BC0-BB3D-83E963EBD00D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F91FC45-54B3-4BC0-BB3D-83E963EBD00D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F91FC45-54B3-4BC0-BB3D-83E963EBD00D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {9A363462-23E2-49FB-A65C-7B1CA1400046} = {A79A4872-4289-4429-B616-3E8CEC71ECB8} + {623151DA-A4E0-4A7F-B600-A51ABCDE7038} = {A79A4872-4289-4429-B616-3E8CEC71ECB8} + {E8D0BBF5-8DA6-49FA-AC86-9F766C203D99} = {A79A4872-4289-4429-B616-3E8CEC71ECB8} + {92AF4CAA-64AF-4A43-A637-CD10FFEB2FEC} = {884B8E01-303A-40CF-8884-D62115F98683} + {0F91FC45-54B3-4BC0-BB3D-83E963EBD00D} = {884B8E01-303A-40CF-8884-D62115F98683} EndGlobalSection EndGlobal diff --git a/Source/Encryptor.ClearText/ClearTextContentEncryptor.cs b/Source/Encryptor.ClearText/ClearTextContentEncryptor.cs new file mode 100644 index 0000000..2c2cb35 --- /dev/null +++ b/Source/Encryptor.ClearText/ClearTextContentEncryptor.cs @@ -0,0 +1,70 @@ +namespace EternalArrowBackup.Encryptor.ClearText +{ + using System; + using System.Text; + using System.Threading.Tasks; + using Contracts.ContentTransformations; + + public class ClearTextContentEncryptor : IContentEncryptor + { + public ClearTextContentEncryptor(IContentHasher hasher) + { + this.Hasher = hasher; + } + + private IContentHasher Hasher { get; } + + public async Task Decrypt(byte[] encryptedData) + { + var hashLength = encryptedData[encryptedData.Length - 1]; + + var originalData = new byte[encryptedData.Length - hashLength - 1]; + var hashBytes = new byte[hashLength]; + + Buffer.BlockCopy(encryptedData, 0, originalData, 0, originalData.Length); + Buffer.BlockCopy(encryptedData, originalData.Length, hashBytes, 0, hashBytes.Length); + + var expectedHash = Encoding.UTF8.GetString(hashBytes); + var actualHash = await this.Hasher.ComputeHash(originalData); + + if (expectedHash != actualHash) + { + return new FailedDecryptionResult(); + } + + return new SuccessfulDecryptionResult(originalData); + } + + public async Task Encrypt(byte[] originalData) + { + var hash = await this.Hasher.ComputeHash(originalData); + var hashBytes = Encoding.UTF8.GetBytes(hash); + if (hashBytes.Length >= 256) + { + throw new Exception("Hash should be shorter than 256 bytes"); + } + + var result = new byte[originalData.Length + hashBytes.Length + 1]; + Buffer.BlockCopy(originalData, 0, result, 0, originalData.Length); + Buffer.BlockCopy(hashBytes, 0, result, originalData.Length, hashBytes.Length); + result[result.Length - 1] = (byte)hashBytes.Length; + return result; + } + + private class FailedDecryptionResult : IDecryptionResult + { + public bool IsSuccessful => false; + + public byte[] Data => throw new NotImplementedException(); + } + + private class SuccessfulDecryptionResult : IDecryptionResult + { + public SuccessfulDecryptionResult(byte[] data) => this.Data = data; + + public bool IsSuccessful => true; + + public byte[] Data { get; } + } + } +} diff --git a/Source/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.csproj b/Source/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.csproj new file mode 100644 index 0000000..73bbdde --- /dev/null +++ b/Source/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.csproj @@ -0,0 +1,11 @@ + + + + netstandard1.4 + + + + + + + \ No newline at end of file diff --git a/Source/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.csproj b/Source/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.csproj new file mode 100644 index 0000000..73bbdde --- /dev/null +++ b/Source/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.csproj @@ -0,0 +1,11 @@ + + + + netstandard1.4 + + + + + + + \ No newline at end of file diff --git a/Source/Hasher.SHA1/SHA1ContentHasher.cs b/Source/Hasher.SHA1/SHA1ContentHasher.cs new file mode 100644 index 0000000..f21ac69 --- /dev/null +++ b/Source/Hasher.SHA1/SHA1ContentHasher.cs @@ -0,0 +1,21 @@ +namespace EternalArrowBackup.Hasher.SHA1 +{ + using System.Linq; + using System.Threading.Tasks; + using Contracts.ContentTransformations; + + public class SHA1ContentHasher : IContentHasher + { + public Task ComputeHash(byte[] content) + { + return Task.Run(() => + { + using (var sha1 = System.Security.Cryptography.SHA1.Create()) + { + var hash = sha1.ComputeHash(content); + return string.Concat(hash.Select(b => b.ToString("x2"))); + } + }); + } + } +} diff --git a/Tests/Encryptor.ClearText/EncryptorAndHasherTests.cs b/Tests/Encryptor.ClearText/EncryptorAndHasherTests.cs new file mode 100644 index 0000000..3f97318 --- /dev/null +++ b/Tests/Encryptor.ClearText/EncryptorAndHasherTests.cs @@ -0,0 +1,35 @@ +namespace EternalArrowBackup.Encryptor.ClearText.Tests +{ + using System.Text; + using System.Threading.Tasks; + using EternalArrowBackup.Hasher.SHA1; + using Xunit; + + public static class EncryptorAndHasherTests + { + [Theory] + [InlineData("")] + [InlineData("abc")] + [InlineData("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")] + public static async Task TestCorrectDecryption(string message) + { + var messageBytes = Encoding.ASCII.GetBytes(message); + var hasher = new SHA1ContentHasher(); + var encryptor = new ClearTextContentEncryptor(hasher); + var encrypted = await encryptor.Encrypt(messageBytes); + + var decryptionResult = await encryptor.Decrypt(encrypted); + Assert.True(decryptionResult.IsSuccessful); + Assert.Equal(messageBytes, decryptionResult.Data); + + encrypted[0]++; + decryptionResult = await encryptor.Decrypt(encrypted); + Assert.False(decryptionResult.IsSuccessful); + + encrypted[0]--; + encrypted[encrypted.Length - 1]--; + decryptionResult = await encryptor.Decrypt(encrypted); + Assert.False(decryptionResult.IsSuccessful); + } + } +} diff --git a/Tests/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.Tests.csproj b/Tests/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.Tests.csproj new file mode 100644 index 0000000..df2c314 --- /dev/null +++ b/Tests/Encryptor.ClearText/EternalArrowBackup.Encryptor.ClearText.Tests.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp1.1 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.Tests.csproj b/Tests/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.Tests.csproj new file mode 100644 index 0000000..581a5c5 --- /dev/null +++ b/Tests/Hasher.SHA1/EternalArrowBackup.Hasher.SHA1.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp1.1 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Hasher.SHA1/HasherTests.cs b/Tests/Hasher.SHA1/HasherTests.cs new file mode 100644 index 0000000..421adbd --- /dev/null +++ b/Tests/Hasher.SHA1/HasherTests.cs @@ -0,0 +1,51 @@ +namespace EternalArrowBackup.Hasher.SHA1 +{ + using System; + using System.Diagnostics; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + public static class HasherTests + { + private const int MegabytesPerSecond = 500; + + // Test vectors taken from http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/SHA_All.pdf + [Theory] + [InlineData("A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D", "abc")] + [InlineData("84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")] + public static async Task TestHashes(string expectedHash, string inputMessage) + { + var expectedHashFormatted = expectedHash.Replace(" ", string.Empty).ToLowerInvariant(); + var hasher = new SHA1ContentHasher(); + var messageBytes = Encoding.ASCII.GetBytes(inputMessage); + var actualHash = await hasher.ComputeHash(messageBytes); + Assert.Equal(expectedHashFormatted, actualHash); + } + + // Single-threaded SHA1 performance on i5-6500 is around 500MB/s + [Theory] + [InlineData(1, 0, 1)] + [InlineData(1000, 0, 1)] + [InlineData(1000000, 0.5 * 1000 / MegabytesPerSecond, 2 * 1000 / MegabytesPerSecond)] + [InlineData(10000000, 0.5 * 10000 / MegabytesPerSecond, 2 * 10000 / MegabytesPerSecond)] + [InlineData(100000000, 0.5 * 100000 / MegabytesPerSecond, 2 * 100000 / MegabytesPerSecond)] + [InlineData(1000000000, 0.5 * 1000000 / MegabytesPerSecond, 2 * 1000000 / MegabytesPerSecond)] + public static async Task TestPerformance(int length, int minMilliseconds, int maxMilliseconds) + { + var messageBytes = new byte[length]; + for (var i = 0; i < length; i++) + { + messageBytes[i] = (byte)((int)(Math.E * length + Math.PI * i) % 256); + } + + var hasher = new SHA1ContentHasher(); + var stopwatch = new Stopwatch(); + stopwatch.Start(); + await hasher.ComputeHash(messageBytes); + stopwatch.Stop(); + + Assert.InRange(stopwatch.ElapsedMilliseconds, minMilliseconds, maxMilliseconds); + } + } +}