summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorTim Whittington <bc@whittington.net.nz>2013-10-19 21:09:41 +1300
committerTim Whittington <bc@whittington.net.nz>2013-10-20 20:47:23 +1300
commitab670ffe1486ea52cd88a1a8234b201874460b4c (patch)
treebe2731fe4b3607b02c3603f9e28ab28b31b01f2c /crypto
parentfixed line endings (diff)
downloadBouncyCastle.NET-ed25519-ab670ffe1486ea52cd88a1a8234b201874460b4c.tar.xz
Port Poly1305 Mac implementation and tests from bc-java.
Diffstat (limited to 'crypto')
-rw-r--r--crypto/crypto.mdp3
-rw-r--r--crypto/src/crypto/generators/Poly1305KeyGenerator.cs122
-rw-r--r--crypto/src/crypto/macs/Poly1305.cs272
-rw-r--r--crypto/test/src/crypto/test/Poly1305Test.cs395
-rw-r--r--crypto/test/src/crypto/test/RegressionTest.cs1
5 files changed, 793 insertions, 0 deletions
diff --git a/crypto/crypto.mdp b/crypto/crypto.mdp
index 8152a40a9..d33604eb5 100644
--- a/crypto/crypto.mdp
+++ b/crypto/crypto.mdp
@@ -2297,6 +2297,9 @@
     <File subtype="Code" buildaction="Compile" name="src/bcpg/sig/RevocationReason.cs" />
     <File subtype="Code" buildaction="Compile" name="src/bcpg/sig/RevocationReasonTags.cs" />
     <File subtype="Code" buildaction="Compile" name="src/bcpg/sig/RevocationKeyTags.cs" />
+    <File subtype="Code" buildaction="Compile" name="src/crypto/macs/Poly1305.cs" />
+    <File subtype="Code" buildaction="Compile" name="src/crypto/generators/Poly1305KeyGenerator.cs" />
+    <File subtype="Code" buildaction="Compile" name="test/src/crypto/test/Poly1305Test.cs" />
   </Contents>
   <References>
     <ProjectReference type="Assembly" localcopy="True" refto="test/lib/nunit.core.dll" />
diff --git a/crypto/src/crypto/generators/Poly1305KeyGenerator.cs b/crypto/src/crypto/generators/Poly1305KeyGenerator.cs
new file mode 100644
index 000000000..f2aa85262
--- /dev/null
+++ b/crypto/src/crypto/generators/Poly1305KeyGenerator.cs
@@ -0,0 +1,122 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Crypto.Generators
+{
+	/// <summary>
+	/// Generates keys for the Poly1305 MAC.
+	/// </summary>
+	/// <remarks>
+	/// Poly1305 keys are 256 bit keys consisting of a 128 bit secret key used for the underlying block
+	/// cipher followed by a 128 bit {@code r} value used for the polynomial portion of the Mac. <br>
+	/// The {@code r} value has a specific format with some bits required to be cleared, resulting in an
+	/// effective 106 bit key. <br>
+	/// A separately generated 256 bit key can be modified to fit the Poly1305 key format by using the
+	/// {@link #clamp(byte[])} method to clear the required bits.
+	/// </remarks>
+	/// <seealso cref="Org.Bouncycastle.Crypto.Macs.Poly1305"/>
+	public class Poly1305KeyGenerator
+		: CipherKeyGenerator
+	{
+		private const byte R_MASK_LOW_2 = (byte)0xFC;
+		private const byte R_MASK_HIGH_4 = (byte)0x0F;
+
+		/// <summary>
+		/// Initialises the key generator.
+		/// </summary>
+		/// <remarks>
+		/// Poly1305 keys are always 256 bits, so the key length in the provided parameters is ignored.
+		/// </remarks>
+		protected override void engineInit(KeyGenerationParameters param)
+		{
+			// Poly1305 keys are always 256 bits
+			this.random = param.Random;
+			this.strength = 32;
+		}
+
+		/// <summary>
+		/// Generates a 256 bit key in the format required for Poly1305 - e.g.
+		/// <code>k[0] ... k[15], r[0] ... r[15]</code> with the required bits in <code>r</code> cleared
+		/// as per <see cref="Clamp(byte[])"/>.
+		/// </summary>
+		protected override byte[] engineGenerateKey()
+		{
+			byte[] key = base.engineGenerateKey();
+			Clamp(key);
+			return key;
+		}
+
+		/// <summary>
+		/// Modifies an existing 32 byte key value to comply with the requirements of the Poly1305 key by
+		/// clearing required bits in the <code>r</code> (second 16 bytes) portion of the key.<br>
+		/// Specifically:
+		/// <ul>
+		/// <li>r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15})</li>
+		/// <li>r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252})</li>
+		/// </ul>
+		/// </summary>
+		/// <param name="key">a 32 byte key value <code>k[0] ... k[15], r[0] ... r[15]</code></param>
+		public static void Clamp(byte[] key)
+		{
+			/*
+	         * Key is k[0] ... k[15], r[0] ... r[15] as per poly1305_aes_clamp in ref impl.
+	         */
+			if (key.Length != 32)
+			{
+				throw new ArgumentException("Poly1305 key must be 256 bits.");
+			}
+
+			/*
+	         * r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15})
+	         */
+			key[19] &= R_MASK_HIGH_4;
+			key[23] &= R_MASK_HIGH_4;
+			key[27] &= R_MASK_HIGH_4;
+			key[31] &= R_MASK_HIGH_4;
+
+			/*
+	         * r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252}).
+	         */
+			key[20] &= R_MASK_LOW_2;
+			key[24] &= R_MASK_LOW_2;
+			key[28] &= R_MASK_LOW_2;
+		}
+
+		/// <summary>
+		/// Checks a 32 byte key for compliance with the Poly1305 key requirements, e.g.
+		/// <code>k[0] ... k[15], r[0] ... r[15]</code> with the required bits in <code>r</code> cleared
+		/// as per <see cref="Clamp(byte[])"/>.
+		/// </summary>
+		/// <param name="key">Key.</param>
+		/// <exception cref="System.ArgumentException">if the key is of the wrong length, or has invalid bits set
+		///           in the <code>r</code> portion of the key.</exception>
+		public static void CheckKey(byte[] key)
+		{
+			if (key.Length != 32)
+			{
+				throw new ArgumentException("Poly1305 key must be 256 bits.");
+			}
+
+			checkMask(key[19], R_MASK_HIGH_4);
+			checkMask(key[23], R_MASK_HIGH_4);
+			checkMask(key[27], R_MASK_HIGH_4);
+			checkMask(key[31], R_MASK_HIGH_4);
+
+			checkMask(key[20], R_MASK_LOW_2);
+			checkMask(key[24], R_MASK_LOW_2);
+			checkMask(key[28], R_MASK_LOW_2);
+		}
+
+		private static void checkMask(byte b, byte mask)
+		{
+			if ((b & (~mask)) != 0)
+			{
+				throw new ArgumentException("Invalid format for r portion of Poly1305 key.");
+			}
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/crypto/src/crypto/macs/Poly1305.cs b/crypto/src/crypto/macs/Poly1305.cs
new file mode 100644
index 000000000..2d453b6ad
--- /dev/null
+++ b/crypto/src/crypto/macs/Poly1305.cs
@@ -0,0 +1,272 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Macs
+{
+
+	/// <summary>
+	/// Poly1305 message authentication code, designed by D. J. Bernstein.
+	/// </summary>
+	/// <remarks>
+	/// Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key
+	/// consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106
+	/// effective key bits) used in the authenticator.
+	/// 
+	/// The polynomial calculation in this implementation is adapted from the public domain <a
+	/// href="https://github.com/floodyberry/poly1305-donna">poly1305-donna-unrolled</a> C implementation
+	/// by Andrew M (@floodyberry).
+	/// </remarks>
+	/// <seealso cref="Org.BouncyCastle.Crypto.Generators.Poly1305KeyGenerator"/>
+	public class Poly1305
+		: IMac
+	{
+		private const int BLOCK_SIZE = 16;
+
+		private readonly IBlockCipher cipher;
+
+		private readonly byte[] singleByte = new byte[1];
+
+		// Initialised state
+
+		/** Polynomial key */
+		private uint r0, r1, r2, r3, r4;
+
+		/** Precomputed 5 * r[1..4] */
+		private uint s1, s2, s3, s4;
+
+		/** Encrypted nonce */
+		private uint k0, k1, k2, k3;
+
+		// Accumulating state
+
+		/** Current block of buffered input */
+		private byte[] currentBlock = new byte[BLOCK_SIZE];
+
+		/** Current offset in input buffer */
+		private int currentBlockOffset = 0;
+
+		/** Polynomial accumulator */
+		private uint h0, h1, h2, h3, h4;
+
+		/**
+	     * Constructs a Poly1305 MAC, using a 128 bit block cipher.
+	     */
+		public Poly1305(IBlockCipher cipher)
+		{
+			if (cipher.GetBlockSize() != BLOCK_SIZE)
+			{
+				throw new ArgumentException("Poly1305 requires a 128 bit block cipher.");
+			}
+			this.cipher = cipher;
+		}
+
+		/// <summary>
+		/// Initialises the Poly1305 MAC.
+		/// </summary>
+		/// <param name="parameters">a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with
+		///          a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}.</param>
+		public void Init(ICipherParameters parameters)
+		{
+			byte[] nonce;
+			byte[] key;
+			if ((parameters is ParametersWithIV) && ((ParametersWithIV)parameters).Parameters is KeyParameter)
+			{
+				nonce = ((ParametersWithIV)parameters).GetIV();
+				key = ((KeyParameter)((ParametersWithIV)parameters).Parameters).GetKey();
+			}
+			else
+			{
+				throw new ArgumentException("Poly1305 requires a key and and IV.");
+			}
+
+			setKey(key, nonce);
+			Reset();
+		}
+
+		private void setKey(byte[] key, byte[] nonce)
+		{
+			if (nonce.Length != BLOCK_SIZE)
+			{
+				throw new ArgumentException("Poly1305 requires a 128 bit IV.");
+			}
+			Poly1305KeyGenerator.CheckKey(key);
+
+			// Extract r portion of key
+			uint t0 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 0);
+			uint t1 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 4);
+			uint t2 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 8);
+			uint t3 = Pack.LE_To_UInt32(key, BLOCK_SIZE + 12);
+
+			r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6;
+			r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12;
+			r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18;
+			r3 = t2 & 0x3f03fff; t3 >>= 8;
+			r4 = t3 & 0x00fffff;
+
+			// Precompute multipliers
+			s1 = r1 * 5;
+			s2 = r2 * 5;
+			s3 = r3 * 5;
+			s4 = r4 * 5;
+
+			// Compute encrypted nonce
+			byte[] cipherKey = new byte[BLOCK_SIZE];
+			Array.Copy(key, 0, cipherKey, 0, cipherKey.Length);
+
+			cipher.Init(true, new KeyParameter(cipherKey));
+			cipher.ProcessBlock(nonce, 0, cipherKey, 0);
+
+			k0 = Pack.LE_To_UInt32(cipherKey, 0);
+			k1 = Pack.LE_To_UInt32(cipherKey, 4);
+			k2 = Pack.LE_To_UInt32(cipherKey, 8);
+			k3 = Pack.LE_To_UInt32(cipherKey, 12);
+		}
+
+		public string AlgorithmName
+		{
+			get { return "Poly1305-" + cipher.AlgorithmName; }
+		}
+
+		public int GetMacSize()
+		{
+			return BLOCK_SIZE;
+		}
+
+		public void Update(byte input)
+		{
+			singleByte[0] = input;
+			BlockUpdate(singleByte, 0, 1);
+		}
+
+		public void BlockUpdate(byte[] input, int inOff, int len)
+		{
+			int copied = 0;
+			while (len > copied)
+			{
+				if (currentBlockOffset == BLOCK_SIZE)
+				{
+					processBlock();
+					currentBlockOffset = 0;
+				}
+
+				int toCopy = System.Math.Min((len - copied), BLOCK_SIZE - currentBlockOffset);
+				Array.Copy(input, copied + inOff, currentBlock, currentBlockOffset, toCopy);
+				copied += toCopy;
+				currentBlockOffset += toCopy;
+			}
+
+		}
+
+		private void processBlock()
+		{
+			if (currentBlockOffset < BLOCK_SIZE)
+			{
+				currentBlock[currentBlockOffset] = 1;
+				for (int i = currentBlockOffset + 1; i < BLOCK_SIZE; i++)
+				{
+					currentBlock[i] = 0;
+				}
+			}
+
+			ulong t0 = Pack.LE_To_UInt32(currentBlock, 0);
+			ulong t1 = Pack.LE_To_UInt32(currentBlock, 4);
+			ulong t2 = Pack.LE_To_UInt32(currentBlock, 8);
+			ulong t3 = Pack.LE_To_UInt32(currentBlock, 12);
+
+			h0 += (uint)(t0 & 0x3ffffffU);
+			h1 += (uint)((((t1 << 32) | t0) >> 26) & 0x3ffffff);
+			h2 += (uint)((((t2 << 32) | t1) >> 20) & 0x3ffffff);
+			h3 += (uint)((((t3 << 32) | t2) >> 14) & 0x3ffffff);
+			h4 += (uint)(t3 >> 8);
+
+			if (currentBlockOffset == BLOCK_SIZE)
+			{
+				h4 += (1 << 24);
+			}
+
+			ulong tp0 = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1);
+			ulong tp1 = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2);
+			ulong tp2 = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3);
+			ulong tp3 = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4);
+			ulong tp4 = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0);
+
+			ulong b;
+			h0 = (uint)tp0 & 0x3ffffff; b = (tp0 >> 26);
+			tp1 += b; h1 = (uint)tp1 & 0x3ffffff; b = (tp1 >> 26);
+			tp2 += b; h2 = (uint)tp2 & 0x3ffffff; b = (tp2 >> 26);
+			tp3 += b; h3 = (uint)tp3 & 0x3ffffff; b = (tp3 >> 26);
+			tp4 += b; h4 = (uint)tp4 & 0x3ffffff; b = (tp4 >> 26);
+			h0 += (uint)(b * 5);
+		}
+
+		public int DoFinal(byte[] output, int outOff)
+		{
+			if (outOff + BLOCK_SIZE > output.Length)
+			{
+				throw new DataLengthException("Output buffer is too short.");
+			}
+
+			if (currentBlockOffset > 0)
+			{
+				// Process padded block
+				processBlock();
+			}
+
+			ulong f0, f1, f2, f3;
+
+			uint b = h0 >> 26;
+			h0 = h0 & 0x3ffffff;
+			h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff;
+			h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff;
+			h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff;
+			h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff;
+			h0 += b * 5;
+
+			uint g0, g1, g2, g3, g4;
+			g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff;
+			g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff;
+			g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff;
+			g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff;
+			g4 = h4 + b - (1 << 26);
+
+			b = (g4 >> 31) - 1;
+			uint nb = ~b;
+			h0 = (h0 & nb) | (g0 & b);
+			h1 = (h1 & nb) | (g1 & b);
+			h2 = (h2 & nb) | (g2 & b);
+			h3 = (h3 & nb) | (g3 & b);
+			h4 = (h4 & nb) | (g4 & b);
+
+			f0 = ((h0      ) | (h1 << 26)) + (ulong)k0;
+			f1 = ((h1 >> 6 ) | (h2 << 20)) + (ulong)k1;
+			f2 = ((h2 >> 12) | (h3 << 14)) + (ulong)k2;
+			f3 = ((h3 >> 18) | (h4 << 8 )) + (ulong)k3;
+
+			Pack.UInt32_To_LE((uint)f0, output, outOff);
+			f1 += (f0 >> 32);
+			Pack.UInt32_To_LE((uint)f1, output, outOff + 4);
+			f2 += (f1 >> 32);
+			Pack.UInt32_To_LE((uint)f2, output, outOff + 8);
+			f3 += (f2 >> 32);
+			Pack.UInt32_To_LE((uint)f3, output, outOff + 12);
+
+			Reset();
+			return BLOCK_SIZE;
+		}
+
+		public void Reset()
+		{
+			currentBlockOffset = 0;
+
+			h0 = h1 = h2 = h3 = h4 = 0;
+		}
+
+		private static ulong mul32x32_64(uint i1, uint i2)
+		{
+			return ((ulong)i1) * i2;
+		}
+	}
+}
diff --git a/crypto/test/src/crypto/test/Poly1305Test.cs b/crypto/test/src/crypto/test/Poly1305Test.cs
new file mode 100644
index 000000000..a1513165b
--- /dev/null
+++ b/crypto/test/src/crypto/test/Poly1305Test.cs
@@ -0,0 +1,395 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+	/*
+	 */
+	public class Poly1305Test
+		: SimpleTest
+	{
+		private const int MAXLEN = 1000;
+
+		private class KeyEngine
+			: IBlockCipher
+		{
+
+			private byte[] key;
+			private int blockSize;
+
+			public KeyEngine(int blockSize)
+			{
+				this.blockSize = blockSize;
+			}
+
+			public void Init(bool forEncryption, ICipherParameters parameters)
+			{
+				if (parameters is KeyParameter)
+				{
+					this.key = ((KeyParameter)parameters).GetKey();
+				}
+			}
+
+			public bool IsPartialBlockOkay 
+			{ 
+				get { return false; } 
+			}
+
+			public string AlgorithmName
+			{
+				get { return "Key"; }
+			}
+
+			public int GetBlockSize()
+			{
+				return blockSize;
+			}
+
+			public int ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+			{
+				Array.Copy(key, 0, output, outOff, key.Length);
+				return key.Length;
+			}
+
+			public void Reset()
+			{
+			}
+
+		}
+
+		private class TestCase
+		{
+			internal byte[] key;
+			internal byte[] nonce;
+			internal byte[] message;
+			internal byte[] expectedMac;
+
+			public TestCase(string key, string nonce, string message, string expectedMac)
+			{
+				this.key = Hex.Decode(key);
+				// nacl test case keys are not pre-Clamped
+				Poly1305KeyGenerator.Clamp(this.key);
+				this.nonce = (nonce == null) ? null : Hex.Decode(nonce);
+				this.message = Hex.Decode(message);
+				this.expectedMac = Hex.Decode(expectedMac);
+			}
+		}
+
+		private static TestCase[] CASES = {
+			// Raw Poly1305
+			// onetimeauth.c from nacl-20110221
+			new TestCase("2539121d8e234e652d651fa4c8cff880eea6a7251c1e72916d11c2cb214d3c25", null,
+			             "8e993b9f48681273c29650ba32fc76ce48332ea7164d96a4476fb8c531a1186a"
+			             + "c0dfc17c98dce87b4da7f011ec48c97271d2c20f9b928fe2270d6fb863d51738"
+			             + "b48eeee314a7cc8ab932164548e526ae90224368517acfeabd6bb3732bc0e9da"
+			             + "99832b61ca01b6de56244a9e88d5f9b37973f622a43d14a6599b1f654cb45a74e355a5",
+			             "f3ffc7703f9400e52a7dfb4b3d3305d9"),
+
+			// Poly1305-AES
+			// Loop 1 of test-poly1305aes from poly1305aes-20050218
+			new TestCase("0000000000000000000000000000000000000000000000000000000000000000",
+			             "00000000000000000000000000000000", "", "66e94bd4ef8a2c3b884cfa59ca342b2e"),
+			new TestCase("f795bd4a52e29ed713d313fa20e98dbcf795bd0a50e29e0710d3130a20e98d0c",
+			             "917cf69ebd68b2ec9b9fe9a3eadda692", "66f7", "5ca585c75e8f8f025e710cabc9a1508b"),
+			new TestCase("e69dae0aab9f91c03a325dcc9436fa903ef49901c8e11c000430d90ad45e7603",
+			             "166450152e2394835606a9d1dd2cdc8b", "66f75c0e0c7a406586", "2924f51b9c2eff5df09db61dd03a9ca1"),
+			new TestCase("85a4ea91a7de0b0d96eed0d4bf6ecf1cda4afc035087d90e503f8f0ea08c3e0d",
+			             "0b6ef7a0b8f8c738b0f8d5995415271f",
+			             "66f75c0e0c7a40658629e3392f7f8e3349a02191ffd49f39879a8d9d1d0e23ea",
+			             "3c5a13adb18d31c64cc29972030c917d"),
+			new TestCase(
+				"25eb69bac5cdf7d6bfcee4d9d5507b82ca3c6a0da0a864024ca3090628c28e0d",
+				"046772a4f0a8de92e4f0d628cdb04484",
+				"66f75c0e0c7a40658629e3392f7f8e3349a02191ffd49f39879a8d9d1d0e23ea3caa4d240bd2ab8a8c4a6bb8d3288d9de4b793f05e97646dd4d98055de",
+				"fc5fb58dc65daf19b14d1d05da1064e8"),
+
+			// Specific test cases generated from test-poly1305aes from poly1305aes-20050218 that
+			// expose Java unsigned integer problems
+			new TestCase(
+				"95cc0e44d0b79a8856afcae1bec4fe3c" + "01bcb20bfc8b6e03609ddd09f44b060f",
+				null,
+				"66f75c0e0c7a40658629e3392f7f8e3349a02191ffd49f39879a8d9d1d0e23ea3caa4d240bd2ab8a8c4a6bb8d3288d9de4b793f05e97646dd4d98055de"
+				+ "fc3e0677d956b4c62664bac15962ab15d93ccbbc03aafdbde779162ed93b55361f0f8acaa41d50ef5175927fe79ea316186516eef15001cd04d3524a55"
+				+ "e4fa3c5ca479d3aaa8a897c21807f721b6270ffc68b6889d81a116799f6aaa35d8e04c7a7dd5e6da2519e8759f54e906696f5772fee093283bcef7b930"
+				+ "aed50323bcbc8c820c67422c1e16bdc022a9c0277c9d95fef0ea4ee11e2b27276da811523c5acb80154989f8a67ee9e3fa30b73b0c1c34bf46e3464d97"
+				+ "7cd7fcd0ac3b82721080bb0d9b982ee2c77feee983d7ba35da88ce86955002940652ab63bc56fb16f994da2b01d74356509d7d1b6d7956b0e5a557757b"
+				+ "d1ced2eef8650bc5b6d426108c1518abcbd0befb6a0d5fd57a3e2dbf31458eab63df66613653d4beae73f5c40eb438fbcfdcf4a4ba46320184b9ca0da4"
+				+ "dfae77de7ccc910356caea3243f33a3c81b064b3b7cedc7435c223f664227215715980e6e0bb570d459ba80d7512dbe458c8f0f3f52d659b6e8eef19ee"
+				+ "71aea2ced85c7a42ffca6522a62db49a2a46eff72bd7f7e0883acd087183f0627f3537a4d558754ed63358e8182bee196735b361dc9bd64d5e34e1074a"
+				+ "855655d2974cc6fa1653754cf40f561d8c7dc526aab2908ec2d2b977cde1a1fb1071e32f40e049ea20f30368ba1592b4fe57fb51595d23acbdace324cd"
+				+ "d78060a17187c662368854e915402d9b52fb21e984663e41c26a109437e162cfaf071b53f77e50000a5388ff183b82ce7a1af476c416d7d204157b3633"
+				+ "b2f4ec077b699b032816997e37bceded8d4a04976fd7d0c0b029f290794c3be504c5242287ea2f831f11ed5690d92775cd6e863d7731fd4da687ebfb13"
+				+ "df4c41dc0fb8", "ae345d555eb04d6947bb95c0965237e2"),
+			new TestCase(
+				"76fb3635a2dc92a1f768163ab12f2187" + "cd07fd0ef8c0be0afcbdb30af4af0009",
+				null,
+				"f05204a74f0f88a7fa1a95b84ec3d8ffb36fcdc7723ea65dfe7cd464e86e0abf6b9d51db3220cfd8496ad6e6d36ebee8d990f9ce0d3bb7f72b7ab5b3ab0a73240d11efe772c857021ae859db4933cdde4387b471d2ce700fef4b81087f8f47c307881fd83017afcd15b8d21edf9b704677f46df97b07e5b83f87c8abd90af9b1d0f9e2710e8ebd0d4d1c6a055abea861f42368bed94d9373e909c1d3715b221c16bc524c55c31ec3eab204850bb2474a84f9917038eff9d921130951391b5c54f09b5e1de833ea2cd7d3b306740abb7096d1e173da83427da2adddd3631eda30b54dbf487f2b082e8646f07d6e0a87e97522ca38d4ace4954bf3db6dd3a93b06fa18eb56856627ed6cffcd7ae26374554ca18ab8905f26331d323fe10e6e70624c7bc07a70f06ecd804b48f8f7e75e910165e1beb554f1f0ec7949c9c8d429a206b4d5c0653102249b6098e6b45fac2a07ff0220b0b8ae8f4c6bcc0c813a7cd141fa8b398b42575fc395747c5a0257ac41d6c1f434cfbf5dfe8349f5347ef6b60e611f5d6c3cbc20ca2555274d1934325824cef4809da293ea13f181929e2af025bbd1c9abdc3af93afd4c50a2854ade3887f4d2c8c225168052c16e74d76d2dd3e9467a2c5b8e15c06ffbffa42b8536384139f07e195a8c9f70f514f31dca4eb2cf262c0dcbde53654b6250a29efe21d54e83c80e005a1cad36d5934ff01c32e4bc5fe06d03064ff4a268517df4a94c759289f323734318cfa5d859d4ce9c16e63d02dff0896976f521607638535d2ee8dd3312e1ddc80a55d34fe829ab954c1ebd54d929954770f1be9d32b4c05003c5c9e97943b6431e2afe820b1e967b19843e5985a131b1100517cdc363799104af91e2cf3f53cb8fd003653a6dd8a31a3f9d566a7124b0ffe9695bcb87c482eb60106f88198f766a40bc0f4873c23653c5f9e7a8e446f770beb8034cf01d21028ba15ccee21a8db918c4829d61c88bfa927bc5def831501796c5b401a60a6b1b433c9fb905c8cd40412fffee81ab",
+				"045be28cc52009f506bdbfabedacf0b4"),
+
+		};
+
+		public override string Name
+		{
+			get { return "Poly1305"; }
+		}
+
+		public override void PerformTest()
+		{
+			testKeyGenerator();
+			testInit();
+			for (int i = 0; i < CASES.Length; i++)
+			{
+				testCase(i);
+			}
+			testSequential();
+			testReset();
+		}
+
+		private void testCase(int i)
+		{
+			byte[] output = new byte[16];
+			TestCase tc = CASES[i];
+
+			IMac mac;
+			if (tc.nonce == null)
+			{
+				// Raw Poly1305 test - don't do any transform on AES key part
+				mac = new Poly1305(new KeyEngine(16));
+				mac.Init(new ParametersWithIV(new KeyParameter(tc.key), new byte[16]));
+			}
+			else
+			{
+				mac = new Poly1305(new AesFastEngine());
+				mac.Init(new ParametersWithIV(new KeyParameter(tc.key), tc.nonce));
+			}
+			mac.BlockUpdate(tc.message, 0, tc.message.Length);
+			mac.DoFinal(output, 0);
+
+			if (!Arrays.AreEqual(output, tc.expectedMac))
+			{
+				Fail("Mismatched output " + i, Hex.ToHexString(tc.expectedMac), Hex.ToHexString(output));
+			}
+		}
+
+		private void testSequential()
+		{
+			// Sequential test, adapted from test-poly1305aes
+			int len;
+			byte[] kr = new byte[32];
+			byte[] m = new byte[MAXLEN];
+			byte[] n = new byte[16];
+			byte[] output = new byte[16];
+
+			int c = 0;
+			IMac mac = new Poly1305(new AesFastEngine());
+			for (int loop = 0; loop < 13; loop++)
+			{
+				len = 0;
+				for (;;)
+				{
+					c++;
+					mac.Init(new ParametersWithIV(new KeyParameter(kr), n));
+					mac.BlockUpdate(m, 0, len);
+					mac.DoFinal(output, 0);
+
+					// if (c == 678)
+					// {
+					// TestCase tc = CASES[0];
+					//
+					// if (!Arrays.AreEqual(tc.key, kr))
+					// {
+					// System.err.println("Key bad");
+					// System.err.println(Hex.ToHexString(tc.key)));
+					// System.err.println(Hex.ToHexString(kr)));
+					// System.exit(1);
+					// }
+					// if (!Arrays.AreEqual(tc.nonce, n))
+					// {
+					// System.err.println("Nonce bad");
+					// System.exit(1);
+					// }
+					// System.out.printf("[%d] m: %s\n", c, Hex.ToHexString(m, 0, len)));
+					// System.out.printf("[%d] K: %s\n", c, new string(Hex.encodje(kr)));
+					// System.out.printf("[%d] N: %s\n", c, Hex.ToHexString(n)));
+					// System.out.printf("[%d] M: ", c);
+					// }
+					// System.out.printf("%d/%s\n", c, Hex.ToHexString(out)));
+
+					if (len >= MAXLEN)
+						break;
+					n[0] = (byte)(n[0] ^ loop);
+					for (int i = 0; i < 16; ++i)
+						n[i] ^= output[i];
+					if (len % 2 != 0)
+						for (int i = 0; i < 16; ++i)
+							kr[i] ^= output[i];
+					if (len % 3 != 0)
+						for (int i = 0; i < 16; ++i)
+							kr[i + 16] ^= output[i];
+					Poly1305KeyGenerator.Clamp(kr);
+					m[len++] ^= output[0];
+				}
+			}
+			// Output after 13 loops as generated by poly1305 ref
+			if (c != 13013 || !Arrays.AreEqual(output, Hex.Decode("c96f60a23701a5b0fd2016f58cbe4f7e")))
+			{
+				Fail("Sequential Poly1305 " + c, "c96f60a23701a5b0fd2016f58cbe4f7e", Hex.ToHexString(output));
+			}
+		}
+
+		private void testReset()
+		{
+			CipherKeyGenerator gen = new Poly1305KeyGenerator();
+			gen.Init(new KeyGenerationParameters(new SecureRandom(), 256));
+			byte[] k = gen.GenerateKey();
+
+			byte[] m = new byte[10000];
+			byte[] check = new byte[16];
+			byte[] output = new byte[16];
+
+			// Generate baseline
+			IMac poly = new Poly1305(new AesFastEngine());
+			poly.Init(new ParametersWithIV(new KeyParameter(k), new byte[16]));
+
+			poly.BlockUpdate(m, 0, m.Length);
+			poly.DoFinal(check, 0);
+
+			// Check reset after doFinal
+			poly.BlockUpdate(m, 0, m.Length);
+			poly.DoFinal(output, 0);
+
+			if (!Arrays.AreEqual(check, output))
+			{
+				Fail("Mac not reset after doFinal");
+			}
+
+			// Check reset
+			poly.Update((byte)1);
+			poly.Update((byte)2);
+			poly.Reset();
+			poly.BlockUpdate(m, 0, m.Length);
+			poly.DoFinal(output, 0);
+
+			if (!Arrays.AreEqual(check, output))
+			{
+				Fail("Mac not reset after doFinal");
+			}
+
+			// Check init resets
+			poly.Update((byte)1);
+			poly.Update((byte)2);
+			poly.Init(new ParametersWithIV(new KeyParameter(k), new byte[16]));
+			poly.BlockUpdate(m, 0, m.Length);
+			poly.DoFinal(output, 0);
+
+			if (!Arrays.AreEqual(check, output))
+			{
+				Fail("Mac not reset after doFinal");
+			}
+		}
+
+		private void testInit()
+		{
+			CipherKeyGenerator gen = new Poly1305KeyGenerator();
+			gen.Init(new KeyGenerationParameters(new SecureRandom(), 256));
+			byte[] k = gen.GenerateKey();
+
+			IMac poly = new Poly1305(new AesFastEngine());
+			poly.Init(new ParametersWithIV(new KeyParameter(k), new byte[16]));
+
+			try
+			{
+				poly.Init(new ParametersWithIV(new KeyParameter(k), new byte[15]));
+				Fail("16 byte nonce required");
+			} catch (ArgumentException)
+			{
+				// Expected
+			}
+
+			try
+			{
+				byte[] k2 = new byte[k.Length - 1];
+				Array.Copy(k, 0, k2, 0, k2.Length);
+				poly.Init(new ParametersWithIV(new KeyParameter(k2), new byte[16]));
+				Fail("32 byte key required");
+			} catch (ArgumentException)
+			{
+				// Expected
+			}
+
+			try
+			{
+				k[19] = (byte)0xFF;
+				poly.Init(new ParametersWithIV(new KeyParameter(k), new byte[16]));
+				Fail("UnClamped key should not be accepted.");
+			} catch (ArgumentException)
+			{
+				// Expected
+			}
+
+		}
+
+		private void testKeyGenerator()
+		{
+			CipherKeyGenerator gen = new Poly1305KeyGenerator();
+			gen.Init(new KeyGenerationParameters(new SecureRandom(), 256));
+			byte[] k = gen.GenerateKey();
+
+			if (k.Length != 32)
+			{
+				Fail("Poly1305 key should be 256 bits.");
+			}
+
+			try
+			{
+				Poly1305KeyGenerator.CheckKey(k);
+			} catch (ArgumentException)
+			{
+				Fail("Poly1305 key should be Clamped on generation.");
+			}
+
+			byte[] k2 = new byte[k.Length];
+			Array.Copy(k, 0, k2, 0, k2.Length);
+			Poly1305KeyGenerator.Clamp(k);
+			if (!Arrays.AreEqual(k, k2))
+			{
+				Fail("Poly1305 key should be Clamped on generation.");
+			}
+
+			try
+			{
+				k2[19] = (byte)0xff;
+				Poly1305KeyGenerator.CheckKey(k2);
+				Fail("UnClamped key should fail check.");
+			} catch (ArgumentException)
+			{
+				// Expected
+			}
+		}
+
+		public static void Main(
+			string[] args)
+		{
+			RunTest(new Poly1305Test());
+		}
+
+		[Test]
+		public void TestFunction()
+		{
+			string resultText = Perform().ToString();
+
+			Assert.AreEqual(Name + ": Okay", resultText);
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/crypto/test/src/crypto/test/RegressionTest.cs b/crypto/test/src/crypto/test/RegressionTest.cs
index 687e9ee4f..a43ee76cc 100644
--- a/crypto/test/src/crypto/test/RegressionTest.cs
+++ b/crypto/test/src/crypto/test/RegressionTest.cs
@@ -109,6 +109,7 @@ namespace Org.BouncyCastle.Crypto.Tests
             new SCryptTest(),
             new NullTest(),
             new SipHashTest(),
+            new Poly1305Test(),
             new OcbTest(),
         };