diff options
author | Tim Whittington <bc@whittington.net.nz> | 2013-10-19 21:09:41 +1300 |
---|---|---|
committer | Tim Whittington <bc@whittington.net.nz> | 2013-10-20 20:47:23 +1300 |
commit | ab670ffe1486ea52cd88a1a8234b201874460b4c (patch) | |
tree | be2731fe4b3607b02c3603f9e28ab28b31b01f2c /crypto/src | |
parent | fixed line endings (diff) | |
download | BouncyCastle.NET-ed25519-ab670ffe1486ea52cd88a1a8234b201874460b4c.tar.xz |
Port Poly1305 Mac implementation and tests from bc-java.
Diffstat (limited to 'crypto/src')
-rw-r--r-- | crypto/src/crypto/generators/Poly1305KeyGenerator.cs | 122 | ||||
-rw-r--r-- | crypto/src/crypto/macs/Poly1305.cs | 272 |
2 files changed, 394 insertions, 0 deletions
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; + } + } +} |