context: using System;
  
  using Org.BouncyCastle.Crypto.Engines;
  using Org.BouncyCastle.Crypto.Modes;
  using Org.BouncyCastle.Crypto.Paddings;
  using Org.BouncyCastle.Crypto.Parameters;
  
  namespace Org.BouncyCastle.Crypto.Macs
  {
  	/**
  	* DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC)
  	*
  	* This could as well be derived from CBCBlockCipherMac, but then the property mac in the base
  	* class must be changed to protected
  	*/
  	public class ISO9797Alg3Mac : IMac
  	{
  		private byte[] mac;
  		private byte[] buf;
  		private int bufOff;
  		private IBlockCipher cipher;
  		private IBlockCipherPadding padding;
  		private int macSize;
  		private KeyParameter lastKey2;
  		private KeyParameter lastKey3;
  
  		/**
  		* create a Retail-MAC based on a CBC block cipher. This will produce an
  		* authentication code of the length of the block size of the cipher.
  		*
  		* @param cipher the cipher to be used as the basis of the MAC generation. This must
  		* be DESEngine.
  		*/
  		public ISO9797Alg3Mac(
  			IBlockCipher cipher)
  			: this(cipher, cipher.GetBlockSize() * 8, null)
  		{
  		}
  
  		/**
  		* create a Retail-MAC based on a CBC block cipher. This will produce an
  		* authentication code of the length of the block size of the cipher.
  		*
  		* @param cipher the cipher to be used as the basis of the MAC generation.
  		* @param padding the padding to be used to complete the last block.
  		*/
  		public ISO9797Alg3Mac(
  			IBlockCipher		cipher,
  			IBlockCipherPadding	padding)
  			: this(cipher, cipher.GetBlockSize() * 8, padding)
  		{
  		}
  
  		/**
  		* create a Retail-MAC based on a block cipher with the size of the
  		* MAC been given in bits. This class uses single DES CBC mode as the basis for the
  		* MAC generation.
  		* <p>
  		* Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
  		* or 16 bits if being used as a data authenticator (FIPS Publication 113),
  		* and in general should be less than the size of the block cipher as it reduces
  		* the chance of an exhaustive attack (see Handbook of Applied Cryptography).
  		* </p>
  		* @param cipher the cipher to be used as the basis of the MAC generation.
  		* @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
  		*/
  		public ISO9797Alg3Mac(
  			IBlockCipher	cipher,
  			int				macSizeInBits)
  			: this(cipher, macSizeInBits, null)
  		{
  		}
  
  		/**
  		* create a standard MAC based on a block cipher with the size of the
  		* MAC been given in bits. This class uses single DES CBC mode as the basis for the
  		* MAC generation. The final block is decrypted and then encrypted using the
  		* middle and right part of the key.
  		* <p>
  		* Note: the size of the MAC must be at least 24 bits (FIPS Publication 81),
  		* or 16 bits if being used as a data authenticator (FIPS Publication 113),
  		* and in general should be less than the size of the block cipher as it reduces
  		* the chance of an exhaustive attack (see Handbook of Applied Cryptography).
  		* </p>
  		* @param cipher the cipher to be used as the basis of the MAC generation.
  		* @param macSizeInBits the size of the MAC in bits, must be a multiple of 8.
  		* @param padding the padding to be used to complete the last block.
  		*/
  		public ISO9797Alg3Mac(
  			IBlockCipher		cipher,
  			int					macSizeInBits,
  			IBlockCipherPadding	padding)
  		{
  			if ((macSizeInBits % 8) != 0)
  				throw new ArgumentException("MAC size must be multiple of 8");
  
  			if (!(cipher is DesEngine))
  				throw new ArgumentException("cipher must be instance of DesEngine");
  
  			this.cipher = new CbcBlockCipher(cipher);
  			this.padding = padding;
  			this.macSize = macSizeInBits / 8;
  
  			mac = new byte[cipher.GetBlockSize()];
  			buf = new byte[cipher.GetBlockSize()];
  			bufOff = 0;
  		}
  
  		public string AlgorithmName
  		{
  			get { return "ISO9797Alg3"; }
  		}
  
  		public void Init(
  			ICipherParameters parameters)
  		{
  			Reset();
  
  			if (!(parameters is KeyParameter))
  				throw new ArgumentException("parameters must be an instance of KeyParameter");
  
  			// KeyParameter must contain a double or triple length DES key,
  			// however the underlying cipher is a single DES. The middle and
  			// right key are used only in the final step.
  
  			KeyParameter kp = (KeyParameter)parameters;
  			KeyParameter key1;
  			byte[] keyvalue = kp.GetKey();
  
  			if (keyvalue.Length == 16)
  			{ // Double length DES key
  				key1 = new KeyParameter(keyvalue, 0, 8);
  				this.lastKey2 = new KeyParameter(keyvalue, 8, 8);
  				this.lastKey3 = key1;
  			}
  			else if (keyvalue.Length == 24)
  			{ // Triple length DES key
  				key1 = new KeyParameter(keyvalue, 0, 8);
  				this.lastKey2 = new KeyParameter(keyvalue, 8, 8);
  				this.lastKey3 = new KeyParameter(keyvalue, 16, 8);
  			}
  			else
  			{
  				throw new ArgumentException("Key must be either 112 or 168 bit long");
  			}
  
  			cipher.Init(true, key1);
  		}
  
  		public int GetMacSize()
  		{
  			return macSize;
  		}
  
  		public void Update(
  			byte input)
  		{
  			if (bufOff == buf.Length)
  			{
  				cipher.ProcessBlock(buf, 0, mac, 0);
  				bufOff = 0;
  			}
  
  			buf[bufOff++] = input;
  		}
  
  		public void BlockUpdate(
  			byte[]	input,
  			int		inOff,
  			int		len)
  		{
  			if (len < 0)
  				throw new ArgumentException("Can't have a negative input length!");
  
  			int blockSize = cipher.GetBlockSize();
  			int resultLen = 0;
  			int gapLen = blockSize - bufOff;
  
  			if (len > gapLen)
  			{
  				Array.Copy(input, inOff, buf, bufOff, gapLen);
  
  				resultLen += cipher.ProcessBlock(buf, 0, mac, 0);
  
  				bufOff = 0;
  				len -= gapLen;
  				inOff += gapLen;
  
  				while (len > blockSize)
  				{
  					resultLen += cipher.ProcessBlock(input, inOff, mac, 0);
  
  					len -= blockSize;
  					inOff += blockSize;
  				}
  			}
  
  			Array.Copy(input, inOff, buf, bufOff, len);
  
  			bufOff += len;
  		}
  
  		public int DoFinal(
  			byte[]	output,
  			int		outOff)
  		{
  			int blockSize = cipher.GetBlockSize();
  
  			if (padding == null)
  			{
  				// pad with zeroes
  				while (bufOff < blockSize)
  				{
  					buf[bufOff++] = 0;
  				}
  			}
  			else
  			{
  				if (bufOff == blockSize)
  				{
  					cipher.ProcessBlock(buf, 0, mac, 0);
  					bufOff = 0;
  				}
  
  				padding.AddPadding(buf, bufOff);
  			}
  
  			cipher.ProcessBlock(buf, 0, mac, 0);
  
  			// Added to code from base class
  			DesEngine deseng = new DesEngine();
  
  			deseng.Init(false, this.lastKey2);
  			deseng.ProcessBlock(mac, 0, mac, 0);
  
  			deseng.Init(true, this.lastKey3);
  			deseng.ProcessBlock(mac, 0, mac, 0);
  			// ****
  
  			Array.Copy(mac, 0, output, outOff, macSize);
  
  			Reset();
  
  			return macSize;
  		}
  
  		/**
  		* Reset the mac generator.
  		*/
  		public void Reset()
  		{
  
gt: 			Array.Clear(buf, 0, buf.Length);