summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Hook <dgh@cryptoworkshop.com>2021-03-19 15:25:57 +1100
committerDavid Hook <dgh@cryptoworkshop.com>2021-03-19 15:25:57 +1100
commit504554b718911423f64b9d7235eee6fdba9241ee (patch)
treec7e41b1a3769b9cfba3dea124f434d551a9c16ed
parentAdd SECURITY.md (diff)
downloadBouncyCastle.NET-ed25519-504554b718911423f64b9d7235eee6fdba9241ee.tar.xz
first cut FPE, TupleHash, ParallelHash, and GCM-SIV
-rw-r--r--crypto/src/crypto/IAlphabetMapper.cs32
-rw-r--r--crypto/src/crypto/digests/CSHAKEDigest.cs6
-rw-r--r--crypto/src/crypto/digests/KeccakDigest.cs2
-rw-r--r--crypto/src/crypto/digests/ParallelHash.cs210
-rw-r--r--crypto/src/crypto/digests/TupleHash.cs134
-rw-r--r--crypto/src/crypto/digests/XofUtils.cs16
-rw-r--r--crypto/src/crypto/fpe/FpeEngine.cs121
-rw-r--r--crypto/src/crypto/fpe/FpeFf1Engine.cs70
-rw-r--r--crypto/src/crypto/fpe/FpeFf3_1Engine.cs75
-rw-r--r--crypto/src/crypto/fpe/SP80038G.cs698
-rw-r--r--crypto/src/crypto/modes/GcmSivBlockCipher.cs963
-rw-r--r--crypto/src/crypto/parameters/FpeParameters.cs49
-rw-r--r--crypto/src/crypto/util/BasicAlphabetMapper.cs104
-rw-r--r--crypto/src/util/Arrays.cs24
-rw-r--r--crypto/src/util/Bytes.cs10
-rw-r--r--crypto/src/util/Integers.cs3
-rw-r--r--crypto/src/util/Longs.cs3
-rw-r--r--crypto/test/src/crypto/test/GcmSivTest.cs458
-rw-r--r--crypto/test/src/crypto/test/ParallelHashTest.cs141
-rw-r--r--crypto/test/src/crypto/test/SP80038GTest.cs582
-rw-r--r--crypto/test/src/crypto/test/SentrixSigningSha256Hard.pfxbin0 -> 2778 bytes
-rw-r--r--crypto/test/src/crypto/test/SentrixSigningSha256Soft.pfxbin0 -> 2693 bytes
-rw-r--r--crypto/test/src/crypto/test/TupleHashTest.cs108
23 files changed, 3808 insertions, 1 deletions
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);
diff --git a/crypto/test/src/crypto/test/GcmSivTest.cs b/crypto/test/src/crypto/test/GcmSivTest.cs
new file mode 100644
index 000000000..03f1003f4
--- /dev/null
+++ b/crypto/test/src/crypto/test/GcmSivTest.cs
@@ -0,0 +1,458 @@
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Encodings;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+
+        /**
+         * ISO vector test for Whirlpool
+         *
+         */
+        [TestFixture]
+public class GcmSivTest
+        : SimpleTest
+{
+    public override string Name
+    {
+        get { return "GCM-SIV"; }
+    }
+
+    public override void PerformTest()
+    {
+        new AESGcmSiv128Test1().testTheCipher(this);
+        new AESGcmSiv128Test2().testTheCipher(this);
+        new AESGcmSiv128Test3().testTheCipher(this);
+        new AESGcmSiv256Test1().testTheCipher(this);
+        new AESGcmSiv256Test2().testTheCipher(this);
+        new AESGcmSiv256Test3().testTheCipher(this);
+        new AESGcmSiv256Test4().testTheCipher(this);
+    }
+
+    /**
+     * Test the GcmSivCipher against the results.
+     * @param pCipher the cipher to test.
+     * @param pKey the key to test
+     * @param pNonce the nonce
+     * @param pAEAD the AEAD
+     * @param pData the data to test
+     * @param pExpected the expected results
+     */
+    void testSIVCipher(GcmSivBlockCipher pCipher,
+                       string pKey,
+                       string pNonce,
+                       string pAEAD,
+                       string pData,
+                       string pExpected)
+    {
+        /* protect against exceptions */
+        try
+        {
+            /* Access the key and the data */
+            KeyParameter myKey = new KeyParameter(Hex.Decode(pKey));
+            byte[] myNonce = Hex.Decode(pNonce);
+            byte[] myAEAD = Hex.Decode(pAEAD);
+            byte[] myData = Hex.Decode(pData);
+
+            /* Initialise the cipher */
+            AeadParameters myParams = new AeadParameters(myKey, 128, myNonce, myAEAD);
+            pCipher.Init(true, myParams);
+
+            /* Create the output buffers */
+            byte[] myOutput = new byte[pCipher.GetOutputSize(myData.Length)];
+            byte[] myFinal = new byte[myData.Length];
+
+            /* Process the data */
+            pCipher.ProcessBytes(myData, 0, myData.Length, null, 0);
+            pCipher.DoFinal(myOutput, 0);
+
+            /* Check the encryption */
+            byte[] myExpected = Hex.Decode(pExpected);
+            IsTrue("Encryption mismatch", Arrays.AreEqual(myExpected, myOutput));
+
+            /* Repeat processing byte at a time */
+            for (int i = 0; i != myData.Length; i++)
+            {
+                pCipher.ProcessByte(myData[i], null, 0);
+            }
+            pCipher.DoFinal(myOutput, 0);
+            IsTrue("Encryption mismatch", Arrays.AreEqual(myExpected, myOutput));
+
+            /* Re-initialise the cipher */
+            pCipher.Init(false, myParams);
+            pCipher.ProcessBytes(myOutput, 0, myOutput.Length, null, 0);
+            pCipher.DoFinal(myFinal, 0);
+            IsTrue("Decryption mismatch", Arrays.AreEqual(myData, myFinal));
+        }
+        catch (InvalidCipherTextException e)
+        {
+            Fail("Bad Text", e);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-128 Set 1.
+     */
+    class AESGcmSiv128Test1
+    {
+        private readonly string EMPTY       = "";
+        private readonly string KEY_1       = "01000000000000000000000000000000";
+        private readonly string NONCE_1     = "030000000000000000000000";
+        private readonly string DATA_8      = "0100000000000000";
+        private readonly string DATA_12     = "010000000000000000000000";
+        private readonly string DATA_16     = "01000000000000000000000000000000";
+        private readonly string DATA_32     = "01000000000000000000000000000000" + "02000000000000000000000000000000";
+        private readonly string DATA_48     = "01000000000000000000000000000000" + "02000000000000000000000000000000"
+                + "03000000000000000000000000000000";
+        private readonly string DATA_64     = "01000000000000000000000000000000" + "02000000000000000000000000000000"
+                + "03000000000000000000000000000000" + "04000000000000000000000000000000";
+        private readonly string EXPECTED_1  = "dc20e2d83f25705bb49e439eca56de25";
+        private readonly string EXPECTED_2  = "b5d839330ac7b786578782fff6013b81" + "5b287c22493a364c";
+        private readonly string EXPECTED_3  = "7323ea61d05932260047d942a4978db3" + "57391a0bc4fdec8b0d106639";
+        private readonly string EXPECTED_4  = "743f7c8077ab25f8624e2e948579cf77" + "303aaf90f6fe21199c6068577437a0c4";
+        private readonly string EXPECTED_5  = "84e07e62ba83a6585417245d7ec413a9" + "fe427d6315c09b57ce45f2e3936a9445"
+                + "1a8e45dcd4578c667cd86847bf6155ff";
+        private readonly string EXPECTED_6  = "3fd24ce1f5a67b75bf2351f181a475c7" + "b800a5b4d3dcf70106b1eea82fa1d64d"
+                + "f42bf7226122fa92e17a40eeaac1201b" + "5e6e311dbf395d35b0fe39c2714388f8";
+        private readonly string EXPECTED_7  = "2433668f1058190f6d43e360f4f35cd8" + "e475127cfca7028ea8ab5c20f7ab2af0"
+                + "2516a2bdcbc08d521be37ff28c152bba" + "36697f25b4cd169c6590d1dd39566d3f"
+                + "8a263dd317aa88d56bdf3936dba75bb8";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, EMPTY, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_8, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_12, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_16, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_32, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_48, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_64, EXPECTED_7);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-128 Set 2.
+     */
+    class AESGcmSiv128Test2
+    {
+        private  readonly string KEY_1       = "01000000000000000000000000000000";
+        private  readonly string NONCE_1     = "030000000000000000000000";
+        private  readonly string AEAD_1      = "01";
+        private  readonly string AEAD_12     = "010000000000000000000000";
+        private  readonly string AEAD_18     = "01000000000000000000000000000000" + "0200";
+        private  readonly string AEAD_20     = "01000000000000000000000000000000" + "02000000";
+        private  readonly string DATA_4      = "02000000";
+        private  readonly string DATA_8      = "0200000000000000";
+        private  readonly string DATA_12     = "020000000000000000000000";
+        private  readonly string DATA_16     = "02000000000000000000000000000000";
+        private  readonly string DATA_18     = "03000000000000000000000000000000" + "0400";
+        private  readonly string DATA_20     = "03000000000000000000000000000000" + "04000000";
+        private  readonly string DATA_32     = "02000000000000000000000000000000" + "03000000000000000000000000000000";
+        private  readonly string DATA_48     = "02000000000000000000000000000000" + "03000000000000000000000000000000"
+                + "04000000000000000000000000000000";
+        private  readonly string DATA_64     = "02000000000000000000000000000000" + "03000000000000000000000000000000"
+                + "04000000000000000000000000000000" + "05000000000000000000000000000000";
+        private  readonly string EXPECTED_1  = "1e6daba35669f4273b0a1a2560969cdf" + "790d99759abd1508";
+        private  readonly string EXPECTED_2  = "296c7889fd99f41917f4462008299c51" + "02745aaa3a0c469fad9e075a";
+        private  readonly string EXPECTED_3  = "e2b0c5da79a901c1745f700525cb335b" + "8f8936ec039e4e4bb97ebd8c4457441f";
+        private  readonly string EXPECTED_4  = "620048ef3c1e73e57e02bb8562c416a3" + "19e73e4caac8e96a1ecb2933145a1d71"
+                + "e6af6a7f87287da059a71684ed3498e1";
+        private  readonly string EXPECTED_5  = "50c8303ea93925d64090d07bd109dfd9" + "515a5a33431019c17d93465999a8b005"
+                + "3201d723120a8562b838cdff25bf9d1e" + "6a8cc3865f76897c2e4b245cf31c51f2";
+        private  readonly string EXPECTED_6  = "2f5c64059db55ee0fb847ed513003746" + "aca4e61c711b5de2e7a77ffd02da42fe"
+                + "ec601910d3467bb8b36ebbaebce5fba3" + "0d36c95f48a3e7980f0e7ac299332a80"
+                + "cdc46ae475563de037001ef84ae21744";
+        private  readonly string EXPECTED_7  = "a8fe3e8707eb1f84fb28f8cb73de8e99" + "e2f48a14";
+        private  readonly string EXPECTED_8  = "6bb0fecf5ded9b77f902c7d5da236a43" + "91dd029724afc9805e976f451e6d87f6"
+                + "fe106514";
+        private  readonly string EXPECTED_9  = "44d0aaf6fb2f1f34add5e8064e83e12a" + "2adabff9b2ef00fb47920cc72a0c0f13"
+                + "b9fd";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_8, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_12, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_16, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_32, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_48, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_64, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_12, DATA_4, EXPECTED_7);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_18, DATA_20, EXPECTED_8);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_20, DATA_18, EXPECTED_9);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-128 Set 3.
+     */
+    class AESGcmSiv128Test3
+    {
+        private readonly string EMPTY       = "";
+        private readonly string KEY_1       = "e66021d5eb8e4f4066d4adb9c33560e4";
+        private readonly string KEY_2       = "36864200e0eaf5284d884a0e77d31646";
+        private readonly string KEY_3       = "aedb64a6c590bc84d1a5e269e4b47801";
+        private readonly string KEY_4       = "d5cc1fd161320b6920ce07787f86743b";
+        private readonly string KEY_5       = "b3fed1473c528b8426a582995929a149";
+        private readonly string KEY_6       = "2d4ed87da44102952ef94b02b805249b";
+        private readonly string KEY_7       = "bde3b2f204d1e9f8b06bc47f9745b3d1";
+        private readonly string KEY_8       = "f901cfe8a69615a93fdf7a98cad48179";
+        private readonly string NONCE_1     = "f46e44bb3da0015c94f70887";
+        private readonly string NONCE_2     = "bae8e37fc83441b16034566b";
+        private readonly string NONCE_3     = "afc0577e34699b9e671fdd4f";
+        private readonly string NONCE_4     = "275d1ab32f6d1f0434d8848c";
+        private readonly string NONCE_5     = "9e9ad8780c8d63d0ab4149c0";
+        private readonly string NONCE_6     = "ac80e6f61455bfac8308a2d4";
+        private readonly string NONCE_7     = "ae06556fb6aa7890bebc18fe";
+        private readonly string NONCE_8     = "6245709fb18853f68d833640";
+        private readonly string AEAD_2      = "46bb91c3c5";
+        private readonly string AEAD_3      = "fc880c94a95198874296";
+        private readonly string AEAD_4      = "046787f3ea22c127aaf195d1894728";
+        private readonly string AEAD_5      = "c9882e5386fd9f92ec489c8fde2be2cf" + "97e74e93";
+        private readonly string AEAD_6      = "2950a70d5a1db2316fd568378da107b5" + "2b0da55210cc1c1b0a";
+        private readonly string AEAD_7      = "1860f762ebfbd08284e421702de0de18" + "baa9c9596291b08466f37de21c7f";
+        private readonly string AEAD_8      = "7576f7028ec6eb5ea7e298342a94d4b2" + "02b370ef9768ec6561c4fe6b7e7296fa"
+                + "859c21";
+        private readonly string DATA_2      = "7a806c";
+        private readonly string DATA_3      = "bdc66f146545";
+        private readonly string DATA_4      = "1177441f195495860f";
+        private readonly string DATA_5      = "9f572c614b4745914474e7c7";
+        private readonly string DATA_6      = "0d8c8451178082355c9e940fea2f58";
+        private readonly string DATA_7      = "6b3db4da3d57aa94842b9803a96e07fb" + "6de7";
+        private readonly string DATA_8      = "e42a3c02c25b64869e146d7b233987bd" + "dfc240871d";
+        private readonly string EXPECTED_1  = "a4194b79071b01a87d65f706e3949578";
+        private readonly string EXPECTED_2  = "af60eb711bd85bc1e4d3e0a462e074ee" + "a428a8";
+        private readonly string EXPECTED_3  = "bb93a3e34d3cd6a9c45545cfc11f03ad" + "743dba20f966";
+        private readonly string EXPECTED_4  = "4f37281f7ad12949d01d02fd0cd174c8" + "4fc5dae2f60f52fd2b";
+        private readonly string EXPECTED_5  = "f54673c5ddf710c745641c8bc1dc2f87" + "1fb7561da1286e655e24b7b0";
+        private readonly string EXPECTED_6  = "c9ff545e07b88a015f05b274540aa183" + "b3449b9f39552de99dc214a1190b0b";
+        private readonly string EXPECTED_7  = "6298b296e24e8cc35dce0bed484b7f30" + "d5803e377094f04709f64d7b985310a4"
+                + "db84";
+        private readonly string EXPECTED_8  = "391cc328d484a4f46406181bcd62efd9" + "b3ee197d052d15506c84a9edd65e13e9"
+                + "d24a2a6e70";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, EMPTY, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_2, NONCE_2, AEAD_2, DATA_2, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_3, NONCE_3, AEAD_3, DATA_3, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_4, NONCE_4, AEAD_4, DATA_4, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_5, NONCE_5, AEAD_5, DATA_5, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_6, NONCE_6, AEAD_6, DATA_6, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_7, NONCE_7, AEAD_7, DATA_7, EXPECTED_7);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_8, NONCE_8, AEAD_8, DATA_8, EXPECTED_8);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-256 Set 1.
+     */
+    class AESGcmSiv256Test1
+    {
+        private readonly string EMPTY       = "";
+        private readonly string KEY_1       = "01000000000000000000000000000000" + "00000000000000000000000000000000";
+        private readonly string NONCE_1     = "030000000000000000000000";
+        private readonly string DATA_8      = "0100000000000000";
+        private readonly string DATA_12     = "010000000000000000000000";
+        private readonly string DATA_16     = "01000000000000000000000000000000";
+        private readonly string DATA_32     = "01000000000000000000000000000000" + "02000000000000000000000000000000";
+        private readonly string DATA_48     = "01000000000000000000000000000000" + "02000000000000000000000000000000"
+                + "03000000000000000000000000000000";
+        private readonly string DATA_64     = "01000000000000000000000000000000" + "02000000000000000000000000000000"
+                + "03000000000000000000000000000000" + "04000000000000000000000000000000";
+        private readonly string EXPECTED_1  = "07f5f4169bbf55a8400cd47ea6fd400f";
+        private readonly string EXPECTED_2  = "c2ef328e5c71c83b843122130f7364b7" + "61e0b97427e3df28";
+        private readonly string EXPECTED_3  = "9aab2aeb3faa0a34aea8e2b18ca50da9" + "ae6559e48fd10f6e5c9ca17e";
+        private readonly string EXPECTED_4  = "85a01b63025ba19b7fd3ddfc033b3e76" + "c9eac6fa700942702e90862383c6c366";
+        private readonly string EXPECTED_5  = "4a6a9db4c8c6549201b9edb53006cba8" + "21ec9cf850948a7c86c68ac7539d027f"
+                + "e819e63abcd020b006a976397632eb5d";
+        private readonly string EXPECTED_6  = "c00d121893a9fa603f48ccc1ca3c57ce" + "7499245ea0046db16c53c7c66fe717e3"
+                + "9cf6c748837b61f6ee3adcee17534ed5" + "790bc96880a99ba804bd12c0e6a22cc4";
+        private readonly string EXPECTED_7  = "c2d5160a1f8683834910acdafc41fbb1" + "632d4a353e8b905ec9a5499ac34f96c7"
+                + "e1049eb080883891a4db8caaa1f99dd0" + "04d80487540735234e3744512c6f90ce"
+                + "112864c269fc0d9d88c61fa47e39aa08";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, EMPTY, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_8, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_12, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_16, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_32, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_48, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_64, EXPECTED_7);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-256 Set 2.
+     */
+    class AESGcmSiv256Test2
+    {
+        private readonly string KEY_1       = "01000000000000000000000000000000" + "00000000000000000000000000000000";
+        private readonly string NONCE_1     = "030000000000000000000000";
+        private readonly string AEAD_1      = "01";
+        private readonly string AEAD_12     = "010000000000000000000000";
+        private readonly string AEAD_18     = "01000000000000000000000000000000" + "0200";
+        private readonly string AEAD_20     = "01000000000000000000000000000000" + "02000000";
+        private readonly string DATA_4      = "02000000";
+        private readonly string DATA_8      = "0200000000000000";
+        private readonly string DATA_12     = "020000000000000000000000";
+        private readonly string DATA_16     = "02000000000000000000000000000000";
+        private readonly string DATA_18     = "03000000000000000000000000000000" + "0400";
+        private readonly string DATA_20     = "03000000000000000000000000000000" + "04000000";
+        private readonly string DATA_32     = "02000000000000000000000000000000" + "03000000000000000000000000000000";
+        private readonly string DATA_48     = "02000000000000000000000000000000" + "03000000000000000000000000000000"
+                + "04000000000000000000000000000000";
+        private readonly string DATA_64     = "02000000000000000000000000000000" + "03000000000000000000000000000000"
+                + "04000000000000000000000000000000" + "05000000000000000000000000000000";
+        private readonly string EXPECTED_1  = "1de22967237a813291213f267e3b452f" + "02d01ae33e4ec854";
+        private readonly string EXPECTED_2  = "163d6f9cc1b346cd453a2e4cc1a4a19a" + "e800941ccdc57cc8413c277f";
+        private readonly string EXPECTED_3  = "c91545823cc24f17dbb0e9e807d5ec17" + "b292d28ff61189e8e49f3875ef91aff7";
+        private readonly string EXPECTED_4  = "07dad364bfc2b9da89116d7bef6daaaf" + "6f255510aa654f920ac81b94e8bad365"
+                + "aea1bad12702e1965604374aab96dbbc";
+        private readonly string EXPECTED_5  = "c67a1f0f567a5198aa1fcc8e3f213143" + "36f7f51ca8b1af61feac35a86416fa47"
+                + "fbca3b5f749cdf564527f2314f42fe25" + "03332742b228c647173616cfd44c54eb";
+        private readonly string EXPECTED_6  = "67fd45e126bfb9a79930c43aad2d3696" + "7d3f0e4d217c1e551f59727870beefc9"
+                + "8cb933a8fce9de887b1e40799988db1f" + "c3f91880ed405b2dd298318858467c89"
+                + "5bde0285037c5de81e5b570a049b62a0";
+        private readonly string EXPECTED_7  = "22b3f4cd1835e517741dfddccfa07fa4" + "661b74cf";
+        private readonly string EXPECTED_8  = "43dd0163cdb48f9fe3212bf61b201976" + "067f342bb879ad976d8242acc188ab59"
+                + "cabfe307";
+        private readonly string EXPECTED_9  = "462401724b5ce6588d5a54aae5375513" + "a075cfcdf5042112aa29685c912fc205"
+                + "6543";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_8, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_12, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_16, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_32, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_48, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_1, DATA_64, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_12, DATA_4, EXPECTED_7);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_18, DATA_20, EXPECTED_8);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, AEAD_20, DATA_18, EXPECTED_9);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-256 Set 3.
+     */
+    class AESGcmSiv256Test3
+    {
+        private readonly string EMPTY       = "";
+        private readonly string KEY_1       = "e66021d5eb8e4f4066d4adb9c33560e4" + "f46e44bb3da0015c94f7088736864200";
+        private readonly string KEY_2       = "bae8e37fc83441b16034566b7a806c46" + "bb91c3c5aedb64a6c590bc84d1a5e269";
+        private readonly string KEY_3       = "6545fc880c94a95198874296d5cc1fd1" + "61320b6920ce07787f86743b275d1ab3";
+        private readonly string KEY_4       = "d1894728b3fed1473c528b8426a58299" + "5929a1499e9ad8780c8d63d0ab4149c0";
+        private readonly string KEY_5       = "a44102952ef94b02b805249bac80e6f6" + "1455bfac8308a2d40d8c845117808235";
+        private readonly string KEY_6       = "9745b3d1ae06556fb6aa7890bebc18fe" + "6b3db4da3d57aa94842b9803a96e07fb";
+        private readonly string KEY_7       = "b18853f68d833640e42a3c02c25b6486" + "9e146d7b233987bddfc240871d7576f7";
+        private readonly string KEY_8       = "3c535de192eaed3822a2fbbe2ca9dfc8" + "8255e14a661b8aa82cc54236093bbc23";
+        private readonly string NONCE_1     = "e0eaf5284d884a0e77d31646";
+        private readonly string NONCE_2     = "e4b47801afc0577e34699b9e";
+        private readonly string NONCE_3     = "2f6d1f0434d8848c1177441f";
+        private readonly string NONCE_4     = "9f572c614b4745914474e7c7";
+        private readonly string NONCE_5     = "5c9e940fea2f582950a70d5a";
+        private readonly string NONCE_6     = "6de71860f762ebfbd08284e4";
+        private readonly string NONCE_7     = "028ec6eb5ea7e298342a94d4";
+        private readonly string NONCE_8     = "688089e55540db1872504e1c";
+        private readonly string AEAD_2      = "4fbdc66f14";
+        private readonly string AEAD_3      = "6787f3ea22c127aaf195";
+        private readonly string AEAD_4      = "489c8fde2be2cf97e74e932d4ed87d";
+        private readonly string AEAD_5      = "0da55210cc1c1b0abde3b2f204d1e9f8" + "b06bc47f";
+        private readonly string AEAD_6      = "f37de21c7ff901cfe8a69615a93fdf7a" + "98cad481796245709f";
+        private readonly string AEAD_7      = "9c2159058b1f0fe91433a5bdc20e214e" + "ab7fecef4454a10ef0657df21ac7";
+        private readonly string AEAD_8      = "734320ccc9d9bbbb19cb81b2af4ecbc3" + "e72834321f7aa0f70b7282b4f33df23f"
+                + "167541";
+        private readonly string DATA_2      = "671fdd";
+        private readonly string DATA_3      = "195495860f04";
+        private readonly string DATA_4      = "c9882e5386fd9f92ec";
+        private readonly string DATA_5      = "1db2316fd568378da107b52b";
+        private readonly string DATA_6      = "21702de0de18baa9c9596291b08466";
+        private readonly string DATA_7      = "b202b370ef9768ec6561c4fe6b7e7296" + "fa85";
+        private readonly string DATA_8      = "ced532ce4159b035277d4dfbb7db6296" + "8b13cd4eec";
+        private readonly string EXPECTED_1  = "169fbb2fbf389a995f6390af22228a62";
+        private readonly string EXPECTED_2  = "0eaccb93da9bb81333aee0c785b240d3" + "19719d";
+        private readonly string EXPECTED_3  = "a254dad4f3f96b62b84dc40c84636a5e" + "c12020ec8c2c";
+        private readonly string EXPECTED_4  = "0df9e308678244c44bc0fd3dc6628dfe" + "55ebb0b9fb2295c8c2";
+        private readonly string EXPECTED_5  = "8dbeb9f7255bf5769dd56692404099c2" + "587f64979f21826706d497d5";
+        private readonly string EXPECTED_6  = "793576dfa5c0f88729a7ed3c2f1bffb3" + "080d28f6ebb5d3648ce97bd5ba67fd";
+        private readonly string EXPECTED_7  = "857e16a64915a787637687db4a951963" + "5cdd454fc2a154fea91f8363a39fec7d"
+                + "0a49";
+        private readonly string EXPECTED_8  = "626660c26ea6612fb17ad91e8e767639" + "edd6c9faee9d6c7029675b89eaf4ba1d"
+                + "ed1a286594";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, EMPTY, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_2, NONCE_2, AEAD_2, DATA_2, EXPECTED_2);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_3, NONCE_3, AEAD_3, DATA_3, EXPECTED_3);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_4, NONCE_4, AEAD_4, DATA_4, EXPECTED_4);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_5, NONCE_5, AEAD_5, DATA_5, EXPECTED_5);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_6, NONCE_6, AEAD_6, DATA_6, EXPECTED_6);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_7, NONCE_7, AEAD_7, DATA_7, EXPECTED_7);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_8, NONCE_8, AEAD_8, DATA_8, EXPECTED_8);
+        }
+    }
+
+    /**
+     * AES-GCM-SIV-256 Set 4.
+     */
+    class AESGcmSiv256Test4
+    {
+        private readonly string EMPTY       = "";
+        private readonly string KEY_1       = "00000000000000000000000000000000" + "00000000000000000000000000000000";
+        private readonly string NONCE_1     = "000000000000000000000000";
+        private readonly string DATA_1      = "00000000000000000000000000000000" + "4db923dc793ee6497c76dcc03a98e108";
+        private readonly string DATA_2      = "eb3640277c7ffd1303c7a542d02d3e4c" + "0000000000000000";
+        private readonly string EXPECTED_1  = "f3f80f2cf0cb2dd9c5984fcda908456c" + "c537703b5ba70324a6793a7bf218d3ea"
+                + "ffffffff000000000000000000000000";
+        private readonly string EXPECTED_2  = "18ce4f0b8cb4d0cac65fea8f79257b20" + "888e53e72299e56dffffffff00000000"
+                + "0000000000000000";
+
+        /**
+         * Test cipher.
+         * @param pTest the test engine
+         */
+        internal void testTheCipher(GcmSivTest pTest)
+        {
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_1, EXPECTED_1);
+            pTest.testSIVCipher(new GcmSivBlockCipher(), KEY_1, NONCE_1, EMPTY, DATA_2, EXPECTED_2);
+        }
+    }
+}
+}
diff --git a/crypto/test/src/crypto/test/ParallelHashTest.cs b/crypto/test/src/crypto/test/ParallelHashTest.cs
new file mode 100644
index 000000000..4edffc7c3
--- /dev/null
+++ b/crypto/test/src/crypto/test/ParallelHashTest.cs
@@ -0,0 +1,141 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+
+/**
+ * ParallelHash test vectors from:
+ * <p>
+ * https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf
+ */
+public class ParallelHashTest
+    : SimpleTest
+{
+    public override string Name
+    {
+        get { return "ParallelHash"; }
+    }
+
+    public override void PerformTest()
+    {
+        ParallelHash  pHash = new ParallelHash(128, new byte[0], 8);
+
+        byte[] data = Hex.Decode("00 01 02 03 04 05 06 07 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27");
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        byte[] res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("BA 8D C1 D1 D9 79 33 1D 3F 81 36 03 C6 7F 72 609A B5 E4 4B 94 A0 B8 F9 AF 46 51 44 54 A2 B4 F5"), res));
+
+        pHash = new ParallelHash(128, Strings.ToByteArray("Parallel Data"), 8);
+
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("FC 48 4D CB 3F 84 DC EE DC 35 34 38 15 1B EE 58 15 7D 6E FE D0 44 5A 81 F1 65 E4 95 79 5B 72 06"), res));
+
+        pHash = new ParallelHash(128, Strings.ToByteArray("Parallel Data"), 12);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 08 09 0A 0B 10 11 12 13 14 15 16 17 18 19 1A 1B 20 21 22 23 24 25 26 27 28 29 2A 2B 30 31 32 33 34 35 36 37 38 39 3A 3B 40 41 42 43 44 45 46 47 48 49 4A 4B 50 51 52 53 54 55 56 57 58 59 5A 5B");
+
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("F7 FD 53 12 89 6C 66 85 C8 28 AF 7E 2A DB 97 E3 93 E7 F8 D5 4E 3C 2E A4 B9 5E 5A CA 37 96 E8 FC"), res));
+
+        pHash = new ParallelHash(256, new byte[0], 8);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27");
+
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("BC 1E F1 24 DA 34 49 5E 94 8E AD 20 7D D9 84 22 35 DA 43 2D 2B BC 54 B4 C1 10 E6 4C 45 11 05 53 1B 7F 2A 3E 0C E0 55 C0 28 05 E7 C2 DE 1F B7 46 AF 97 A1 DD 01 F4 3B 82 4E 31 B8 76 12 41 04 29"), res));
+
+        pHash = new ParallelHash(256, Strings.ToByteArray("Parallel Data"), 8);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27");
+
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("CD F1 52 89 B5 4F 62 12 B4 BC 27 05 28 B4 95 26 00 6D D9 B5 4E 2B 6A DD 1E F6 90 0D DA 39 63 BB 33 A7 24 91 F2 36 96 9C A8 AF AE A2 9C 68 2D 47 A3 93 C0 65 B3 8E 29 FA E6 51 A2 09 1C 83 31 10"), res));
+
+        pHash = new ParallelHash(256, Strings.ToByteArray("Parallel Data"), 12);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 08 09 0A 0B 10 11 12 13 14 15 16 17 18 19 1A 1B 20 21 22 23 24 25 26 27 28 29 2A 2B 30 31 32 33 34 35 36 37 38 39 3A 3B 40 41 42 43 44 45 46 47 48 49 4A 4B 50 51 52 53 54 55 56 57 58 59 5A 5B");
+
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[pHash.GetDigestSize()];
+
+        pHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("69 D0 FC B7 64 EA 05 5D D0 93 34 BC 60 21 CB 7E 4B 61 34 8D FF 37 5D A2 62 67 1C DE C3 EF FA 8D 1B 45 68 A6 CC E1 6B 1C AD 94 6D DD E2 7F 6C E2 B8 DE E4 CD 1B 24 85 1E BF 00 EB 90 D4 38 13 E9"), res));
+
+        pHash = new ParallelHash(128, Strings.ToByteArray("Parallel Data"), 12);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 08 09 0A 0B 10 11 12 13 14 15 16 17 18 19 1A 1B 20 21 22 23 24 25 26 27 28 29 2A 2B 30 31 32 33 34 35 36 37 38 39 3A 3B 40 41 42 43 44 45 46 47 48 49 4A 4B 50 51 52 53 54 55 56 57 58 59 5A 5B");
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[32];
+
+        pHash.DoOutput(res, 0, res.Length);
+
+        IsTrue("oops!", !Arrays.AreEqual(Hex.Decode("F7 FD 53 12 89 6C 66 85 C8 28 AF 7E 2A DB 97 E3 93 E7 F8 D5 4E 3C 2E A4 B9 5E 5A CA 37 96 E8 FC"), res));
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("0127ad9772ab904691987fcc4a24888f341fa0db2145e872d4efd255376602f0"), res));
+
+        pHash = new ParallelHash(256, Strings.ToByteArray("Parallel Data"), 12);
+
+        data = Hex.Decode("00 01 02 03 04 05 06 07 08 09 0A 0B 10 11 12 13 14 15 16 17 18 19 1A 1B 20 21 22 23 24 25 26 27 28 29 2A 2B 30 31 32 33 34 35 36 37 38 39 3A 3B 40 41 42 43 44 45 46 47 48 49 4A 4B 50 51 52 53 54 55 56 57 58 59 5A 5B");
+        pHash.BlockUpdate(data, 0, data.Length);
+
+        res = new byte[64];
+
+        pHash.DoOutput(res, 0, res.Length);
+
+        IsTrue("oops!", !Arrays.AreEqual(Hex.Decode("69 D0 FC B7 64 EA 05 5D D0 93 34 BC 60 21 CB 7E 4B 61 34 8D FF 37 5D A2 62 67 1C DE C3 EF FA 8D 1B 45 68 A6 CC E1 6B 1C AD 94 6D DD E2 7F 6C E2 B8 DE E4 CD 1B 24 85 1E BF 00 EB 90 D4 38 13 E9"), res));
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("6b3e790b330c889a204c2fbc728d809f19367328d852f4002dc829f73afd6bcefb7fe5b607b13a801c0be5c1170bdb794e339458fdb0e62a6af3d42558970249"), res));
+
+        testEmpty();
+    }
+
+    private void testEmpty()
+    {
+        //{"tcId":90,"msg":"","len":0,"blockSize":62,"customization":"Ny0LL2tUmt\u003C\u002BkuN5:Z7pZ_7]R; l/i:%pWbo4}","outLen":16},
+        //{"tcId":90,"md":"13C4","outLen":16}
+        ParallelHash pHash = new ParallelHash(256, Strings.ToByteArray("Ny0LL2tUmt\u003C\u002BkuN5:Z7pZ_7]R; l/i:%pWbo4}"), 62);
+
+        pHash.BlockUpdate(new byte[0], 0, 0);
+
+        byte[] res = new byte[16 / 8];
+
+        pHash.DoOutput(res, 0, res.Length);
+
+        IsTrue(Arrays.AreEqual(Hex.Decode("13C4"), res));
+    }
+}
+}
diff --git a/crypto/test/src/crypto/test/SP80038GTest.cs b/crypto/test/src/crypto/test/SP80038GTest.cs
new file mode 100644
index 000000000..c6ed1b2c7
--- /dev/null
+++ b/crypto/test/src/crypto/test/SP80038GTest.cs
@@ -0,0 +1,582 @@
+
+using NUnit.Framework;
+
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Crypto.Fpe;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+	public class SP80038GTest
+	    : SimpleTest
+{
+    private class FFSample
+    {
+        private readonly int radix;
+        private readonly byte[] key;
+        private readonly byte[] plaintext;
+        private readonly byte[] ciphertext;
+        private readonly byte[] tweak;
+
+        public static FFSample from(int radix, String hexKey, String asciiPT, String asciiCT, String hexTweak)
+        {
+            return new FFSample(radix, fromHex(hexKey), fromAscii(radix, asciiPT), fromAscii(radix, asciiCT), fromHex(hexTweak));
+        }
+
+        private static byte fromAlphaNumeric(char c)
+        {
+            if (c >= '0' && c <= '9')
+            {
+                return (byte)(c - '0');
+            }
+            else if (c >= 'a' && c <= 'z')
+            {
+                return (byte)(10 + (c - 'a'));
+            }
+            else if (c >= 'A' && c <= 'Z')
+            {
+                return (byte)(36 + (c - 'A'));
+            }
+            else
+            {
+                throw new ArgumentException();
+            }
+        }
+
+        private static byte[] fromAscii(int radix, string ascii)
+        {
+            byte[] result = new byte[ascii.Length];
+            for (int i = 0; i < result.Length; ++i)
+            {
+                result[i] = fromAlphaNumeric(ascii[i]);
+                if (result[i] < 0 || result[i] >= radix)
+                {
+                    throw new ArgumentException();
+                }
+            }
+            return result;
+        }
+
+        private static byte[] fromHex(string hex)
+        {
+            return Hex.Decode(hex);
+        }
+
+        private FFSample(int radix, byte[] key, byte[] plaintext, byte[] ciphertext, byte[] tweak)
+        {
+            this.radix = radix;
+            this.key = key;
+            this.plaintext = plaintext;
+            this.ciphertext = ciphertext;
+            this.tweak = tweak;
+        }
+
+        public byte[] getCiphertext()
+        {
+            return ciphertext;
+        }
+
+        public byte[] getKey()
+        {
+            return key;
+        }
+
+        public byte[] getPlaintext()
+        {
+            return plaintext;
+        }
+
+        public int getRadix()
+        {
+            return radix;
+        }
+
+        public byte[] getTweak()
+        {
+            return tweak;
+        }
+    }
+
+    private static FFSample[] ff1Samples = new FFSample[]
+        {
+            // FF1-AES128
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3C", "0123456789", "2433477484", ""),
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3C", "0123456789", "6124200773", "39383736353433323130"),
+            FFSample.from(36, "2B7E151628AED2A6ABF7158809CF4F3C", "0123456789abcdefghi", "a9tv40mll9kdu509eum", "3737373770717273373737"),
+
+            // FF1-AES192
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "0123456789", "2830668132", ""),
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "0123456789", "2496655549", "39383736353433323130"),
+            FFSample.from(36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "0123456789abcdefghi", "xbj3kv35jrawxv32ysr", "3737373770717273373737"),
+
+            // FF1-AES256
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "0123456789", "6657667009", ""),
+            FFSample.from(10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "0123456789", "1001623463", "39383736353433323130"),
+            FFSample.from(36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "0123456789abcdefghi", "xs8a0azh2avyalyzuwd", "3737373770717273373737"),
+        };
+
+    private static FFSample[] ff3_1Samples = new FFSample[]
+        {
+            // FF3-AES128
+            FFSample.from(62, "7793833CE891B496381BD5B882F77EA1", "YbpT3hDo0J9xwCQ5qUWt93iv", "dDEYxViK56lGbV1WdZTPTe4w", "C58797C2580174"),
+        };
+
+    private void testFF1()
+    {
+        for (int i = 0; i < ff1Samples.Length; ++i)
+        {
+            testFF1Sample(ff1Samples[i]);
+        }
+
+        byte[] key = Hex.Decode("EF4359D8D580AA4F7F036D6F04FC6A942B7E151628AED2A6");
+        byte[] plainText = Hex.Decode("0327035100210215");
+        byte[] tweak = Hex.Decode("39383736353433323130");
+
+        FpeEngine fpeEngine = new FpeFf1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), 24, tweak));
+
+        try
+        {
+            fpeEngine.ProcessBlock(plainText, 0, plainText.Length, plainText, 0);
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input data outside of radix", e.Message);
+        }
+
+        try
+        {
+            fpeEngine.ProcessBlock(new byte[] { 1 }, 0, 1, plainText, 0);
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input too short", e.Message);
+        }
+    }
+
+    public void testFF1w()
+    {
+        byte[] key = Hex.Decode("EF4359D8D580AA4F7F036D6F04FC6A942B7E151628AED2A6");
+        byte[] plainText = Hex.Decode("0327035100210215");
+        byte[] cipherText = Hex.Decode("022701f80217020a");
+        byte[] tweak = Hex.Decode("39383736353433323130");
+
+        FpeEngine fpeEngine = new FpeFf1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), 1024, tweak));
+
+        byte[] enc = new byte[plainText.Length];
+
+        fpeEngine.ProcessBlock(plainText, 0, plainText.Length, enc, 0);
+
+        AreEqual(cipherText, enc);
+
+        fpeEngine.Init(false, new FpeParameters(new KeyParameter(key), 1024, tweak));
+
+        fpeEngine.ProcessBlock(cipherText, 0, cipherText.Length, enc, 0);
+
+        AreEqual(plainText, enc);
+
+        byte[] outPt = Hex.Decode("03270F5100210215");
+
+        try
+        {
+            fpeEngine.ProcessBlock(outPt, 0, outPt.Length, enc, 0);
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input data outside of radix", e.Message);
+        }
+    }
+
+    public void testFF3_1()
+    {
+        for (int i = 0; i < ff3_1Samples.Length; ++i)
+        {
+            testFF3_1Sample(ff3_1Samples[i]);
+        }
+
+        byte[] key = Hex.Decode("EF4359D8D580AA4F7F036D6F04FC6A942B7E151628AED2A6");
+        byte[] plainText = Hex.Decode("0327035100210215");
+        byte[] tweak = Hex.Decode("39383736353433");
+
+        FpeEngine fpeEngine = new FpeFf3_1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), 24, tweak));
+
+        try
+        {
+            fpeEngine.ProcessBlock(plainText, 0, plainText.Length, plainText, 0);
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input data outside of radix", e.Message);
+        }
+
+        try
+        {
+            fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), 24, Hex.Decode("beef")));
+
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("tweak should be 56 bits", e.Message);
+        }
+    }
+
+    private void testFF3_1w()
+    {
+        byte[] key = Hex.Decode("EF4359D8D580AA4F7F036D6F04FC6A942B7E151628AED2A6");
+        byte[] plainText = Hex.Decode("0327035100210215");
+        byte[] cipherText = Hex.Decode("02fb024900310220");
+        byte[] tweak = Hex.Decode("39383736353433");
+
+        FpeEngine fpeEngine = new FpeFf3_1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), 1024, tweak));
+
+        byte[] enc = new byte[plainText.Length];
+
+        fpeEngine.ProcessBlock(plainText, 0, plainText.Length, enc, 0);
+
+        IsTrue("enc failed: " + Hex.ToHexString(enc), AreEqual(cipherText, enc));
+
+        fpeEngine.Init(false, new FpeParameters(new KeyParameter(key), 1024, tweak));
+
+        fpeEngine.ProcessBlock(cipherText, 0, cipherText.Length, enc, 0);
+
+        IsTrue(AreEqual(plainText, enc));
+
+        byte[] outPt = Hex.Decode("03270F5100210215");
+
+        try
+        {
+            fpeEngine.ProcessBlock(outPt, 0, outPt.Length, enc, 0);
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input data outside of radix", e.Message);
+        }
+    }
+
+    private void testDisable()
+    {
+        Environment.SetEnvironmentVariable("org.bouncycastle.fpe.disable", "true");
+        try
+        {
+            testFF1();
+            Fail("no exception");
+        }
+        catch (InvalidOperationException e)
+        {
+            IsEquals("FF1 encryption disabled", e.Message);
+        }
+
+        try
+        {
+            testFF3_1();
+            Fail("no exception");
+        }
+        catch (InvalidOperationException e)
+        {
+            IsEquals("Fpe disabled", e.Message);
+        }
+        Environment.SetEnvironmentVariable("org.bouncycastle.fpe.disable", "false");
+
+        Environment.SetEnvironmentVariable("org.bouncycastle.fpe.disable_ff1", "true");
+        try
+        {
+            testFF1();
+            Fail("no exception");
+        }
+        catch (InvalidOperationException e)
+        {
+            IsEquals("FF1 encryption disabled", e.Message);
+        }
+
+        testFF3_1();
+        Environment.SetEnvironmentVariable("org.bouncycastle.fpe.disable_ff1", "false");
+    }
+
+    private void testFF3_1_255()
+    {
+        byte[] key = Hex.Decode("339BB5B1F2D44BAABF87CA1B7380CDC8");
+        byte[] tweak = Hex.Decode("3F096DE35BFA31");
+        int radix = 256;
+
+        FpeEngine fpeEngine = new FpeFf3_1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), radix, tweak));
+
+        ulong valueToEncrypt = 0x31009155FFL;
+
+        byte[] bytes = Pack.UInt64_To_BE(valueToEncrypt);
+        byte[] enc = new byte[bytes.Length];
+        //Encrypt
+
+        fpeEngine.ProcessBlock(bytes, 0, bytes.Length, enc, 0);
+
+        IsTrue(Arrays.AreEqual(Hex.Decode("18fa139dc978a681"), enc));
+
+        //Decrypt
+        fpeEngine.Init(false, new FpeParameters(new KeyParameter(key), radix, tweak));
+
+        fpeEngine.ProcessBlock(enc, 0, enc.Length, enc, 0);
+
+        IsTrue(Arrays.AreEqual(bytes, enc));
+    }
+
+    private void testFF1Sample(FFSample ff1)
+    {
+        FpeEngine fpeEngine = new FpeFf1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(ff1.getKey()), ff1.getRadix(), ff1.getTweak()));
+
+        byte[] plain = ff1.getPlaintext();
+        byte[] enc = new byte[plain.Length];
+
+        fpeEngine.ProcessBlock(plain, 0, plain.Length, enc, 0);
+
+        IsTrue(AreEqual(ff1.getCiphertext(), enc));
+
+        fpeEngine.Init(false, new FpeParameters(new KeyParameter(ff1.getKey()), ff1.getRadix(), ff1.getTweak()));
+
+        fpeEngine.ProcessBlock(ff1.getCiphertext(), 0, ff1.getCiphertext().Length, enc, 0);
+
+        IsTrue(AreEqual(ff1.getPlaintext(), enc));
+    }
+
+    private void testFF3_1Sample(FFSample ff3_1)
+    {
+        FpeEngine fpeEngine = new FpeFf3_1Engine();
+
+        fpeEngine.Init(true, new FpeParameters(new KeyParameter(ff3_1.getKey()), ff3_1.getRadix(), ff3_1.getTweak()));
+
+        byte[] plain = ff3_1.getPlaintext();
+        byte[] enc = new byte[plain.Length];
+
+        fpeEngine.ProcessBlock(plain, 0, plain.Length, enc, 0);
+
+        IsTrue(AreEqual(ff3_1.getCiphertext(), enc));
+
+        fpeEngine.Init(false, new FpeParameters(new KeyParameter(ff3_1.getKey()), ff3_1.getRadix(), ff3_1.getTweak()));
+
+        fpeEngine.ProcessBlock(ff3_1.getCiphertext(), 0, plain.Length, enc, 0);
+
+        IsTrue(AreEqual(ff3_1.getPlaintext(), enc));
+    }
+
+    public void testFF1Bounds()
+    {
+        byte[] key = Hex.Decode("339BB5B1F2D44BAABF87CA1B7380CDC8");
+        byte[] tweak = Hex.Decode("3F096DE35BFA31");
+
+        FpeEngine fpeEngine = new FpeFf1Engine();
+
+        try
+        {
+            IAlphabetMapper alphabetMapper = new BasicAlphabetMapper("ABCDEFGHI");
+
+            fpeEngine.Init(true, new FpeParameters(new KeyParameter(key),
+                        alphabetMapper.Radix, tweak));
+
+            process(fpeEngine, new byte[] { 1, 2, 3 });
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+           IsEquals("input too short", e.Message);
+        }
+
+        try
+        {
+            IAlphabetMapper alphabetMapper = new BasicAlphabetMapper("ABCD");
+
+            fpeEngine.Init(true, new FpeParameters(new KeyParameter(key),
+                        alphabetMapper.Radix, tweak));
+
+            process(fpeEngine, new byte[] { 1, 2, 3 });
+            Fail("no exception");
+        }
+        catch (ArgumentException e)
+        {
+            IsEquals("input too short", e.Message);
+        }
+    }
+
+    private void testFF3_1Bounds()
+    {
+        String bigAlpha = "+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
+
+        IAlphabetMapper alphabetMapper = new BasicAlphabetMapper(bigAlpha);
+
+        ff3_1Test(alphabetMapper, "467094C27E47978FE616F475215BF4F1", "ECC8AA7B87B41C", "9RwG+t8cKfa9JweBYgHAA6fHUShNZ5tc", "-DXMBhb3AFPq5Xf4oUva4WbB8eagGK2u");
+        ff3_1Test(alphabetMapper, "4DB04B58E97819015A08BA7A39A79C303968A34DB0936FAD", "26B3A632FAADFE", "k5Kop6xYpT0skr1zHHPEt5rPWQ4s4O-3", "JyWzuPL6SNsciOXdEgwnKZJxHiKaTu4Z");
+        ff3_1Test(alphabetMapper, "15567AA6CD8CCA401ADB6A10730655AEEC10E9101FD3969A", "379B9572B687A6", "ZpztPp90Oo5ekoNRzqArsAqAbnmM--W6", "NPxEDufvnYzVX3jxupv+iJOuPVpWRPjD");
+        try
+        {
+            ff3_1Test(alphabetMapper, "15567AA6CD8CCA401ADB6A10730655AEEC10E9101FD3969A", "379B9572B687A6", "ZpztPp90Oo5ekoNRzqArsAqAbnmM+-W6ZZ", "L1yx-4YLQG9W1P5yTI7Wp2h0IDcRoBq1kk");
+            Fail("no exception 1");
+        }
+        catch (ArgumentException e)
+        {
+           IsEquals("maximum input length is 32", e.Message);
+        }
+
+        try
+        {
+            ff3_1Test(alphabetMapper, "15567AA6CD8CCA401ADB6A10730655AEEC10E9101FD3969A", "379B9572B687A6", "Z", "L");
+            Fail("no exception 2");
+        }
+        catch (ArgumentException e)
+        {
+           IsEquals("input too short", e.Message);
+        }
+
+        try
+        {
+            alphabetMapper = new BasicAlphabetMapper("ABCDEFGHI");
+
+            ff3_1Test(alphabetMapper, "15567AA6CD8CCA401ADB6A10730655AEEC10E9101FD3969A", "379B9572B687A6", "AB", "ZZ");
+            Fail("no exception 3");
+        }
+        catch (ArgumentException e)
+        {
+           IsEquals("input too short", e.Message);
+        }
+    }
+
+    private void ff3_1Test(IAlphabetMapper alphabetMapper, String skey, String stweak, String input, String output)
+    {
+        FpeEngine fpeEncEngine = new FpeFf3_1Engine();
+        FpeEngine fpeDecEngine = new FpeFf3_1Engine();
+
+        byte[] key = Hex.Decode(skey);
+        byte[] tweak = Hex.Decode(stweak);
+        int radix = alphabetMapper.Radix;
+
+        fpeEncEngine.Init(true, new FpeParameters(new KeyParameter(key), radix, tweak));
+        fpeDecEngine.Init(false, new FpeParameters(new KeyParameter(key), radix, tweak));
+
+        byte[] bytes = alphabetMapper.ConvertToIndexes(input.ToCharArray());
+
+        byte[] encryptedBytes = process(fpeEncEngine, bytes);
+        IsEquals(output, new String(alphabetMapper.ConvertToChars(encryptedBytes)));
+
+        byte[] decryptedBytes = process(fpeDecEngine, encryptedBytes);
+        IsTrue(Arrays.AreEqual(bytes, decryptedBytes));
+        char[] chars = alphabetMapper.ConvertToChars(decryptedBytes);
+        IsEquals(input, new String(chars));
+    }
+
+    private byte[] process(FpeEngine fpeEngine, byte[] bytes)
+    {
+        byte[] rv = new byte[bytes.Length];
+
+        fpeEngine.ProcessBlock(bytes, 0, bytes.Length, rv, 0);
+
+        return rv;
+    }
+
+    public void testUtility()
+    {
+        FpeCharEncryptor fpeEnc = new FpeCharEncryptor(new FpeFf1Engine(), Hex.Decode("2B7E151628AED2A6ABF7158809CF4F3C"), "0123456789".ToCharArray());
+
+        char[] input = "01234567890123456".ToCharArray();
+        char[] encrypted = fpeEnc.Process(input);
+
+        FpeCharDecryptor fpeDec = new FpeCharDecryptor(new FpeFf1Engine(), Hex.Decode("2B7E151628AED2A6ABF7158809CF4F3C"), "0123456789".ToCharArray());
+        char[] decrypted = fpeDec.Process(encrypted);
+
+        IsTrue("no match", Arrays.AreEqual(input, decrypted));
+    }
+
+    public override string Name
+    {
+        get { return "SP80038GTest"; }
+    }
+
+    public override void PerformTest()
+    {
+        testFF1();
+        testFF1w();
+        testFF1Bounds();
+        testFF3_1();
+        testFF3_1w();
+        testFF3_1_255();
+        testFF3_1Bounds();
+        testDisable();
+        testUtility();
+    }
+
+    public class FpeCharEncryptor
+    {
+        private readonly FpeEngine fpeEngine;
+        private IAlphabetMapper alphabetMapper;
+
+        public FpeCharEncryptor(FpeEngine fpeEngine, byte[] key, char[] alphabet): this(fpeEngine, key, new byte[0], alphabet)
+        {
+
+        }
+
+        public FpeCharEncryptor(FpeEngine fpeEngine, byte[] key, byte[] tweak, char[] alphabet)
+        {
+            this.fpeEngine = fpeEngine;
+
+            alphabetMapper = new BasicAlphabetMapper(alphabet);
+
+            fpeEngine.Init(true, new FpeParameters(new KeyParameter(key), alphabetMapper.Radix, tweak));
+        }
+
+        public char[] Process(char[] input)
+        {
+            byte[] bytes = alphabetMapper.ConvertToIndexes(input);
+
+            fpeEngine.ProcessBlock(bytes, 0, bytes.Length, bytes, 0);
+
+            return alphabetMapper.ConvertToChars(bytes);
+        }
+    }
+
+    public class FpeCharDecryptor
+    {
+        private readonly FpeEngine fpeEngine;
+        private IAlphabetMapper alphabetMapper;
+
+        public FpeCharDecryptor(FpeEngine fpeEngine, byte[] key, char[] alphabet): this(fpeEngine, key, new byte[0], alphabet)
+        {
+        }
+
+        public FpeCharDecryptor(FpeEngine fpeEngine, byte[] key, byte[] tweak, char[] alphabet)
+        {
+            this.fpeEngine = fpeEngine;
+
+            alphabetMapper = new BasicAlphabetMapper(alphabet);
+
+            fpeEngine.Init(false, new FpeParameters(new KeyParameter(key), alphabetMapper.Radix, tweak));
+        }
+
+        public char[] Process(char[] input)
+        {
+            byte[] bytes = alphabetMapper.ConvertToIndexes(input);
+
+            fpeEngine.ProcessBlock(bytes, 0, bytes.Length, bytes, 0);
+
+            return alphabetMapper.ConvertToChars(bytes);
+        }
+    }
+}
+}
diff --git a/crypto/test/src/crypto/test/SentrixSigningSha256Hard.pfx b/crypto/test/src/crypto/test/SentrixSigningSha256Hard.pfx
new file mode 100644
index 000000000..770956d0b
--- /dev/null
+++ b/crypto/test/src/crypto/test/SentrixSigningSha256Hard.pfx
Binary files differdiff --git a/crypto/test/src/crypto/test/SentrixSigningSha256Soft.pfx b/crypto/test/src/crypto/test/SentrixSigningSha256Soft.pfx
new file mode 100644
index 000000000..d8c3cd534
--- /dev/null
+++ b/crypto/test/src/crypto/test/SentrixSigningSha256Soft.pfx
Binary files differdiff --git a/crypto/test/src/crypto/test/TupleHashTest.cs b/crypto/test/src/crypto/test/TupleHashTest.cs
new file mode 100644
index 000000000..f212f4abe
--- /dev/null
+++ b/crypto/test/src/crypto/test/TupleHashTest.cs
@@ -0,0 +1,108 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Crypto.Tests
+{
+    [TestFixture]
+/**
+ * TupleHash test vectors from:
+ * <p>
+ * https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf
+ */
+public class TupleHashTest
+    : SimpleTest
+{
+    public override string Name
+    {
+        get { return "TupleHash"; }
+    }
+
+    public override void PerformTest()
+    {
+        TupleHash tHash = new TupleHash(128, new byte[0]);
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+
+        byte[] res = new byte[tHash.GetDigestSize()];
+
+        tHash.DoFinal(res, 0);
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("C5 D8 78 6C 1A FB 9B 82 11 1A B3 4B 65 B2 C0 04 8F A6 4E 6D 48 E2 63 26 4C E1 70 7D 3F FC 8E D1"), res));
+
+        tHash = new TupleHash(128, Strings.ToByteArray("My Tuple App"));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+
+        tHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("75 CD B2 0F F4 DB 11 54 E8 41 D7 58 E2 41 60 C5 4B AE 86 EB 8C 13 E7 F5 F4 0E B3 55 88 E9 6D FB"), res));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+        tHash.BlockUpdate(Hex.Decode("202122232425262728"), 0, 9);
+
+        tHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("E6 0F 20 2C 89 A2 63 1E DA 8D 4C 58 8C A5 FD 07 F3 9E 51 51 99 8D EC CF 97 3A DB 38 04 BB 6E 84"), res));
+
+        tHash = new TupleHash(256, new byte[0]);
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+
+        res = new byte[tHash.GetDigestSize()];
+
+        tHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("CF B7 05 8C AC A5 E6 68 F8 1A 12 A2 0A 21 95 CE 97 A9 25 F1 DB A3 E7 44 9A 56 F8 22 01 EC 60 73 11 AC 26 96 B1 AB 5E A2 35 2D F1 42 3B DE 7B D4 BB 78 C9 AE D1 A8 53 C7 86 72 F9 EB 23 BB E1 94"), res));
+
+        tHash = new TupleHash(256, Strings.ToByteArray("My Tuple App"));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+
+        tHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("14 7C 21 91 D5 ED 7E FD 98 DB D9 6D 7A B5 A1 16 92 57 6F 5F E2 A5 06 5F 3E 33 DE 6B BA 9F 3A A1 C4 E9 A0 68 A2 89 C6 1C 95 AA B3 0A EE 1E 41 0B 0B 60 7D E3 62 0E 24 A4 E3 BF 98 52 A1 D4 36 7E"), res));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+        tHash.BlockUpdate(Hex.Decode("202122232425262728"), 0, 9);
+
+        tHash.DoFinal(res, 0);
+
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("45 00 0B E6 3F 9B 6B FD 89 F5 47 17 67 0F 69 A9 BC 76 35 91 A4 F0 5C 50 D6 88 91 A7 44 BC C6 E7 D6 D5 B5 E8 2C 01 8D A9 99 ED 35 B0 BB 49 C9 67 8E 52 6A BD 8E 85 C1 3E D2 54 02 1D B9 E7 90 CE"), res));
+
+        tHash = new TupleHash(128, Strings.ToByteArray("My Tuple App"));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+        tHash.BlockUpdate(Hex.Decode("202122232425262728"), 0, 9);
+
+        res = new byte[32];
+        tHash.DoOutput(res, 0, res.Length);
+
+        IsTrue("oops!", !Arrays.AreEqual(Hex.Decode("E6 0F 20 2C 89 A2 63 1E DA 8D 4C 58 8C A5 FD 07 F3 9E 51 51 99 8D EC CF 97 3A DB 38 04 BB 6E 84"), res));
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("900fe16cad098d28e74d632ed852f99daab7f7df4d99e775657885b4bf76d6f8"), res));
+
+        tHash = new TupleHash(256, Strings.ToByteArray("My Tuple App"));
+
+        tHash.BlockUpdate(Hex.Decode("000102"), 0, 3);
+        tHash.BlockUpdate(Hex.Decode("101112131415"), 0, 6);
+        tHash.BlockUpdate(Hex.Decode("202122232425262728"), 0, 9);
+
+        res = new byte[64];
+        tHash.DoOutput(res, 0, res.Length);
+
+        IsTrue("oops!", !Arrays.AreEqual(Hex.Decode("45 00 0B E6 3F 9B 6B FD 89 F5 47 17 67 0F 69 A9 BC 76 35 91 A4 F0 5C 50 D6 88 91 A7 44 BC C6 E7 D6 D5 B5 E8 2C 01 8D A9 99 ED 35 B0 BB 49 C9 67 8E 52 6A BD 8E 85 C1 3E D2 54 02 1D B9 E7 90 CE"), res));
+        IsTrue("oops!", Arrays.AreEqual(Hex.Decode("0c59b11464f2336c34663ed51b2b950bec743610856f36c28d1d088d8a2446284dd09830a6a178dc752376199fae935d86cfdee5913d4922dfd369b66a53c897"), res));
+    }
+}
+}