diff --git a/crypto/src/crypto/IAlphabetMapper.cs b/crypto/src/crypto/IAlphabetMapper.cs
new file mode 100644
index 000000000..af63caafd
--- /dev/null
+++ b/crypto/src/crypto/IAlphabetMapper.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Org.BouncyCastle.Crypto
+{
+/**
+ * Base interface for mapping from an alphabet to a set of indexes
+ * suitable for use with FPE.
+ */
+public interface IAlphabetMapper
+{
+ /// <summary>
+ /// Return the number of characters in the alphabet.
+ /// </summary>
+ /// <returns>the radix for the alphabet.</returns>
+ int Radix { get; }
+
+ /// <summary>
+ /// Return the passed in char[] as a byte array of indexes (indexes
+ /// can be more than 1 byte)
+ /// </summary>
+ /// <returns>an index array.</returns>
+ /// <param name="input">characters to be mapped.</param>
+ byte[] ConvertToIndexes(char[] input);
+
+ /// <summary>
+ /// Return a char[] for this alphabet based on the indexes passed.
+ /// </summary>
+ /// <returns>an array of char corresponding to the index values.</returns>
+ /// <param name="input">input array of indexes.</param>
+ char[] ConvertToChars(byte[] input);
+}
+}
diff --git a/crypto/src/crypto/digests/CSHAKEDigest.cs b/crypto/src/crypto/digests/CSHAKEDigest.cs
index 30d532089..c3b0b7068 100644
--- a/crypto/src/crypto/digests/CSHAKEDigest.cs
+++ b/crypto/src/crypto/digests/CSHAKEDigest.cs
@@ -44,6 +44,12 @@ namespace Org.BouncyCastle.Crypto.Digests
}
}
+ public CShakeDigest(CShakeDigest source)
+ : base(source)
+ {
+ this.diff = Arrays.Clone(source.diff);
+ }
+
// bytepad in SP 800-185
private void DiffPadAndAbsorb()
{
diff --git a/crypto/src/crypto/digests/KeccakDigest.cs b/crypto/src/crypto/digests/KeccakDigest.cs
index 62a9e03b6..2da2e099e 100644
--- a/crypto/src/crypto/digests/KeccakDigest.cs
+++ b/crypto/src/crypto/digests/KeccakDigest.cs
@@ -28,7 +28,7 @@ namespace Org.BouncyCastle.Crypto.Digests
protected byte[] dataQueue = new byte[192];
protected int rate;
protected int bitsInQueue;
- protected int fixedOutputLength;
+ protected internal int fixedOutputLength;
protected bool squeezing;
public KeccakDigest()
diff --git a/crypto/src/crypto/digests/ParallelHash.cs b/crypto/src/crypto/digests/ParallelHash.cs
new file mode 100644
index 000000000..1e42d86ab
--- /dev/null
+++ b/crypto/src/crypto/digests/ParallelHash.cs
@@ -0,0 +1,210 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+ /// <summary>
+ /// ParallelHash - a hash designed to support the efficient hashing of very long strings, by taking advantage,
+ /// of the parallelism available in modern processors with an optional XOF mode.
+ /// <para>
+ /// From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash
+ /// </para>
+ /// </summary>
+ public class ParallelHash
+ : IXof, IDigest
+ {
+ private static readonly byte[] N_PARALLEL_HASH = Strings.ToByteArray("ParallelHash");
+
+ private readonly CShakeDigest cshake;
+ private readonly CShakeDigest compressor;
+ private readonly int bitLength;
+ private readonly int outputLength;
+ private readonly int B;
+ private readonly byte[] buffer;
+ private readonly byte[] compressorBuffer;
+
+ private bool firstOutput;
+ private int nCount;
+ private int bufOff;
+
+ /**
+ * Base constructor.
+ *
+ * @param bitLength bit length of the underlying SHAKE function, 128 or 256.
+ * @param S the customization string - available for local use.
+ * @param B the blocksize (in bytes) for hashing.
+ */
+ public ParallelHash(int bitLength, byte[] S, int B): this(bitLength, S, B, bitLength * 2)
+ {
+
+ }
+
+ public ParallelHash(int bitLength, byte[] S, int B, int outputSize)
+ {
+ this.cshake = new CShakeDigest(bitLength, N_PARALLEL_HASH, S);
+ this.compressor = new CShakeDigest(bitLength, new byte[0], new byte[0]);
+ this.bitLength = bitLength;
+ this.B = B;
+ this.outputLength = (outputSize + 7) / 8;
+ this.buffer = new byte[B];
+ this.compressorBuffer = new byte[bitLength * 2 / 8];
+
+ Reset();
+ }
+
+ public ParallelHash(ParallelHash source)
+ {
+ this.cshake = new CShakeDigest(source.cshake);
+ this.compressor = new CShakeDigest(source.compressor);
+ this.bitLength = source.bitLength;
+ this.B = source.B;
+ this.outputLength = source.outputLength;
+ this.buffer = Arrays.Clone(source.buffer);
+ this.compressorBuffer = Arrays.Clone(source.compressorBuffer);
+ }
+
+ public string AlgorithmName
+ {
+ get { return "ParallelHash" + cshake.AlgorithmName.Substring(6); }
+ }
+
+ public int GetByteLength()
+ {
+ return cshake.GetByteLength();
+ }
+
+ public int GetDigestSize()
+ {
+ return outputLength;
+ }
+
+ public void Update(byte b)
+ {
+ buffer[bufOff++] = b;
+ if (bufOff == buffer.Length)
+ {
+ compress();
+ }
+ }
+
+ public void BlockUpdate(byte[] inBuf, int inOff, int len)
+ {
+ len = System.Math.Max(0, len);
+
+ //
+ // fill the current word
+ //
+ int i = 0;
+ if (bufOff != 0)
+ {
+ while (i < len && bufOff != buffer.Length)
+ {
+ buffer[bufOff++] = inBuf[inOff + i++];
+ }
+
+ if (bufOff == buffer.Length)
+ {
+ compress();
+ }
+ }
+
+ if (i < len)
+ {
+ while (len - i > B)
+ {
+ compress(inBuf, inOff + i, B);
+ i += B;
+ }
+ }
+
+ while (i < len)
+ {
+ Update(inBuf[inOff + i++]);
+ }
+ }
+
+ private void compress()
+ {
+ compress(buffer, 0, bufOff);
+ bufOff = 0;
+ }
+
+ private void compress(byte[] buf, int offSet, int len)
+ {
+ compressor.BlockUpdate(buf, offSet, len);
+ compressor.DoFinal(compressorBuffer, 0, compressorBuffer.Length);
+
+ cshake.BlockUpdate(compressorBuffer, 0, compressorBuffer.Length);
+
+ nCount++;
+ }
+
+ private void wrapUp(int outputSize)
+ {
+ if (bufOff != 0)
+ {
+ compress();
+ }
+ byte[] nOut = XofUtilities.RightEncode(nCount);
+ byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
+
+ cshake.BlockUpdate(nOut, 0, nOut.Length);
+ cshake.BlockUpdate(encOut, 0, encOut.Length);
+
+ firstOutput = false;
+ }
+
+ public int DoFinal(byte[] outBuf, int outOff)
+ {
+ if (firstOutput)
+ {
+ wrapUp(outputLength);
+ }
+
+ int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
+
+ Reset();
+
+ return rv;
+ }
+
+ public int DoFinal(byte[] outBuf, int outOff, int outLen)
+ {
+ if (firstOutput)
+ {
+ wrapUp(outputLength);
+ }
+
+ int rv = cshake.DoFinal(outBuf, outOff, outLen);
+
+ Reset();
+
+ return rv;
+ }
+
+ public int DoOutput(byte[] outBuf, int outOff, int outLen)
+ {
+ if (firstOutput)
+ {
+ wrapUp(0);
+ }
+
+ return cshake.DoOutput(outBuf, outOff, outLen);
+ }
+
+ public void Reset()
+ {
+ cshake.Reset();
+ Arrays.Clear(buffer);
+
+ byte[] hdr = XofUtilities.LeftEncode(B);
+ cshake.BlockUpdate(hdr, 0, hdr.Length);
+
+ nCount = 0;
+ bufOff = 0;
+ firstOutput = true;
+ }
+ }
+}
diff --git a/crypto/src/crypto/digests/TupleHash.cs b/crypto/src/crypto/digests/TupleHash.cs
new file mode 100644
index 000000000..76bba3f94
--- /dev/null
+++ b/crypto/src/crypto/digests/TupleHash.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Digests
+{
+ /// <summary>
+ /// TupleHash - a hash designed to simply hash a tuple of input strings, any or all of which may be empty strings,
+ /// in an unambiguous way with an optional XOF mode.
+ /// <para>
+ /// From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash
+ /// </para>
+ /// </summary>
+ public class TupleHash
+ : IXof, IDigest
+ {
+ private static readonly byte[] N_TUPLE_HASH = Strings.ToByteArray("TupleHash");
+
+ private readonly CShakeDigest cshake;
+ private readonly int bitLength;
+ private readonly int outputLength;
+
+ private bool firstOutput;
+
+ /**
+ * Base constructor.
+ *
+ * @param bitLength bit length of the underlying SHAKE function, 128 or 256.
+ * @param S the customization string - available for local use.
+ */
+ public TupleHash(int bitLength, byte[] S): this(bitLength, S, bitLength * 2)
+ {
+
+ }
+
+ public TupleHash(int bitLength, byte[] S, int outputSize)
+ {
+ this.cshake = new CShakeDigest(bitLength, N_TUPLE_HASH, S);
+ this.bitLength = bitLength;
+ this.outputLength = (outputSize + 7) / 8;
+
+ Reset();
+ }
+
+ public TupleHash(TupleHash original)
+ {
+ this.cshake = new CShakeDigest(original.cshake);
+ this.bitLength = cshake.fixedOutputLength;
+ this.outputLength = bitLength * 2 / 8;
+ this.firstOutput = original.firstOutput;
+ }
+
+ public string AlgorithmName
+ {
+ get { return "TupleHash" + cshake.AlgorithmName.Substring(6); }
+ }
+
+ public int GetByteLength()
+ {
+ return cshake.GetByteLength();
+ }
+
+ public int GetDigestSize()
+ {
+ return outputLength;
+ }
+
+ public void Update(byte b)
+ {
+ byte[] bytes = XofUtilities.Encode(b);
+ cshake.BlockUpdate(bytes, 0, bytes.Length);
+ }
+
+ public void BlockUpdate(byte[] inBuf, int inOff, int len)
+ {
+ byte[] bytes = XofUtilities.Encode(inBuf, inOff, len);
+ cshake.BlockUpdate(bytes, 0, bytes.Length);
+ }
+
+ private void wrapUp(int outputSize)
+ {
+ byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
+
+ cshake.BlockUpdate(encOut, 0, encOut.Length);
+
+ firstOutput = false;
+ }
+
+ public int DoFinal(byte[] outBuf, int outOff)
+ {
+ if (firstOutput)
+ {
+ wrapUp(GetDigestSize());
+ }
+
+ int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
+
+ Reset();
+
+ return rv;
+ }
+
+ public int DoFinal(byte[] outBuf, int outOff, int outLen)
+ {
+ if (firstOutput)
+ {
+ wrapUp(GetDigestSize());
+ }
+
+ int rv = cshake.DoFinal(outBuf, outOff, outLen);
+
+ Reset();
+
+ return rv;
+ }
+
+ public int DoOutput(byte[] outBuf, int outOff, int outLen)
+ {
+ if (firstOutput)
+ {
+ wrapUp(0);
+ }
+
+ return cshake.DoOutput(outBuf, outOff, outLen);
+ }
+
+ public void Reset()
+ {
+ cshake.Reset();
+ firstOutput = true;
+ }
+ }
+}
diff --git a/crypto/src/crypto/digests/XofUtils.cs b/crypto/src/crypto/digests/XofUtils.cs
index 5c197e0e6..a4d6622b3 100644
--- a/crypto/src/crypto/digests/XofUtils.cs
+++ b/crypto/src/crypto/digests/XofUtils.cs
@@ -1,5 +1,7 @@
using System;
+using Org.BouncyCastle.Utilities;
+
namespace Org.BouncyCastle.Crypto.Digests
{
internal class XofUtilities
@@ -47,5 +49,19 @@ namespace Org.BouncyCastle.Crypto.Digests
return b;
}
+
+ internal static byte[] Encode(byte X)
+ {
+ return Arrays.Concatenate(LeftEncode(8), new byte[] { X });
+ }
+
+ internal static byte[] Encode(byte[] inBuf, int inOff, int len)
+ {
+ if (inBuf.Length == len)
+ {
+ return Arrays.Concatenate(LeftEncode(len * 8), inBuf);
+ }
+ return Arrays.Concatenate(LeftEncode(len * 8), Arrays.CopyOfRange(inBuf, inOff, inOff + len));
+ }
}
}
diff --git a/crypto/src/crypto/fpe/FpeEngine.cs b/crypto/src/crypto/fpe/FpeEngine.cs
new file mode 100644
index 000000000..6757bad3a
--- /dev/null
+++ b/crypto/src/crypto/fpe/FpeEngine.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Fpe
+{
+/**
+ * Base class for format-preserving encryption.
+ */
+public abstract class FpeEngine
+{
+ protected IBlockCipher baseCipher;
+
+ protected bool forEncryption;
+ protected FpeParameters fpeParameters;
+
+ protected FpeEngine(IBlockCipher baseCipher)
+ {
+ this.baseCipher = baseCipher;
+ }
+
+ /// <summary>
+ /// Process length bytes from inBuf, writing the output to outBuf.
+ /// </summary>
+ /// <returns>number of bytes output.</returns>
+ /// <param name="inBuf">input data.</param>
+ /// <param name="inOff">offset in input data to start at.</param>
+ /// <param name="length">number of bytes to process.</param>
+ /// <param name="outBuf">destination buffer.</param>
+ /// <param name="outOff">offset to start writing at in destination buffer.</param>
+ public int ProcessBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff)
+ {
+ if (fpeParameters == null)
+ {
+ throw new InvalidOperationException("FPE engine not initialized");
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentException("input length cannot be negative");
+ }
+
+ if (inBuf == null || outBuf == null)
+ {
+ throw new NullReferenceException("buffer value is null");
+ }
+
+ if (inBuf.Length < inOff + length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if (outBuf.Length < outOff + length)
+ {
+ throw new OutputLengthException("output buffer too short");
+ }
+
+ if (forEncryption)
+ {
+ return encryptBlock(inBuf, inOff, length, outBuf, outOff);
+ }
+ else
+ {
+ return decryptBlock(inBuf, inOff, length, outBuf, outOff);
+ }
+ }
+
+ protected static ushort[] toShortArray(byte[] buf)
+ {
+ if ((buf.Length & 1) != 0)
+ {
+ throw new ArgumentException("data must be an even number of bytes for a wide radix");
+ }
+
+ ushort[] rv = new ushort[buf.Length / 2];
+
+ for (int i = 0; i != rv.Length; i++)
+ {
+ rv[i] = Pack.BE_To_UInt16(buf, i * 2);
+ }
+
+ return rv;
+ }
+
+ protected static bool IsOverrideSet(string propName)
+ {
+ string propValue = Platform.GetEnvironmentVariable(propName);
+
+ return propValue == null || Platform.EqualsIgnoreCase("true", propValue);
+ }
+
+ protected static byte[] toByteArray(ushort[] buf)
+ {
+ byte[] rv = new byte[buf.Length * 2];
+
+ for (int i = 0; i != buf.Length; i++)
+ {
+ Pack.UInt16_To_BE(buf[i], rv, i * 2);
+ }
+
+ return rv;
+ }
+
+ /// <summary>
+ /// Initialize the FPE engine for encryption/decryption.
+ /// </summary>
+ /// <returns>number of bytes output.</returns>
+ /// <param name="forEncryption">true if initialising for encryption, false otherwise.</param>
+ /// <param name="parameters ">the key and other parameters to use to set the engine up.</param>
+ public abstract void Init(bool forEncryption, ICipherParameters parameters);
+
+ protected abstract int encryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff);
+
+ protected abstract int decryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff);
+}
+}
diff --git a/crypto/src/crypto/fpe/FpeFf1Engine.cs b/crypto/src/crypto/fpe/FpeFf1Engine.cs
new file mode 100644
index 000000000..8f34ef888
--- /dev/null
+++ b/crypto/src/crypto/fpe/FpeFf1Engine.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Crypto.Fpe
+{
+public class FpeFf1Engine
+ : FpeEngine
+{
+ public FpeFf1Engine(): this(new AesEngine())
+ {
+ }
+
+ public FpeFf1Engine(IBlockCipher baseCipher): base(baseCipher)
+ {
+ if (IsOverrideSet(SP80038G.FPE_DISABLED)
+ || IsOverrideSet(SP80038G.FF1_DISABLED))
+ {
+ throw new InvalidOperationException("FF1 encryption disabled");
+ }
+ }
+
+ public override void Init(bool forEncryption, ICipherParameters parameters)
+ {
+ this.forEncryption = forEncryption;
+
+ this.fpeParameters = (FpeParameters)parameters;
+
+ baseCipher.Init(!fpeParameters.UseInverseFunction, fpeParameters.Key);
+ }
+
+ protected override int encryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff)
+ {
+ byte[] enc;
+
+ if (fpeParameters.Radix > 256)
+ {
+ enc = toByteArray(SP80038G.EncryptFF1w(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), toShortArray(inBuf), inOff, length / 2));
+ }
+ else
+ {
+ enc = SP80038G.EncryptFF1(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), inBuf, inOff, length);
+ }
+
+ Array.Copy(enc, 0, outBuf, outOff, length);
+
+ return length;
+ }
+
+ protected override int decryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff)
+ {
+ byte[] dec;
+
+ if (fpeParameters.Radix > 256)
+ {
+ dec = toByteArray(SP80038G.DecryptFF1w(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), toShortArray(inBuf), inOff, length / 2));
+ }
+ else
+ {
+ dec = SP80038G.DecryptFF1(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), inBuf, inOff, length);
+ }
+
+ Array.Copy(dec, 0, outBuf, outOff, length);
+
+ return length;
+ }
+}
+}
diff --git a/crypto/src/crypto/fpe/FpeFf3_1Engine.cs b/crypto/src/crypto/fpe/FpeFf3_1Engine.cs
new file mode 100644
index 000000000..480560bb2
--- /dev/null
+++ b/crypto/src/crypto/fpe/FpeFf3_1Engine.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Fpe
+{
+public class FpeFf3_1Engine
+ : FpeEngine
+{
+ public FpeFf3_1Engine(): this(new AesEngine())
+ {
+ }
+
+ public FpeFf3_1Engine(IBlockCipher baseCipher): base(baseCipher)
+ {
+ if (IsOverrideSet(SP80038G.FPE_DISABLED))
+ {
+ throw new InvalidOperationException("FPE disabled");
+ }
+ }
+
+ public override void Init(bool forEncryption, ICipherParameters parameters)
+ {
+ this.forEncryption = forEncryption;
+
+ this.fpeParameters = (FpeParameters)parameters;
+
+ baseCipher.Init(!fpeParameters.UseInverseFunction, new KeyParameter(Arrays.Reverse(fpeParameters.Key.GetKey())));
+
+ if (fpeParameters.GetTweak().Length != 7)
+ {
+ throw new ArgumentException("tweak should be 56 bits");
+ }
+ }
+
+ protected override int encryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff)
+ {
+ byte[] enc;
+
+ if (fpeParameters.Radix > 256)
+ {
+ enc = toByteArray(SP80038G.EncryptFF3_1w(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), toShortArray(inBuf), inOff, length / 2));
+ }
+ else
+ {
+ enc = SP80038G.EncryptFF3_1(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), inBuf, inOff, length);
+ }
+
+ Array.Copy(enc, 0, outBuf, outOff, length);
+
+ return length;
+ }
+
+ protected override int decryptBlock(byte[] inBuf, int inOff, int length, byte[] outBuf, int outOff)
+ {
+ byte[] dec;
+
+ if (fpeParameters.Radix > 256)
+ {
+ dec = toByteArray(SP80038G.DecryptFF3_1w(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), toShortArray(inBuf), inOff, length / 2));
+ }
+ else
+ {
+ dec = SP80038G.DecryptFF3_1(baseCipher, fpeParameters.Radix, fpeParameters.GetTweak(), inBuf, inOff, length);
+ }
+
+ Array.Copy(dec, 0, outBuf, outOff, length);
+
+ return length;
+ }
+}
+}
diff --git a/crypto/src/crypto/fpe/SP80038G.cs b/crypto/src/crypto/fpe/SP80038G.cs
new file mode 100644
index 000000000..4ce89c9b2
--- /dev/null
+++ b/crypto/src/crypto/fpe/SP80038G.cs
@@ -0,0 +1,698 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Fpe
+{
+ /*
+ * SP800-38G Format-Preserving Encryption
+ *
+ * TODOs
+ * - Initialize the cipher internally or externally?
+ * 1. Algs 7-10 don't appear to require forward vs. inverse transform, although sample data is forward.
+ * 2. Algs 9-10 specify reversal of the cipher key!
+ * - Separate construction/initialization stage for "prerequisites"
+ */
+ internal class SP80038G
+ {
+ internal static readonly string FPE_DISABLED = "Org.BouncyCastle.Fpe.Disable";
+ internal static readonly string FF1_DISABLED = "Org.BouncyCastle.Fpe.Disable_Ff1";
+
+ protected static readonly int BLOCK_SIZE = 16;
+ protected static readonly double LOG2 = System.Math.Log(2.0);
+ protected static readonly double TWO_TO_96 = System.Math.Pow(2, 96);
+
+ public static byte[] DecryptFF1(IBlockCipher cipher, int radix, byte[] tweak, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, true, radix, buf, off, len);
+
+ // Algorithm 8
+ int n = len;
+ int u = n / 2, v = n - u;
+
+ ushort[] A = toShort(buf, off, u);
+ ushort[] B = toShort(buf, off + u, v);
+
+ ushort[] rv = decFF1(cipher, radix, tweak, n, u, v, A, B);
+
+ return toByte(rv);
+ }
+
+ public static ushort[] DecryptFF1w(IBlockCipher cipher, int radix, byte[] tweak, ushort[] buf, int off, int len)
+ {
+ checkArgs(cipher, true, radix, buf, off, len);
+
+ // Algorithm 8
+ int n = len;
+ int u = n / 2, v = n - u;
+
+ ushort[] A = new ushort[u];
+ ushort[] B = new ushort[v];
+
+ Array.Copy(buf, off, A, 0, u);
+ Array.Copy(buf, off + u, B, 0, v);
+
+ return decFF1(cipher, radix, tweak, n, u, v, A, B);
+ }
+
+ private static ushort[] decFF1(IBlockCipher cipher, int radix, byte[] T, int n, int u, int v, ushort[] A, ushort[] B)
+ {
+ int t = T.Length;
+ int b = ((int)Ceil(System.Math.Log((double)radix) * (double)v / LOG2) + 7) / 8;
+ int d = (((b + 3) / 4) * 4) + 4;
+
+ byte[] P = calculateP_FF1(radix, (byte)u, n, t);
+
+ BigInteger bigRadix = BigInteger.ValueOf(radix);
+ BigInteger[] modUV = calculateModUV(bigRadix, u, v);
+
+ int m = u;
+
+ for (int i = 9; i >= 0; --i)
+ {
+ // i. - iv.
+ BigInteger y = calculateY_FF1(cipher, bigRadix, T, b, d, i, P, A);
+
+ // v.
+ m = n - m;
+ BigInteger modulus = modUV[i & 1];
+
+ // vi.
+ BigInteger c = num(bigRadix, B).Subtract(y).Mod(modulus);
+
+ // vii. - ix.
+ ushort[] C = B;
+ B = A;
+ A = C;
+ str(bigRadix, c, m, C, 0);
+ }
+
+ return Arrays.Concatenate(A, B);
+ }
+
+ public static byte[] DecryptFF3(IBlockCipher cipher, int radix, byte[] tweak64, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak64.Length != 8)
+ {
+ throw new ArgumentException();
+ }
+
+ return implDecryptFF3(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static byte[] DecryptFF3_1(IBlockCipher cipher, int radix, byte[] tweak56, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak56.Length != 7)
+ {
+ throw new ArgumentException("tweak should be 56 bits");
+ }
+
+ byte[] tweak64 = calculateTweak64_FF3_1(tweak56);
+
+ return implDecryptFF3(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static ushort[] DecryptFF3_1w(IBlockCipher cipher, int radix, byte[] tweak56, ushort[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak56.Length != 7)
+ {
+ throw new ArgumentException("tweak should be 56 bits");
+ }
+
+ byte[] tweak64 = calculateTweak64_FF3_1(tweak56);
+
+ return implDecryptFF3w(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static byte[] EncryptFF1(IBlockCipher cipher, int radix, byte[] tweak, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, true, radix, buf, off, len);
+
+ // Algorithm 7
+ int n = len;
+ int u = n / 2, v = n - u;
+
+ ushort[] A = toShort(buf, off, u);
+ ushort[] B = toShort(buf, off + u, v);
+
+ return toByte(encFF1(cipher, radix, tweak, n, u, v, A, B));
+ }
+
+ public static ushort[] EncryptFF1w(IBlockCipher cipher, int radix, byte[] tweak, ushort[] buf, int off, int len)
+ {
+ checkArgs(cipher, true, radix, buf, off, len);
+
+ // Algorithm 7
+ int n = len;
+ int u = n / 2, v = n - u;
+
+ ushort[] A = new ushort[u];
+ ushort[] B = new ushort[v];
+
+ Array.Copy(buf, off, A, 0, u);
+ Array.Copy(buf, off + u, B, 0, v);
+
+ return encFF1(cipher, radix, tweak, n, u, v, A, B);
+ }
+
+ private static ushort[] encFF1(IBlockCipher cipher, int radix, byte[] T, int n, int u, int v, ushort[] A, ushort[] B)
+ {
+ int t = T.Length;
+
+ int b = ((int)Ceil(System.Math.Log((double)radix) * (double)v / LOG2) + 7) / 8;
+ int d = (((b + 3) / 4) * 4) + 4;
+
+ byte[] P = calculateP_FF1(radix, (byte)u, n, t);
+
+ BigInteger bigRadix = BigInteger.ValueOf(radix);
+ BigInteger[] modUV = calculateModUV(bigRadix, u, v);
+
+ int m = v;
+
+ for (int i = 0; i < 10; ++i)
+ {
+ // i. - iv.
+ BigInteger y = calculateY_FF1(cipher, bigRadix, T, b, d, i, P, B);
+
+ // v.
+ m = n - m;
+ BigInteger modulus = modUV[i & 1];
+
+ // vi.
+ BigInteger c = num(bigRadix, A).Add(y).Mod(modulus);
+
+ // vii. - ix.
+ ushort[] C = A;
+ A = B;
+ B = C;
+ str(bigRadix, c, m, C, 0);
+ }
+
+ return Arrays.Concatenate(A, B);
+ }
+
+ public static byte[] EncryptFF3(IBlockCipher cipher, int radix, byte[] tweak64, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak64.Length != 8)
+ {
+ throw new ArgumentException();
+ }
+
+ return implEncryptFF3(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static ushort[] EncryptFF3w(IBlockCipher cipher, int radix, byte[] tweak64, ushort[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak64.Length != 8)
+ {
+ throw new ArgumentException();
+ }
+
+ return implEncryptFF3w(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static ushort[] EncryptFF3_1w(IBlockCipher cipher, int radix, byte[] tweak56, ushort[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak56.Length != 7)
+ {
+ throw new ArgumentException("tweak should be 56 bits");
+ }
+ byte[] tweak64 = calculateTweak64_FF3_1(tweak56);
+
+ return EncryptFF3w(cipher, radix, tweak64, buf, off, len);
+ }
+
+ public static byte[] EncryptFF3_1(IBlockCipher cipher, int radix, byte[] tweak56, byte[] buf, int off, int len)
+ {
+ checkArgs(cipher, false, radix, buf, off, len);
+
+ if (tweak56.Length != 7)
+ {
+ throw new ArgumentException("tweak should be 56 bits");
+ }
+
+ byte[] tweak64 = calculateTweak64_FF3_1(tweak56);
+
+ return EncryptFF3(cipher, radix, tweak64, buf, off, len);
+ }
+
+ protected static BigInteger[] calculateModUV(BigInteger bigRadix, int u, int v)
+ {
+ BigInteger[] modUV = new BigInteger[2];
+ modUV[0] = bigRadix.Pow(u);
+ modUV[1] = modUV[0];
+ if (v != u)
+ {
+ modUV[1] = modUV[1].Multiply(bigRadix);
+ }
+ return modUV;
+ }
+
+ protected static byte[] calculateP_FF1(int radix, byte uLow, int n, int t)
+ {
+ byte[] P = new byte[BLOCK_SIZE];
+ P[0] = 1;
+ P[1] = 2;
+ P[2] = 1;
+
+ // Radix
+ P[3] = 0;
+ P[4] = (byte)(radix >> 8);
+ P[5] = (byte)radix;
+
+ P[6] = 10;
+ P[7] = uLow;
+ Pack.UInt32_To_BE((uint)n, P, 8);
+ Pack.UInt32_To_BE((uint)t, P, 12);
+ return P;
+ }
+
+ protected static byte[] calculateTweak64_FF3_1(byte[] tweak56)
+ {
+ byte[] tweak64 = new byte[8];
+ tweak64[0] = tweak56[0];
+ tweak64[1] = tweak56[1];
+ tweak64[2] = tweak56[2];
+ tweak64[3] = (byte)(tweak56[3] & 0xF0);
+ tweak64[4] = tweak56[4];
+ tweak64[5] = tweak56[5];
+ tweak64[6] = tweak56[6];
+ tweak64[7] = (byte)(tweak56[3] << 4);
+
+ return tweak64;
+ }
+
+ protected static BigInteger calculateY_FF1(IBlockCipher cipher, BigInteger bigRadix, byte[] T, int b, int d, int round, byte[] P, ushort[] AB)
+ {
+ int t = T.Length;
+
+ // i.
+ BigInteger numAB = num(bigRadix, AB);
+ byte[] bytesAB = BigIntegers.AsUnsignedByteArray(numAB);
+
+ int zeroes = -(t + b + 1) & 15;
+ byte[] Q = new byte[t + zeroes + 1 + b];
+ Array.Copy(T, 0, Q, 0, t);
+ Q[t + zeroes] = (byte)round;
+ Array.Copy(bytesAB, 0, Q, Q.Length - bytesAB.Length, bytesAB.Length);
+
+ // ii.
+ byte[] R = prf(cipher, Arrays.Concatenate(P, Q));
+
+ // iii.
+ byte[] sBlocks = R;
+ if (d > BLOCK_SIZE)
+ {
+ int sBlocksLen = (d + BLOCK_SIZE - 1) / BLOCK_SIZE;
+ sBlocks = new byte[sBlocksLen * BLOCK_SIZE];
+ Array.Copy(R, 0, sBlocks, 0, BLOCK_SIZE);
+
+ byte[] uint32 = new byte[4];
+ for (uint j = 1; j < sBlocksLen; ++j)
+ {
+ int sOff = (int)(j * BLOCK_SIZE);
+ Array.Copy(R, 0, sBlocks, sOff, BLOCK_SIZE);
+ Pack.UInt32_To_BE(j, uint32, 0);
+ xor(uint32, 0, sBlocks, sOff + BLOCK_SIZE - 4, 4);
+ cipher.ProcessBlock(sBlocks, sOff, sBlocks, sOff);
+ }
+ }
+
+ // iv.
+ return num(sBlocks, 0, d);
+ }
+
+ protected static BigInteger calculateY_FF3(IBlockCipher cipher, BigInteger bigRadix, byte[] T, int wOff, uint round, ushort[] AB)
+ {
+ // ii.
+ byte[] P = new byte[BLOCK_SIZE];
+ Pack.UInt32_To_BE(round, P, 0);
+ xor(T, wOff, P, 0, 4);
+ BigInteger numAB = num(bigRadix, AB);
+
+ byte[] bytesAB = BigIntegers.AsUnsignedByteArray(numAB);
+
+ if ((P.Length - bytesAB.Length) < 4) // to be sure...
+ {
+ throw new InvalidOperationException("input out of range");
+ }
+ Array.Copy(bytesAB, 0, P, P.Length - bytesAB.Length, bytesAB.Length);
+
+ // iii.
+ rev(P);
+ cipher.ProcessBlock(P, 0, P, 0);
+ rev(P);
+ byte[] S = P;
+
+ // iv.
+ return num(S, 0, S.Length);
+ }
+
+ protected static void checkArgs(IBlockCipher cipher, bool isFF1, int radix, ushort[] buf, int off, int len)
+ {
+ checkCipher(cipher);
+ if (radix < 2 || radix > (1 << 16))
+ {
+ throw new ArgumentException();
+ }
+ checkData(isFF1, radix, buf, off, len);
+ }
+
+ protected static void checkArgs(IBlockCipher cipher, bool isFF1, int radix, byte[] buf, int off, int len)
+ {
+ checkCipher(cipher);
+ if (radix < 2 || radix > (1 << 8))
+ {
+ throw new ArgumentException();
+ }
+ checkData(isFF1, radix, buf, off, len);
+ }
+
+ protected static void checkCipher(IBlockCipher cipher)
+ {
+ if (BLOCK_SIZE != cipher.GetBlockSize())
+ {
+ throw new ArgumentException();
+ }
+ }
+
+ protected static void checkData(bool isFF1, int radix, ushort[] buf, int off, int len)
+ {
+ checkLength(isFF1, radix, len);
+ for (int i = 0; i < len; ++i)
+ {
+ int b = buf[off + i] & 0xFFFF;
+ if (b >= radix)
+ {
+ throw new ArgumentException("input data outside of radix");
+ }
+ }
+ }
+
+ protected static void checkData(bool isFF1, int radix, byte[] buf, int off, int len)
+ {
+ checkLength(isFF1, radix, len);
+ for (int i = 0; i < len; ++i)
+ {
+ int b = buf[off + i] & 0xFF;
+ if (b >= radix)
+ {
+ throw new ArgumentException("input data outside of radix");
+ }
+ }
+ }
+
+ private static void checkLength(bool isFF1, int radix, int len)
+ {
+ if (len < 2 || System.Math.Pow(radix, len) < 1000000)
+ {
+ throw new ArgumentException("input too short");
+ }
+ if (!isFF1)
+ {
+ int maxLen = 2 * (int)(System.Math.Floor(System.Math.Log(TWO_TO_96) / System.Math.Log(radix)));
+ if (len > maxLen)
+ {
+ throw new ArgumentException("maximum input length is " + maxLen);
+ }
+ }
+ }
+
+ protected static byte[] implDecryptFF3(IBlockCipher cipher, int radix, byte[] tweak64, byte[] buf, int off, int len)
+ {
+ // Algorithm 10
+ byte[] T = tweak64;
+ int n = len;
+ int v = n / 2, u = n - v;
+
+ ushort[] A = toShort(buf, off, u);
+ ushort[] B = toShort(buf, off + u, v);
+
+ ushort[] rv = decFF3_1(cipher, radix, T, n, v, u, A, B);
+
+ return toByte(rv);
+ }
+
+ protected static ushort[] implDecryptFF3w(IBlockCipher cipher, int radix, byte[] tweak64, ushort[] buf, int off, int len)
+ {
+ // Algorithm 10
+ byte[] T = tweak64;
+ int n = len;
+ int v = n / 2, u = n - v;
+
+ ushort[] A = new ushort[u];
+ ushort[] B = new ushort[v];
+
+ Array.Copy(buf, off, A, 0, u);
+ Array.Copy(buf, off + u, B, 0, v);
+
+ return decFF3_1(cipher, radix, T, n, v, u, A, B);
+ }
+
+ private static ushort[] decFF3_1(IBlockCipher cipher, int radix, byte[] T, int n, int v, int u, ushort[] A, ushort[] B)
+ {
+ BigInteger bigRadix = BigInteger.ValueOf(radix);
+ BigInteger[] modVU = calculateModUV(bigRadix, v, u);
+
+ int m = u;
+
+ // Note we keep A, B in reverse order throughout
+ rev(A);
+ rev(B);
+
+ for (int i = 7; i >= 0; --i)
+ {
+ // i.
+ m = n - m;
+ BigInteger modulus = modVU[1 - (i & 1)];
+ int wOff = 4 - ((i & 1) * 4);
+
+ // ii. - iv.
+ BigInteger y = calculateY_FF3(cipher, bigRadix, T, wOff, (uint)i, A);
+
+ // v.
+ BigInteger c = num(bigRadix, B).Subtract(y).Mod(modulus);
+
+ // vi. - viii.
+ ushort[] C = B;
+ B = A;
+ A = C;
+ str(bigRadix, c, m, C, 0);
+ }
+
+ rev(A);
+ rev(B);
+
+ return Arrays.Concatenate(A, B);
+ }
+
+ protected static byte[] implEncryptFF3(IBlockCipher cipher, int radix, byte[] tweak64, byte[] buf, int off, int len)
+ {
+ // Algorithm 9
+ byte[] T = tweak64;
+ int n = len;
+ int v = n / 2, u = n - v;
+
+ ushort[] A = toShort(buf, off, u);
+ ushort[] B = toShort(buf, off + u, v);
+
+ ushort[] rv = encFF3_1(cipher, radix, T, n, v, u, A, B);
+
+ return toByte(rv);
+ }
+
+ protected static ushort[] implEncryptFF3w(IBlockCipher cipher, int radix, byte[] tweak64, ushort[] buf, int off, int len)
+ {
+ // Algorithm 9
+ byte[] T = tweak64;
+ int n = len;
+ int v = n / 2, u = n - v;
+
+ ushort[] A = new ushort[u];
+ ushort[] B = new ushort[v];
+
+ Array.Copy(buf, off, A, 0, u);
+ Array.Copy(buf, off + u, B, 0, v);
+
+ return encFF3_1(cipher, radix, T, n, v, u, A, B);
+ }
+
+ private static ushort[] encFF3_1(IBlockCipher cipher, int radix, byte[] t, int n, int v, int u, ushort[] a, ushort[] b)
+ {
+ BigInteger bigRadix = BigInteger.ValueOf(radix);
+ BigInteger[] modVU = calculateModUV(bigRadix, v, u);
+
+ int m = v;
+
+ // Note we keep A, B in reverse order throughout
+ rev(a);
+ rev(b);
+
+ for (uint i = 0; i < 8; ++i)
+ {
+ // i.
+ m = n - m;
+ BigInteger modulus = modVU[1 - (i & 1)];
+ int wOff = 4 - (int)((i & 1) * 4);
+
+ // ii. - iv.
+ BigInteger y = calculateY_FF3(cipher, bigRadix, t, wOff, i, b);
+
+ // v.
+ BigInteger c = num(bigRadix, a).Add(y).Mod(modulus);
+
+ // vi. - viii.
+ ushort[] C = a;
+ a = b;
+ b = C;
+ str(bigRadix, c, m, C, 0);
+ }
+
+ rev(a);
+ rev(b);
+
+ return Arrays.Concatenate(a, b);
+ }
+
+ protected static BigInteger num(byte[] buf, int off, int len)
+ {
+ return new BigInteger(1, Arrays.CopyOfRange(buf, off, off + len));
+ }
+
+ protected static BigInteger num(BigInteger R, ushort[] x)
+ {
+ BigInteger result = BigInteger.Zero;
+ for (int i = 0; i < x.Length; ++i)
+ {
+ result = result.Multiply(R).Add(BigInteger.ValueOf(x[i] & 0xFFFF));
+ }
+ return result;
+ }
+
+ protected static byte[] prf(IBlockCipher c, byte[] x)
+ {
+ if ((x.Length % BLOCK_SIZE) != 0)
+ {
+ throw new ArgumentException();
+ }
+
+ int m = x.Length / BLOCK_SIZE;
+ byte[] y = new byte[BLOCK_SIZE];
+
+ for (int i = 0; i < m; ++i)
+ {
+ xor(x, i * BLOCK_SIZE, y, 0, BLOCK_SIZE);
+ c.ProcessBlock(y, 0, y, 0);
+ }
+
+ return y;
+ }
+
+ // protected static void rev(byte[] x, int xOff, byte[] y, int yOff, int len)
+ // {
+ // for (int i = 1; i <= len; ++i)
+ // {
+ // y[yOff + len - i] = x[xOff + i - 1];
+ // }
+ // }
+
+ protected static void rev(byte[] x)
+ {
+ int half = x.Length / 2, end = x.Length - 1;
+ for (int i = 0; i < half; ++i)
+ {
+ byte tmp = x[i];
+ x[i] = x[end - i];
+ x[end - i] = tmp;
+ }
+ }
+
+ protected static void rev(ushort[] x)
+ {
+ int half = x.Length / 2, end = x.Length - 1;
+ for (int i = 0; i < half; ++i)
+ {
+ ushort tmp = x[i];
+ x[i] = x[end - i];
+ x[end - i] = tmp;
+ }
+ }
+
+ protected static void str(BigInteger R, BigInteger x, int m, ushort[] output, int off)
+ {
+ if (x.SignValue < 0)
+ {
+ throw new ArgumentException();
+ }
+ for (int i = 1; i <= m; ++i)
+ {
+ BigInteger[] qr = x.DivideAndRemainder(R);
+ output[off + m - i] = (ushort)qr[1].IntValue;
+ x = qr[0];
+ }
+ if (x.SignValue != 0)
+ {
+ throw new ArgumentException();
+ }
+ }
+
+ protected static void xor(byte[] x, int xOff, byte[] y, int yOff, int len)
+ {
+ for (int i = 0; i < len; ++i)
+ {
+ y[yOff + i] ^= x[xOff + i];
+ }
+ }
+
+ private static byte[] toByte(ushort[] buf)
+ {
+ byte[] s = new byte[buf.Length];
+
+ for (int i = 0; i != s.Length; i++)
+ {
+ s[i] = (byte)buf[i];
+ }
+
+ return s;
+ }
+
+ private static ushort[] toShort(byte[] buf, int off, int len)
+ {
+ ushort[] s = new ushort[len];
+
+ for (int i = 0; i != s.Length; i++)
+ {
+ s[i] = (ushort)(buf[off + i] & 0xFF);
+ }
+
+ return s;
+ }
+
+ private static int Ceil(double v)
+ {
+ int rv = (int)v;
+ if ((double)rv < v)
+ {
+ return rv + 1;
+ }
+ return rv;
+ }
+ }
+}
diff --git a/crypto/src/crypto/modes/GcmSivBlockCipher.cs b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
new file mode 100644
index 000000000..a45e5ec06
--- /dev/null
+++ b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
@@ -0,0 +1,963 @@
+using System;
+using System.Diagnostics;
+
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Modes.Gcm;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Modes
+{
+ /**
+ * GCM-SIV Mode.
+ * <p>It should be noted that the specified limit of 2<sup>36</sup> bytes is not supported. This is because all bytes are
+ * cached in a <b>ByteArrayOutputStream</b> object (which has a limit of a little less than 2<sup>31</sup> bytes),
+ * and are output on the <b>DoFinal</b>() call (which can only process a maximum of 2<sup>31</sup> bytes).</p>
+ * <p>The practical limit of 2<sup>31</sup> - 24 bytes is policed, and attempts to breach the limit will be rejected</p>
+ * <p>In order to properly support the higher limit, an extended form of <b>ByteArrayOutputStream</b> would be needed
+ * which would use multiple arrays to store the data. In addition, a new <b>doOutput</b> method would be required (similar
+ * to that in <b>XOF</b> digests), which would allow the data to be output over multiple calls. Alternatively an extended
+ * form of <b>ByteArrayInputStream</b> could be used to deliver the data.</p>
+ */
+ public class GcmSivBlockCipher
+ : IAeadBlockCipher
+ {
+ /**
+ * The buffer length.
+ */
+ private static readonly int BUFLEN = 16;
+
+ /**
+ * The halfBuffer length.
+ */
+ private static readonly int HALFBUFLEN = BUFLEN >> 1;
+
+ /**
+ * The nonce length.
+ */
+ private static readonly int NONCELEN = 12;
+
+ /**
+ * The maximum data length (AEAD/PlainText). Due to implementation constraints this is restricted to the maximum
+ * array length (https://programming.guide/java/array-maximum-length.html) minus the BUFLEN to allow for the MAC
+ */
+ private static readonly int MAX_DATALEN = Int32.MaxValue - 8 - BUFLEN;
+
+ /**
+ * The top bit mask.
+ */
+ private static readonly byte MASK = (byte) 0x80;
+
+ /**
+ * The addition constant.
+ */
+ private static readonly byte ADD = (byte) 0xE1;
+
+ /**
+ * The initialisation flag.
+ */
+ private static readonly int INIT = 1;
+
+ /**
+ * The aeadComplete flag.
+ */
+ private static readonly int AEAD_COMPLETE = 2;
+
+ /**
+ * The cipher.
+ */
+ private readonly IBlockCipher theCipher;
+
+ /**
+ * The multiplier.
+ */
+ private readonly IGcmMultiplier theMultiplier;
+
+ /**
+ * The gHash buffer.
+ */
+ internal readonly byte[] theGHash = new byte[BUFLEN];
+
+ /**
+ * The reverse buffer.
+ */
+ internal readonly byte[] theReverse = new byte[BUFLEN];
+
+ /**
+ * The aeadHasher.
+ */
+ private readonly GCMSIVHasher theAEADHasher;
+
+ /**
+ * The dataHasher.
+ */
+ private readonly GCMSIVHasher theDataHasher;
+
+ /**
+ * The plainDataStream.
+ */
+ private GCMSIVCache thePlain;
+
+ /**
+ * The encryptedDataStream (decryption only).
+ */
+ private GCMSIVCache theEncData;
+
+ /**
+ * Are we encrypting?
+ */
+ private bool forEncryption;
+
+ /**
+ * The initialAEAD.
+ */
+ private byte[] theInitialAEAD;
+
+ /**
+ * The nonce.
+ */
+ private byte[] theNonce;
+
+ /**
+ * The flags.
+ */
+ private int theFlags;
+
+ /**
+ * Constructor.
+ */
+ public GcmSivBlockCipher(): this(new AesEngine())
+ {
+
+ }
+
+ /**
+ * Constructor.
+ * @param pCipher the underlying cipher
+ */
+ public GcmSivBlockCipher(IBlockCipher pCipher): this(pCipher, new Tables4kGcmMultiplier())
+ {
+
+ }
+
+ /**
+ * Constructor.
+ * @param pCipher the underlying cipher
+ * @param pMultiplier the multiplier
+ */
+ public GcmSivBlockCipher(IBlockCipher pCipher,
+ IGcmMultiplier pMultiplier)
+ {
+ /* Ensure that the cipher is the correct size */
+ if (pCipher.GetBlockSize() != BUFLEN)
+ {
+ throw new ArgumentException("Cipher required with a block size of " + BUFLEN + ".");
+ }
+
+ /* Store parameters */
+ theCipher = pCipher;
+ theMultiplier = pMultiplier;
+
+ /* Create the hashers */
+ theAEADHasher = new GCMSIVHasher(this);
+ theDataHasher = new GCMSIVHasher(this);
+ }
+
+ public IBlockCipher GetUnderlyingCipher()
+ {
+ return theCipher;
+ }
+
+ public virtual int GetBlockSize()
+ {
+ return theCipher.GetBlockSize();
+ }
+
+ public void Init(bool pEncrypt,
+ ICipherParameters cipherParameters)
+ {
+ /* Set defaults */
+ byte[] myInitialAEAD = null;
+ byte[] myNonce = null;
+ KeyParameter myKey = null;
+
+ /* Access parameters */
+ if (cipherParameters is AeadParameters)
+ {
+ AeadParameters myAEAD = (AeadParameters) cipherParameters;
+ myInitialAEAD = myAEAD.GetAssociatedText();
+ myNonce = myAEAD.GetNonce();
+ myKey = myAEAD.Key;
+ }
+ else if (cipherParameters is ParametersWithIV)
+ {
+ ParametersWithIV myParms = (ParametersWithIV) cipherParameters;
+ myNonce = myParms.GetIV();
+ myKey = (KeyParameter) myParms.Parameters;
+ }
+ else
+ {
+ throw new ArgumentException("invalid parameters passed to GCM_SIV");
+ }
+
+ /* Check nonceSize */
+ if (myNonce == null || myNonce.Length != NONCELEN)
+ {
+ throw new ArgumentException("Invalid nonce");
+ }
+
+ /* Check keysize */
+ if (myKey == null)
+ {
+ throw new ArgumentException("Invalid key");
+ }
+
+ byte[] k = myKey.GetKey();
+
+ if (k.Length != BUFLEN
+ && k.Length != (BUFLEN << 1))
+ {
+ throw new ArgumentException("Invalid key");
+ }
+
+ /* Reset details */
+ forEncryption = pEncrypt;
+ theInitialAEAD = myInitialAEAD;
+ theNonce = myNonce;
+
+ /* Initialise the keys */
+ deriveKeys(myKey);
+ resetStreams();
+ }
+
+ public string AlgorithmName
+ {
+ get { return theCipher.AlgorithmName + "-GCM-SIV"; }
+ }
+
+ /**
+ * check AEAD status.
+ * @param pLen the aeadLength
+ */
+ private void checkAEADStatus(int pLen)
+ {
+ /* Check we are initialised */
+ if ((theFlags & INIT) == 0)
+ {
+ throw new InvalidOperationException("Cipher is not initialised");
+ }
+
+ /* Check AAD is allowed */
+ if ((theFlags & AEAD_COMPLETE) != 0)
+ {
+ throw new InvalidOperationException("AEAD data cannot be processed after ordinary data");
+ }
+
+ /* Make sure that we haven't breached AEAD data limit */
+ if ((long)theAEADHasher.getBytesProcessed() + Int64.MinValue
+ > (MAX_DATALEN - pLen) + Int64.MinValue)
+ {
+ throw new InvalidOperationException("AEAD byte count exceeded");
+ }
+ }
+
+ /**
+ * check status.
+ * @param pLen the dataLength
+ */
+ private void checkStatus(int pLen)
+ {
+ /* Check we are initialised */
+ if ((theFlags & INIT) == 0)
+ {
+ throw new InvalidOperationException("Cipher is not initialised");
+ }
+
+ /* Complete the AEAD section if this is the first data */
+ if ((theFlags & AEAD_COMPLETE) == 0)
+ {
+ theAEADHasher.completeHash();
+ theFlags |= AEAD_COMPLETE;
+ }
+
+ /* Make sure that we haven't breached data limit */
+ long dataLimit = MAX_DATALEN;
+ long currBytes = thePlain.Length;
+ if (!forEncryption)
+ {
+ dataLimit += BUFLEN;
+ currBytes = theEncData.Length;
+ }
+ if (currBytes + System.Int64.MinValue
+ > (dataLimit - pLen) + System.Int64.MinValue)
+ {
+ throw new InvalidOperationException("byte count exceeded");
+ }
+ }
+
+ public void ProcessAadByte(byte pByte)
+ {
+ /* Check that we can supply AEAD */
+ checkAEADStatus(1);
+
+ /* Process the aead */
+ theAEADHasher.updateHash(pByte);
+ }
+
+ public void ProcessAadBytes(byte[] pData,
+ int pOffset,
+ int pLen)
+ {
+ /* Check that we can supply AEAD */
+ checkAEADStatus(pLen);
+
+ /* Check input buffer */
+ checkBuffer(pData, pOffset, pLen, false);
+
+ /* Process the aead */
+ theAEADHasher.updateHash(pData, pOffset, pLen);
+ }
+
+ public int ProcessByte(byte pByte,
+ byte[] pOutput,
+ int pOutOffset)
+ {
+ /* Check that we have initialised */
+ checkStatus(1);
+
+ /* Store the data */
+ if (forEncryption)
+ {
+ thePlain.WriteByte(pByte);
+ theDataHasher.updateHash(pByte);
+ }
+ else
+ {
+ theEncData.WriteByte(pByte);
+ }
+
+ /* No data returned */
+ return 0;
+ }
+
+ public int ProcessBytes(byte[] pData,
+ int pOffset,
+ int pLen,
+ byte[] pOutput,
+ int pOutOffset)
+ {
+ /* Check that we have initialised */
+ checkStatus(pLen);
+
+ /* Check input buffer */
+ checkBuffer(pData, pOffset, pLen, false);
+
+ /* Store the data */
+ if (forEncryption)
+ {
+ thePlain.Write(pData, pOffset, pLen);
+ theDataHasher.updateHash(pData, pOffset, pLen);
+ }
+ else
+ {
+ theEncData.Write(pData, pOffset, pLen);
+ }
+
+ /* No data returned */
+ return 0;
+ }
+
+ public int DoFinal(byte[] pOutput,
+ int pOffset)
+ {
+ /* Check that we have initialised */
+ checkStatus(0);
+
+ /* Check output buffer */
+ checkBuffer(pOutput, pOffset, GetOutputSize(0), true);
+
+ /* If we are encrypting */
+ if (forEncryption)
+ {
+ /* Derive the tag */
+ byte[] myTag = calculateTag();
+
+ /* encrypt the plain text */
+ int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
+
+ /* Add the tag to the output */
+ Array.Copy(myTag, 0, pOutput, pOffset + thePlain.Length, BUFLEN);
+
+ /* Reset the streams */
+ resetStreams();
+ return myDataLen;
+
+ /* else we are decrypting */
+ }
+ else
+ {
+ /* decrypt to plain text */
+ decryptPlain();
+
+ /* Release plain text */
+ int myDataLen = (int)thePlain.Length;
+ byte[] mySrc = thePlain.GetBuffer();
+ Array.Copy(mySrc, 0, pOutput, pOffset, myDataLen);
+
+ /* Reset the streams */
+ resetStreams();
+ return myDataLen;
+ }
+ }
+
+ public byte[] GetMac()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public int GetUpdateOutputSize(int pLen)
+ {
+ return 0;
+ }
+
+ public int GetOutputSize(int pLen)
+ {
+ if (forEncryption) {
+ return (int)(pLen + thePlain.Length + BUFLEN);
+ }
+ int myCurr = (int)(pLen + theEncData.Length);
+ return myCurr > BUFLEN ? myCurr - BUFLEN : 0;
+ }
+
+ public void Reset()
+ {
+ resetStreams();
+ }
+
+ /**
+ * Reset Streams.
+ */
+ private void resetStreams()
+ {
+ /* Clear the plainText buffer */
+ if (thePlain != null)
+ {
+ thePlain.Dispose();
+ thePlain = new GCMSIVCache();
+ }
+
+ /* Reset hashers */
+ theAEADHasher.reset();
+ theDataHasher.reset();
+
+ /* Recreate streams (to release memory) */
+ thePlain = new GCMSIVCache();
+ theEncData = forEncryption ? null : new GCMSIVCache();
+
+ /* Initialise AEAD if required */
+ theFlags &= ~AEAD_COMPLETE;
+ Arrays.Fill(theGHash, (byte) 0);
+ if (theInitialAEAD != null)
+ {
+ theAEADHasher.updateHash(theInitialAEAD, 0, theInitialAEAD.Length);
+ }
+ }
+
+ /**
+ * Obtain buffer length (allowing for null).
+ * @param pBuffer the buffere
+ * @return the length
+ */
+ private static int bufLength(byte[] pBuffer)
+ {
+ return pBuffer == null ? 0 : pBuffer.Length;
+ }
+
+ /**
+ * Check buffer.
+ * @param pBuffer the buffer
+ * @param pOffset the offset
+ * @param pLen the length
+ * @param pOutput is this an output buffer?
+ */
+ private static void checkBuffer(byte[] pBuffer,
+ int pOffset,
+ int pLen,
+ bool pOutput)
+ {
+ /* Access lengths */
+ int myBufLen = bufLength(pBuffer);
+ int myLast = pOffset + pLen;
+
+ /* Check for negative values and buffer overflow */
+ bool badLen = pLen < 0 || pOffset < 0 || myLast < 0;
+ if (badLen || myLast > myBufLen)
+ {
+ throw pOutput
+ ? new OutputLengthException("Output buffer too short.")
+ : new DataLengthException("Input buffer too short.");
+ }
+ }
+
+ /**
+ * encrypt data stream.
+ * @param pCounter the counter
+ * @param pTarget the target buffer
+ * @param pOffset the target offset
+ * @return the length of data encrypted
+ */
+ private int encryptPlain(byte[] pCounter,
+ byte[] pTarget,
+ int pOffset)
+ {
+ /* Access buffer and length */
+ byte[] mySrc = thePlain.GetBuffer();
+ byte[] myCounter = Arrays.Clone(pCounter);
+ myCounter[BUFLEN - 1] |= MASK;
+ byte[] myMask = new byte[BUFLEN];
+ long myRemaining = thePlain.Length;
+ int myOff = 0;
+
+ /* While we have data to process */
+ while (myRemaining > 0)
+ {
+ /* Generate the next mask */
+ theCipher.ProcessBlock(myCounter, 0, myMask, 0);
+
+ /* Xor data into mask */
+ int myLen = (int)System.Math.Min(BUFLEN, myRemaining);
+ xorBlock(myMask, mySrc, myOff, myLen);
+
+ /* Copy encrypted data to output */
+ Array.Copy(myMask, 0, pTarget, pOffset + myOff, myLen);
+
+ /* Adjust counters */
+ myRemaining -= myLen;
+ myOff += myLen;
+ incrementCounter(myCounter);
+ }
+
+ /* Return the amount of data processed */
+ return (int)thePlain.Length;
+ }
+
+ /**
+ * decrypt data stream.
+ * @throws InvalidCipherTextException on data too short or mac check failed
+ */
+ private void decryptPlain()
+ {
+ /* Access buffer and length */
+ byte[] mySrc = theEncData.GetBuffer();
+ int myRemaining = (int)theEncData.Length - BUFLEN;
+
+ /* Check for insufficient data */
+ if (myRemaining < 0)
+ {
+ throw new InvalidCipherTextException("Data too short");
+ }
+
+ /* Access counter */
+ byte[] myExpected = Arrays.CopyOfRange(mySrc, myRemaining, myRemaining + BUFLEN);
+ byte[] myCounter = Arrays.Clone(myExpected);
+ myCounter[BUFLEN - 1] |= MASK;
+ byte[] myMask = new byte[BUFLEN];
+ int myOff = 0;
+
+ /* While we have data to process */
+ while (myRemaining > 0)
+ {
+ /* Generate the next mask */
+ theCipher.ProcessBlock(myCounter, 0, myMask, 0);
+
+ /* Xor data into mask */
+ int myLen = System.Math.Min(BUFLEN, myRemaining);
+ xorBlock(myMask, mySrc, myOff, myLen);
+
+ /* Write data to plain dataStream */
+ thePlain.Write(myMask, 0, myLen);
+ theDataHasher.updateHash(myMask, 0, myLen);
+
+ /* Adjust counters */
+ myRemaining -= myLen;
+ myOff += myLen;
+ incrementCounter(myCounter);
+ }
+
+ /* Derive and check the tag */
+ byte[] myTag = calculateTag();
+ if (!Arrays.ConstantTimeAreEqual(myTag, myExpected))
+ {
+ Reset();
+ throw new InvalidCipherTextException("mac check failed");
+ }
+ }
+
+ /**
+ * calculate tag.
+ * @return the calculated tag
+ */
+ private byte[] calculateTag()
+ {
+ /* Complete the hash */
+ theDataHasher.completeHash();
+ byte[] myPolyVal = completePolyVal();
+
+ /* calculate polyVal */
+ byte[] myResult = new byte[BUFLEN];
+
+ /* Fold in the nonce */
+ for (int i = 0; i < NONCELEN; i++)
+ {
+ myPolyVal[i] ^= theNonce[i];
+ }
+
+ /* Clear top bit */
+ myPolyVal[BUFLEN - 1] &= (byte)(MASK - 1);
+
+ /* Calculate tag and return it */
+ theCipher.ProcessBlock(myPolyVal, 0, myResult, 0);
+ return myResult;
+ }
+
+ /**
+ * complete polyVAL.
+ * @return the calculated value
+ */
+ private byte[] completePolyVal()
+ {
+ /* Build the polyVal result */
+ byte[] myResult = new byte[BUFLEN];
+ gHashLengths();
+ fillReverse(theGHash, 0, BUFLEN, myResult);
+ return myResult;
+ }
+
+ /**
+ * process lengths.
+ */
+ private void gHashLengths()
+ {
+ /* Create reversed bigEndian buffer to keep it simple */
+ byte[] myIn = new byte[BUFLEN];
+ Pack.UInt64_To_BE((ulong)Bytes.SIZE * theDataHasher.getBytesProcessed(), myIn, 0);
+ Pack.UInt64_To_BE((ulong)Bytes.SIZE * theAEADHasher.getBytesProcessed(), myIn, (int)Longs.BYTES);
+
+ /* hash value */
+ gHASH(myIn);
+ }
+
+ /**
+ * perform the next GHASH step.
+ * @param pNext the next value
+ */
+ private void gHASH(byte[] pNext)
+ {
+ xorBlock(theGHash, pNext);
+ theMultiplier.MultiplyH(theGHash);
+ }
+
+ /**
+ * Byte reverse a buffer.
+ * @param pInput the input buffer
+ * @param pOffset the offset
+ * @param pLength the length of data (<= BUFLEN)
+ * @param pOutput the output buffer
+ */
+ private static void fillReverse(byte[] pInput,
+ int pOffset,
+ int pLength,
+ byte[] pOutput)
+ {
+ /* Loop through the buffer */
+ for (int i = 0, j = BUFLEN - 1; i < pLength; i++, j--)
+ {
+ /* Copy byte */
+ pOutput[j] = pInput[pOffset + i];
+ }
+ }
+
+ /**
+ * xor a full block buffer.
+ * @param pLeft the left operand and result
+ * @param pRight the right operand
+ */
+ private static void xorBlock(byte[] pLeft,
+ byte[] pRight)
+ {
+ /* Loop through the bytes */
+ for (int i = 0; i < BUFLEN; i++)
+ {
+ pLeft[i] ^= pRight[i];
+ }
+ }
+
+ /**
+ * xor a partial block buffer.
+ * @param pLeft the left operand and result
+ * @param pRight the right operand
+ * @param pOffset the offset in the right operand
+ * @param pLength the length of data in the right operand
+ */
+ private static void xorBlock(byte[] pLeft,
+ byte[] pRight,
+ int pOffset,
+ int pLength)
+ {
+ /* Loop through the bytes */
+ for (int i = 0; i < pLength; i++)
+ {
+ pLeft[i] ^= pRight[i + pOffset];
+ }
+ }
+
+ /**
+ * increment the counter.
+ * @param pCounter the counter to increment
+ */
+ private static void incrementCounter(byte[] pCounter)
+ {
+ /* Loop through the bytes incrementing counter */
+ for (int i = 0; i < Integers.BYTES; i++)
+ {
+ if (++pCounter[i] != 0)
+ {
+ break;
+ }
+ }
+ }
+
+ /**
+ * multiply by X.
+ * @param pValue the value to adjust
+ */
+ private static void mulX(byte[] pValue)
+ {
+ /* Loop through the bytes */
+ byte myMask = (byte) 0;
+ for (int i = 0; i < BUFLEN; i++)
+ {
+ byte myValue = pValue[i];
+ pValue[i] = (byte) (((myValue >> 1) & ~MASK) | myMask);
+ myMask = (byte)((myValue & 1) == 0 ? 0 : MASK);
+ }
+
+ /* Xor in addition if last bit was set */
+ if (myMask != 0)
+ {
+ pValue[0] ^= ADD;
+ }
+ }
+
+ /**
+ * Derive Keys.
+ * @param pKey the keyGeneration key
+ */
+ private void deriveKeys(KeyParameter pKey)
+ {
+ /* Create the buffers */
+ byte[] myIn = new byte[BUFLEN];
+ byte[] myOut = new byte[BUFLEN];
+ byte[] myResult = new byte[BUFLEN];
+ byte[] myEncKey = new byte[pKey.GetKey().Length];
+
+ /* Prepare for encryption */
+ Array.Copy(theNonce, 0, myIn, BUFLEN - NONCELEN, NONCELEN);
+ theCipher.Init(true, pKey);
+
+ /* Derive authentication key */
+ int myOff = 0;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myResult, myOff, HALFBUFLEN);
+ myIn[0]++;
+ myOff += HALFBUFLEN;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myResult, myOff, HALFBUFLEN);
+
+ /* Derive encryption key */
+ myIn[0]++;
+ myOff = 0;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
+ myIn[0]++;
+ myOff += HALFBUFLEN;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
+
+ /* If we have a 32byte key */
+ if (myEncKey.Length == BUFLEN << 1)
+ {
+ /* Derive remainder of encryption key */
+ myIn[0]++;
+ myOff += HALFBUFLEN;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
+ myIn[0]++;
+ myOff += HALFBUFLEN;
+ theCipher.ProcessBlock(myIn, 0, myOut, 0);
+ Array.Copy(myOut, 0, myEncKey, myOff, HALFBUFLEN);
+ }
+
+ /* Initialise the Cipher */
+ theCipher.Init(true, new KeyParameter(myEncKey));
+
+ /* Initialise the multiplier */
+ fillReverse(myResult, 0, BUFLEN, myOut);
+ mulX(myOut);
+ theMultiplier.Init(myOut);
+ theFlags |= INIT;
+ }
+
+ /**
+ * GCMSIVCache.
+ */
+ class GCMSIVCache
+ : MemoryOutputStream
+ {
+ /**
+ * number of bytes hashed.
+ */
+ private int numHashed;
+
+ /**
+ * Constructor.
+ */
+ internal GCMSIVCache()
+ {
+ }
+ }
+
+ /**
+ * Hash Control.
+ */
+ class GCMSIVHasher
+ {
+ /**
+ * Cache.
+ */
+ private readonly byte[] theBuffer = new byte[BUFLEN];
+
+ /**
+ * Single byte cache.
+ */
+ private readonly byte[] theByte = new byte[1];
+
+ /**
+ * Count of active bytes in cache.
+ */
+ private int numActive;
+
+ /**
+ * Count of hashed bytes.
+ */
+ private ulong numHashed;
+
+ private readonly GcmSivBlockCipher parent;
+
+ internal GCMSIVHasher(GcmSivBlockCipher parent)
+ {
+ this.parent = parent;
+ }
+
+ /**
+ * Obtain the count of bytes hashed.
+ * @return the count
+ */
+ internal ulong getBytesProcessed()
+ {
+ return numHashed;
+ }
+
+ /**
+ * Reset the hasher.
+ */
+ internal void reset()
+ {
+ numActive = 0;
+ numHashed = 0;
+ }
+
+ /**
+ * update hash.
+ * @param pByte the byte
+ */
+ internal void updateHash(byte pByte)
+ {
+ theByte[0] = pByte;
+ updateHash(theByte, 0, 1);
+ }
+
+ /**
+ * update hash.
+ * @param pBuffer the buffer
+ * @param pOffset the offset within the buffer
+ * @param pLen the length of data
+ */
+ internal void updateHash(byte[] pBuffer,
+ int pOffset,
+ int pLen)
+ {
+ /* If we should process the cache */
+ int mySpace = BUFLEN - numActive;
+ int numProcessed = 0;
+ int myRemaining = pLen;
+ if (numActive > 0
+ && pLen >= mySpace)
+ {
+ /* Copy data into the cache and hash it */
+ Array.Copy(pBuffer, pOffset, theBuffer, numActive, mySpace);
+ fillReverse(theBuffer, 0, BUFLEN, parent.theReverse);
+ parent.gHASH(parent.theReverse);
+
+ /* Adjust counters */
+ numProcessed += mySpace;
+ myRemaining -= mySpace;
+ numActive = 0;
+ }
+
+ /* While we have full blocks */
+ while (myRemaining >= BUFLEN)
+ {
+ /* Access the next data */
+ fillReverse(pBuffer, pOffset + numProcessed, BUFLEN, parent.theReverse);
+ parent.gHASH(parent.theReverse);
+
+ /* Adjust counters */
+ numProcessed += mySpace;
+ myRemaining -= mySpace;
+ }
+
+ /* If we have remaining data */
+ if (myRemaining > 0)
+ {
+ /* Copy data into the cache */
+ Array.Copy(pBuffer, pOffset + numProcessed, theBuffer, numActive, myRemaining);
+ numActive += myRemaining;
+ }
+
+ /* Adjust the number of bytes processed */
+ numHashed += (ulong)pLen;
+ }
+
+ /**
+ * complete hash.
+ */
+ internal void completeHash()
+ {
+ /* If we have remaining data */
+ if (numActive > 0)
+ {
+ /* Access the next data */
+ Arrays.Fill(parent.theReverse, (byte) 0);
+ fillReverse(theBuffer, 0, numActive, parent.theReverse);
+
+ /* hash value */
+ parent.gHASH(parent.theReverse);
+ }
+ }
+ }
+ }
+}
diff --git a/crypto/src/crypto/parameters/FpeParameters.cs b/crypto/src/crypto/parameters/FpeParameters.cs
new file mode 100644
index 000000000..ab8833312
--- /dev/null
+++ b/crypto/src/crypto/parameters/FpeParameters.cs
@@ -0,0 +1,49 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Parameters
+{
+public sealed class FpeParameters
+ : ICipherParameters
+{
+ private readonly KeyParameter key;
+ private readonly int radix;
+ private readonly byte[] tweak;
+ private readonly bool useInverse;
+
+ public FpeParameters(KeyParameter key, int radix, byte[] tweak): this(key, radix, tweak, false)
+ {
+
+ }
+
+ public FpeParameters(KeyParameter key, int radix, byte[] tweak, bool useInverse)
+ {
+ this.key = key;
+ this.radix = radix;
+ this.tweak = Arrays.Clone(tweak);
+ this.useInverse = useInverse;
+ }
+
+ public KeyParameter Key
+ {
+ get { return key; }
+ }
+
+ public int Radix
+ {
+ get { return radix; }
+ }
+
+ public bool UseInverseFunction
+ {
+ get { return useInverse; }
+ }
+
+ public byte[] GetTweak()
+ {
+ return Arrays.Clone(tweak);
+ }
+}
+}
diff --git a/crypto/src/crypto/util/BasicAlphabetMapper.cs b/crypto/src/crypto/util/BasicAlphabetMapper.cs
new file mode 100644
index 000000000..bd0411e30
--- /dev/null
+++ b/crypto/src/crypto/util/BasicAlphabetMapper.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections;
+
+namespace Org.BouncyCastle.Crypto.Utilities
+{
+/**
+ * A basic alphabet mapper that just creates a mapper based on the
+ * passed in array of characters.
+ */
+ public class BasicAlphabetMapper
+ : IAlphabetMapper
+{
+ private Hashtable indexMap = new Hashtable();
+ private Hashtable charMap = new Hashtable();
+
+ /**
+ * Base constructor.
+ *
+ * @param alphabet a string of characters making up the alphabet.
+ */
+ public BasicAlphabetMapper(string alphabet) :
+ this(alphabet.ToCharArray())
+ {
+ }
+
+ /**
+ * Base constructor.
+ *
+ * @param alphabet an array of characters making up the alphabet.
+ */
+ public BasicAlphabetMapper(char[] alphabet)
+ {
+ for (int i = 0; i != alphabet.Length; i++)
+ {
+ if (indexMap.ContainsKey(alphabet[i]))
+ {
+ throw new ArgumentException("duplicate key detected in alphabet: " + alphabet[i]);
+ }
+ indexMap.Add(alphabet[i], i);
+ charMap.Add(i, alphabet[i]);
+ }
+ }
+
+ public int Radix
+ {
+ get { return indexMap.Count; }
+ }
+
+ public byte[] ConvertToIndexes(char[] input)
+ {
+ byte[] outBuf;
+
+ if (indexMap.Count <= 256)
+ {
+ outBuf = new byte[input.Length];
+ for (int i = 0; i != input.Length; i++)
+ {
+ outBuf[i] = (byte)indexMap[input[i]];
+ }
+ }
+ else
+ {
+ outBuf = new byte[input.Length * 2];
+ for (int i = 0; i != input.Length; i++)
+ {
+ int idx = (int)indexMap[input[i]];
+ outBuf[i * 2] = (byte)((idx >> 8) & 0xff);
+ outBuf[i * 2 + 1] = (byte)(idx & 0xff);
+ }
+ }
+
+ return outBuf;
+ }
+
+ public char[] ConvertToChars(byte[] input)
+ {
+ char[] outBuf;
+
+ if (charMap.Count <= 256)
+ {
+ outBuf = new char[input.Length];
+ for (int i = 0; i != input.Length; i++)
+ {
+ outBuf[i] = (char)charMap[input[i] & 0xff];
+ }
+ }
+ else
+ {
+ if ((input.Length & 0x1) != 0)
+ {
+ throw new ArgumentException("two byte radix and input string odd.Length");
+ }
+
+ outBuf = new char[input.Length / 2];
+ for (int i = 0; i != input.Length; i += 2)
+ {
+ outBuf[i / 2] = (char)charMap[((input[i] << 8) & 0xff00) | (input[i + 1] & 0xff)];
+ }
+ }
+
+ return outBuf;
+ }
+}
+}
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 784d45efb..78c4e8ffc 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -467,6 +467,17 @@ namespace Org.BouncyCastle.Utilities
return data == null ? null : (byte[])data.Clone();
}
+ public static short[] Clone(short[] data)
+ {
+ return data == null ? null : (short[])data.Clone();
+ }
+
+ [CLSCompliantAttribute(false)]
+ public static ushort[] Clone(ushort[] data)
+ {
+ return data == null ? null : (ushort[])data.Clone();
+ }
+
public static int[] Clone(int[] data)
{
return data == null ? null : (int[])data.Clone();
@@ -694,6 +705,19 @@ namespace Org.BouncyCastle.Utilities
return rv;
}
+ public static ushort[] Concatenate(ushort[] a, ushort[] b)
+ {
+ if (a == null)
+ return Clone(b);
+ if (b == null)
+ return Clone(a);
+
+ ushort[] rv = new ushort[a.Length + b.Length];
+ Array.Copy(a, 0, rv, 0, a.Length);
+ Array.Copy(b, 0, rv, a.Length, b.Length);
+ return rv;
+ }
+
public static byte[] ConcatenateAll(params byte[][] vs)
{
byte[][] nonNull = new byte[vs.Length][];
diff --git a/crypto/src/util/Bytes.cs b/crypto/src/util/Bytes.cs
new file mode 100644
index 000000000..0d73d67ae
--- /dev/null
+++ b/crypto/src/util/Bytes.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Org.BouncyCastle.Utilities
+{
+ public abstract class Bytes
+ {
+ public static readonly uint BYTES = 1;
+ public static readonly uint SIZE = 8;
+ }
+}
diff --git a/crypto/src/util/Integers.cs b/crypto/src/util/Integers.cs
index b243b88b2..b7bd25ce6 100644
--- a/crypto/src/util/Integers.cs
+++ b/crypto/src/util/Integers.cs
@@ -6,6 +6,9 @@ namespace Org.BouncyCastle.Utilities
{
public abstract class Integers
{
+ public static readonly uint BYTES = 4;
+ public static readonly uint SIZE = 32;
+
private static readonly byte[] DeBruijnTZ = {
0x00, 0x01, 0x02, 0x18, 0x03, 0x13, 0x06, 0x19, 0x16, 0x04, 0x14, 0x0A,
0x10, 0x07, 0x0C, 0x1A, 0x1F, 0x17, 0x12, 0x05, 0x15, 0x09, 0x0F, 0x0B,
diff --git a/crypto/src/util/Longs.cs b/crypto/src/util/Longs.cs
index d206c1f81..892e57137 100644
--- a/crypto/src/util/Longs.cs
+++ b/crypto/src/util/Longs.cs
@@ -6,6 +6,9 @@ namespace Org.BouncyCastle.Utilities
{
public abstract class Longs
{
+ public static readonly uint BYTES = 8;
+ public static readonly uint SIZE = 64;
+
public static long Reverse(long i)
{
i = (long)Bits.BitPermuteStepSimple((ulong)i, 0x5555555555555555UL, 1);
|