From 504554b718911423f64b9d7235eee6fdba9241ee Mon Sep 17 00:00:00 2001 From: David Hook Date: Fri, 19 Mar 2021 15:25:57 +1100 Subject: first cut FPE, TupleHash, ParallelHash, and GCM-SIV --- crypto/src/crypto/IAlphabetMapper.cs | 32 + crypto/src/crypto/digests/CSHAKEDigest.cs | 6 + crypto/src/crypto/digests/KeccakDigest.cs | 2 +- crypto/src/crypto/digests/ParallelHash.cs | 210 ++++++ crypto/src/crypto/digests/TupleHash.cs | 134 ++++ crypto/src/crypto/digests/XofUtils.cs | 16 + crypto/src/crypto/fpe/FpeEngine.cs | 121 ++++ crypto/src/crypto/fpe/FpeFf1Engine.cs | 70 ++ crypto/src/crypto/fpe/FpeFf3_1Engine.cs | 75 ++ crypto/src/crypto/fpe/SP80038G.cs | 698 +++++++++++++++++++ crypto/src/crypto/modes/GcmSivBlockCipher.cs | 963 ++++++++++++++++++++++++++ crypto/src/crypto/parameters/FpeParameters.cs | 49 ++ crypto/src/crypto/util/BasicAlphabetMapper.cs | 104 +++ crypto/src/util/Arrays.cs | 24 + crypto/src/util/Bytes.cs | 10 + crypto/src/util/Integers.cs | 3 + crypto/src/util/Longs.cs | 3 + 17 files changed, 2519 insertions(+), 1 deletion(-) create mode 100644 crypto/src/crypto/IAlphabetMapper.cs create mode 100644 crypto/src/crypto/digests/ParallelHash.cs create mode 100644 crypto/src/crypto/digests/TupleHash.cs create mode 100644 crypto/src/crypto/fpe/FpeEngine.cs create mode 100644 crypto/src/crypto/fpe/FpeFf1Engine.cs create mode 100644 crypto/src/crypto/fpe/FpeFf3_1Engine.cs create mode 100644 crypto/src/crypto/fpe/SP80038G.cs create mode 100644 crypto/src/crypto/modes/GcmSivBlockCipher.cs create mode 100644 crypto/src/crypto/parameters/FpeParameters.cs create mode 100644 crypto/src/crypto/util/BasicAlphabetMapper.cs create mode 100644 crypto/src/util/Bytes.cs (limited to 'crypto/src') 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 +{ + /// + /// Return the number of characters in the alphabet. + /// + /// the radix for the alphabet. + int Radix { get; } + + /// + /// Return the passed in char[] as a byte array of indexes (indexes + /// can be more than 1 byte) + /// + /// an index array. + /// characters to be mapped. + byte[] ConvertToIndexes(char[] input); + + /// + /// Return a char[] for this alphabet based on the indexes passed. + /// + /// an array of char corresponding to the index values. + /// input array of indexes. + 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 +{ + /// + /// 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. + /// + /// From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash + /// + /// + 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 +{ + /// + /// 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. + /// + /// From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash + /// + /// + 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; + } + + /// + /// Process length bytes from inBuf, writing the output to outBuf. + /// + /// number of bytes output. + /// input data. + /// offset in input data to start at. + /// number of bytes to process. + /// destination buffer. + /// offset to start writing at in destination buffer. + 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; + } + + /// + /// Initialize the FPE engine for encryption/decryption. + /// + /// number of bytes output. + /// true if initialising for encryption, false otherwise. + /// the key and other parameters to use to set the engine up. + 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. + *

It should be noted that the specified limit of 236 bytes is not supported. This is because all bytes are + * cached in a ByteArrayOutputStream object (which has a limit of a little less than 231 bytes), + * and are output on the DoFinal() call (which can only process a maximum of 231 bytes).

+ *

The practical limit of 231 - 24 bytes is policed, and attempts to breach the limit will be rejected

+ *

In order to properly support the higher limit, an extended form of ByteArrayOutputStream would be needed + * which would use multiple arrays to store the data. In addition, a new doOutput method would be required (similar + * to that in XOF digests), which would allow the data to be output over multiple calls. Alternatively an extended + * form of ByteArrayInputStream could be used to deliver the data.

+ */ + 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); -- cgit 1.4.1