Permalink
Browse files

.NET support (#121)

* Implement AES-CMAC

* Use test vectors from the top level vectors directory

* Add tests for vector doubling

* Multiply vectors in place

* Implement S2V

* Add S2V documentation

* Be explicit about not using padding in CBC mode

* Implement AES-CTR

* Implement Seal function for SIV mode

* Add tests for Seal function

* Implement SIV Open function

* Add authentication test

* Add SIV benchmarks

* Do not generate more bytes than needed in CTR keystream

* Add more AES-CTR tests

* Performance improvements

* More performance improvements

* Massive performance improvement

I tried to fit AesCmac into standard .NET library design by
inheriting the KeyedHashAlgorithm, but the performance
overhead was huge, so I had to give up on that idea.

* Add methods for generating keys and nonces

* Add test for large SIV message

* Add the examples project

* Update the top-level README file with C# links

* Document exceptions in AesSiv.Open method

* Allow null plaintext and add test for authentication only

* Add the README file for the C# version of the library

* Setup CI for .NET

* Implement AES-PMAC

* Add AES-PMAC tests

* Implement AES-PMAC-SIV

* Add AES-PMAC-SIV tests

* Implement STREAM

* Add STREAM XML documentation

* Implement AEAD interface

* Update the README file

* Update the C# README file

* Add the STREAM example

* Move constant-time functions to Subtle class and add necessary MethodImplOptions flags

* Make AES-CTR internal

* Add Travis build status to .NET README file

* Fix broken build image link

* Update the security notice in the .NET README file

* Remove the documentation for the TrailingZeros function (it's internal, anyway)

* Update the contributors list
  • Loading branch information...
Metalnem authored and tarcieri committed Dec 29, 2017
1 parent e994740 commit 2d46674736d33c7302e689095ca26bd0cd78f844
View
@@ -0,0 +1,9 @@
# Visual Studio Code
.vscode
# Build results
[Bb]in/
[Oo]bj/
# BenchmarkDotNet
**/BenchmarkDotNet.Artifacts/*
View
@@ -40,3 +40,7 @@ matrix:
- cd tests/ffi
- make
- ./ffi_test
- language: csharp
mono: none
dotnet: 2.0.0
before_script: cd dotnet
View
@@ -7,3 +7,4 @@ and have granted the right to use their contributions under the terms of the
* [Tony Arcieri (@tarcieri)](https://github.com/tarcieri)
* [Dmitry Chestnykh (@dchest)](https://github.com/dchest)
* [John Downey (@jtdowney)](https://github.com/jtdowney)
* [Nemanja Mijailovic (@metalnem)](https://github.com/metalnem)
View
@@ -22,13 +22,14 @@ message streams, or large files using the [AES-SIV] ([RFC 5297]),
[AES-PMAC-SIV], and [STREAM] constructions.
Miscreant is available for several programming languages, including
[Go], [JavaScript], [Python], [Ruby], and [Rust].
[C#], [Go], [JavaScript], [Python], [Ruby], and [Rust].
[Phil Rogaway]: https://en.wikipedia.org/wiki/Phillip_Rogaway
[RFC 5297]: https://tools.ietf.org/html/rfc5297
[AES-SIV]: https://github.com/miscreant/miscreant/wiki/AES-SIV
[AES-PMAC-SIV]: https://github.com/miscreant/miscreant/wiki/AES-PMAC-SIV
[STREAM]: https://github.com/miscreant/miscreant/wiki/STREAM
[C#]: https://github.com/miscreant/miscreant/tree/master/dotnet
[Go]: https://github.com/miscreant/miscreant/tree/master/go
[JavaScript]: https://github.com/miscreant/miscreant/tree/master/js
[Python]: https://github.com/miscreant/miscreant/tree/master/python
@@ -116,12 +117,14 @@ The following algorithms are provided by **Miscreant**:
| Language | Version |
|------------------------|--------------------------------------|
| [C#][csharp-link] | N/A |
| [Go][go-link] | N/A |
| [JavaScript][npm-link] | [![npm][npm-shield]][npm-link] |
| [Python][pypi-link] | [![pypi][pypi-shield]][pypi-link] |
| [Ruby][gem-link] | [![gem][gem-shield]][gem-link] |
| [Rust][crate-link] | [![crate][crate-shield]][crate-link] |
[csharp-link]: https://github.com/miscreant/miscreant/tree/master/dotnet
[go-link]: https://github.com/miscreant/miscreant/tree/master/go
[npm-shield]: https://img.shields.io/npm/v/miscreant.svg
[npm-link]: https://www.npmjs.com/package/miscreant
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
namespace Miscreant.Benchmarks
{
[MemoryDiagnoser]
public class AesSivBenchmark
{
private AesSiv cmacSiv;
private AesSiv pmacSiv;
private byte[] data;
private byte[] message1K;
private byte[] message8K;
private byte[] ciphertextCmac1K;
private byte[] ciphertextCmac8K;
private byte[] ciphertextPmac1K;
private byte[] ciphertextPmac8K;
[GlobalSetup]
public void Setup()
{
cmacSiv = AesSiv.CreateAesCmacSiv(new byte[32]);
pmacSiv = AesSiv.CreateAesPmacSiv(new byte[32]);
data = new byte[64];
message1K = new byte[1024];
message8K = new byte[8192];
ciphertextCmac1K = cmacSiv.Seal(message1K, data);
ciphertextCmac8K = cmacSiv.Seal(message8K, data);
ciphertextPmac1K = pmacSiv.Seal(message1K, data);
ciphertextPmac8K = pmacSiv.Seal(message8K, data);
}
[Benchmark]
public void BenchmarkAesCmacSivSeal1K() => cmacSiv.Seal(message1K, data);
[Benchmark]
public void BenchmarkAesCmacSivSeal8K() => cmacSiv.Seal(message8K, data);
[Benchmark]
public void BenchmarkAesCmacSivOpen1K() => cmacSiv.Open(ciphertextCmac1K, data);
[Benchmark]
public void BenchmarkAesCmacSivOpen8K() => cmacSiv.Open(ciphertextCmac8K, data);
[Benchmark]
public void BenchmarkAesPmacSivSeal1K() => pmacSiv.Seal(message1K, data);
[Benchmark]
public void BenchmarkAesPmacSivSeal8K() => pmacSiv.Seal(message8K, data);
[Benchmark]
public void BenchmarkAesPmacSivOpen1K() => pmacSiv.Open(ciphertextPmac1K, data);
[Benchmark]
public void BenchmarkAesPmacSivOpen8K() => pmacSiv.Open(ciphertextPmac8K, data);
}
}
@@ -0,0 +1,43 @@
using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
namespace Miscreant.Benchmarks
{
[MemoryDiagnoser]
public class MacBenchmark
{
private const int BlockSize = 16;
private const int MessageSize = 1024;
private static readonly RandomNumberGenerator random = RandomNumberGenerator.Create();
private readonly byte[] message;
private readonly AesCmac cmac;
private readonly AesPmac pmac;
private readonly ICryptoTransform encryptor;
public MacBenchmark()
{
var key = Utils.GetRandomBytes(BlockSize);
var iv = Utils.GetRandomBytes(BlockSize);
message = Utils.GetRandomBytes(MessageSize);
cmac = new AesCmac(key);
pmac = new AesPmac(key);
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
encryptor = aes.CreateEncryptor(key, iv);
}
[Benchmark]
public void BenchmarkAesCmac() => cmac.HashCore(message, 0, message.Length);
[Benchmark]
public void BenchmarkAesPmac() => pmac.HashCore(message, 0, message.Length);
[Benchmark]
public void BenchmarkAes() => encryptor.TransformBlock(message, 0, message.Length, message, 0);
}
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Miscreant\Miscreant.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.11" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>
@@ -0,0 +1,12 @@
using BenchmarkDotNet.Running;
namespace Miscreant.Benchmarks
{
public class Program
{
public static void Main(string[] args)
{
BenchmarkRunner.Run<AesSivBenchmark>();
}
}
}
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Miscreant\Miscreant.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
</Project>
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Miscreant.Examples
{
public class Program
{
public static void Main(string[] args)
{
StreamExample();
}
private static void AeadExample()
{
// Plaintext to encrypt.
var plaintext = "I'm cooking MC's like a pound of bacon";
// Create a 32-byte key.
var key = Aead.GenerateKey256();
// Create a 16-byte nonce (optional).
var nonce = Aead.GenerateNonce(16);
// Create a new AEAD instance using the AES-CMAC-SIV
// algorithm. It implements the IDisposable interface,
// so it's best to create it inside using statement.
using (var aead = Aead.CreateAesCmacSiv(key))
{
// If the message is string, convert it to byte array first.
var bytes = Encoding.UTF8.GetBytes(plaintext);
// Encrypt the message.
var ciphertext = aead.Seal(bytes, nonce);
// To decrypt the message, call the Open method with the
// ciphertext and the same nonce that you generated previously.
bytes = aead.Open(ciphertext, nonce);
// If the message was originally string,
// convert if from byte array to string.
plaintext = Encoding.UTF8.GetString(bytes);
// Print the decrypted message to the standard output.
Console.WriteLine(plaintext);
}
}
private static void StreamExample()
{
// Messages to encrypt.
var messages = new List<string> {
"Now that the party is jumping",
"With the bass kicked in, the fingers are pumpin'",
"Quick to the point, to the point no faking",
"I'm cooking MC's like a pound of bacon"
};
// Create a 32-byte key.
var key = Aead.GenerateKey256();
// Create a 8-byte STREAM nonce (required).
var nonce = StreamEncryptor.GenerateNonce();
// Create STREAM encryptor and decryptor using the AES-CMAC-SIV
// algorithm. They implement the IDisposable interface,
// so it's best to create them inside using statement.
using (var encryptor = StreamEncryptor.CreateAesCmacSivEncryptor(key, nonce))
using (var decryptor = StreamDecryptor.CreateAesCmacSivDecryptor(key, nonce))
{
for (int i = 0; i < messages.Count; ++i)
{
// Calculate whether the message is the last message to encrypt.
bool last = i == messages.Count - 1;
// Convert the message to byte array first.
var bytes = Encoding.UTF8.GetBytes(messages[i]);
// Encrypt the message.
var ciphertext = encryptor.Seal(bytes, null, last);
// Decrypt the message.
var message = decryptor.Open(ciphertext, null, last);
// Convert the message back to string.
var plaintext = Encoding.UTF8.GetString(bytes);
// Print the decrypted message to the standard output.
Console.WriteLine(plaintext);
}
}
}
}
}
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Miscreant.Tests
{
public class AeadTest
{
[Fact]
public void TestSealAndOpen()
{
foreach (var example in LoadExamples())
{
using (var aead = CreateAead(example.Algorithm, example.Key))
{
var ciphertext = aead.Seal(example.Plaintext, example.Nonce, example.AssociatedData);
Assert.Equal(Hex.Encode(example.Ciphertext), Hex.Encode(ciphertext));
var plaintext = aead.Open(example.Ciphertext, example.Nonce, example.AssociatedData);
Assert.Equal(Hex.Encode(example.Plaintext), Hex.Encode(plaintext));
}
}
}
private static IEnumerable<AeadExample> LoadExamples()
{
var s = File.ReadAllText($"../../../../../vectors/aes_siv_aead.tjson");
var json = JObject.Parse(s);
var examples = json["examples:A<O>"];
foreach (var example in examples)
{
yield return new AeadExample
{
Name = (string)example["name:s"],
Algorithm = (string)example["alg:s"],
Key = Hex.Decode((string)example["key:d16"]),
AssociatedData = Hex.Decode((string)example["ad:d16"]),
Nonce = Hex.Decode((string)example["nonce:d16"]),
Plaintext = Hex.Decode((string)example["plaintext:d16"]),
Ciphertext = Hex.Decode((string)example["ciphertext:d16"])
};
}
}
private static Aead CreateAead(string algorithm, byte[] key)
{
switch (algorithm)
{
case "AES-SIV": return Aead.CreateAesCmacSiv(key);
case "AES-PMAC-SIV": return Aead.CreateAesPmacSiv(key);
default: throw new ArgumentException("Unknown algorithm.");
}
}
private struct AeadExample
{
public string Name;
public string Algorithm;
public byte[] Key;
public byte[] AssociatedData;
public byte[] Nonce;
public byte[] Plaintext;
public byte[] Ciphertext;
}
}
}
Oops, something went wrong.

0 comments on commit 2d46674

Please sign in to comment.