You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
935 lines
28 KiB
935 lines
28 KiB
#if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR) |
|
#pragma warning disable |
|
using System; |
|
using System.IO; |
|
|
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Engines; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes.Gcm; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Utilities; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities; |
|
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.IO; |
|
|
|
namespace BestHTTP.SecureProtocol.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 |
|
{ |
|
/// <summary>The buffer length.</summary> |
|
private static readonly int BUFLEN = 16; |
|
|
|
/// <summary>The halfBuffer length.</summary> |
|
private static readonly int HALFBUFLEN = BUFLEN >> 1; |
|
|
|
/// <summary>The nonce length.</summary> |
|
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 virtual IBlockCipher GetUnderlyingCipher() |
|
{ |
|
return theCipher; |
|
} |
|
|
|
public virtual int GetBlockSize() |
|
{ |
|
return theCipher.GetBlockSize(); |
|
} |
|
|
|
public virtual 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 virtual 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 virtual void ProcessAadByte(byte pByte) |
|
{ |
|
/* Check that we can supply AEAD */ |
|
CheckAeadStatus(1); |
|
|
|
/* Process the aead */ |
|
theAEADHasher.updateHash(pByte); |
|
} |
|
|
|
public virtual 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 virtual 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 virtual 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 virtual 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 + (int)thePlain.Length, BUFLEN); |
|
|
|
/* Reset the streams */ |
|
ResetStreams(); |
|
return myDataLen; |
|
|
|
/* else we are decrypting */ |
|
} |
|
else |
|
{ |
|
/* decrypt to plain text */ |
|
decryptPlain(); |
|
|
|
/* Release plain text */ |
|
int myDataLen = Streams.WriteBufTo(thePlain, pOutput, pOffset); |
|
|
|
/* Reset the streams */ |
|
ResetStreams(); |
|
return myDataLen; |
|
} |
|
} |
|
|
|
public virtual byte[] GetMac() |
|
{ |
|
throw new InvalidOperationException(); |
|
} |
|
|
|
public virtual int GetUpdateOutputSize(int pLen) |
|
{ |
|
return 0; |
|
} |
|
|
|
public virtual 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 virtual void Reset() |
|
{ |
|
ResetStreams(); |
|
} |
|
|
|
/** |
|
* Reset Streams. |
|
*/ |
|
private void ResetStreams() |
|
{ |
|
/* Clear the plainText buffer */ |
|
if (thePlain != null) |
|
{ |
|
thePlain.Position = 0L; |
|
Streams.WriteZeroes(thePlain, thePlain.Capacity); |
|
} |
|
|
|
/* 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 */ |
|
#if PORTABLE || NETFX_CORE |
|
byte[] thePlainBuf = thePlain.ToArray(); |
|
int thePlainLen = thePlainBuf.Length; |
|
#else |
|
byte[] thePlainBuf = thePlain.GetBuffer(); |
|
int thePlainLen = (int)thePlain.Length; |
|
#endif |
|
|
|
byte[] mySrc = thePlainBuf; |
|
byte[] myCounter = Arrays.Clone(pCounter); |
|
myCounter[BUFLEN - 1] |= MASK; |
|
byte[] myMask = new byte[BUFLEN]; |
|
long myRemaining = thePlainLen; |
|
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 thePlainLen; |
|
} |
|
|
|
/** |
|
* decrypt data stream. |
|
* @throws InvalidCipherTextException on data too short or mac check failed |
|
*/ |
|
private void decryptPlain() |
|
{ |
|
/* Access buffer and length */ |
|
#if PORTABLE || NETFX_CORE |
|
byte[] theEncDataBuf = theEncData.ToArray(); |
|
int theEncDataLen = theEncDataBuf.Length; |
|
#else |
|
byte[] theEncDataBuf = theEncData.GetBuffer(); |
|
int theEncDataLen = (int)theEncData.Length; |
|
#endif |
|
|
|
byte[] mySrc = theEncDataBuf; |
|
int myRemaining = theEncDataLen - 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.NumBits * theDataHasher.getBytesProcessed(), myIn, 0); |
|
Pack.UInt64_To_BE((ulong)Bytes.NumBits * theAEADHasher.getBytesProcessed(), myIn, Longs.NumBytes); |
|
|
|
/* 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.NumBytes; 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 ? (byte)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; |
|
} |
|
|
|
private class GcmSivCache |
|
: MemoryStream |
|
{ |
|
internal GcmSivCache() |
|
{ |
|
} |
|
} |
|
|
|
/** |
|
* Hash Control. |
|
*/ |
|
private 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); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#pragma warning restore |
|
#endif
|
|
|