context: // This file is part of SNMP#NET.
  // 
  // SNMP#NET is free software: you can redistribute it and/or modify
  // it under the terms of the GNU Lesser Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  // 
  // SNMP#NET is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  // 
  // You should have received a copy of the GNU General Public License
  // along with SNMP#NET.  If not, see <http://www.gnu.org/licenses/>.
  // 
  using System;
  using System.Security.Cryptography;
  
  namespace SnmpSharpNet
  {
  	/// <summary>DES privacy protocol implementation class.</summary>
  	/// <remarks>
  	/// SNMP Version 3 DES privacy protocol implementation.
  	/// 
  	/// DES requires an encryption key be provided of 16 bytes in length. Class will accept longer key values (which will be
  	/// trimmed to 16 bytes) but both encrypt and decrypt operations will fail if key is shorter then required value length.
  	/// 
  	/// Decryption operation depends on USM header privacyParameters field value which is generated by the Encrypt method. Make
  	/// sure privacyParameters argument value is correctly inserted into the target packet to enable SNMP agent to decrypt the
  	/// message.
  	/// </remarks>
  	public class PrivacyDES: IPrivacyProtocol
  	{
  		/// <summary>
  		/// Internal salt value. As per RFC standard, salt value is initialized in the constructor and incremented by 1 for each
  		/// subsequent packet.
  		/// </summary>
  		protected Int32 _salt = 0;
  		/// <summary>
  		/// Standard constructor.
  		/// </summary>
  		public PrivacyDES()
  		{
  			Random random = new Random();
  			_salt = random.Next(1, Int32.MaxValue);
  		}
  		/// <summary>
  		/// Returns next salt value.
  		/// </summary>
  		/// <returns>32-bit integer salt value in network byte order (big endian)</returns>
  		protected int NextSalt()
  		{
  			if (_salt == Int32.MaxValue)
  				_salt = 1;
  			else
  				_salt += 1;
  			return _salt;
  		}
  		/// <summary>
  		/// Encrypt ScopedPdu using DES encryption protocol
  		/// </summary>
  		/// <param name="unencryptedData">Unencrypted ScopedPdu byte array</param>
  		/// <param name="offset">Offset to start encryption</param>
  		/// <param name="length">Length of data to encrypt</param>
  		/// <param name="key">Encryption key. Key has to be at least 32 bytes is length</param>
  		/// <param name="engineBoots">Authoritative engine boots value</param>
  		/// <param name="engineTime">Authoritative engine time value. Not used for DES</param>
  		/// <param name="privacyParameters">Privacy parameters out buffer. This field will be filled in with information
  		/// required to decrypt the information. Output length of this field is 8 bytes and space has to be reserved
  		/// in the USM header to store this information</param>
  		/// <param name="authDigest">Authentication digest class reference. Not used by DES and can be null.</param>
  		/// <returns>Encrypted byte array</returns>
  		/// <exception cref="ArgumentOutOfRangeException">Thrown when encryption key is null or length of the encryption key is too short.</exception>
  		public byte[] Encrypt(byte[] unencryptedData, int offset, int length, byte[] key, int engineBoots, int engineTime, out byte[] privacyParameters, IAuthenticationDigest authDigest)
  		{
  			if (key == null || key.Length < MinimumKeyLength)
  				throw new ArgumentOutOfRangeException("encryptionKey", "Encryption key length has to 32 bytes or more.");
  
  			privacyParameters = GetSalt(engineBoots);
  			byte[] iv = GetIV(key, privacyParameters);
  
  			// DES uses 8 byte keys but we need 16 to encrypt ScopedPdu. Get first 8 bytes and use them as encryption key
  			byte[] outKey = GetKey(key);
  
  
  			int div = (int)Math.Floor(length / 8.0);
  			if ((length % 8) != 0)
  				div += 1;
  			int newLength = div * 8;
  			byte[] result = new byte[newLength];
  			byte[] buffer = new byte[newLength];
  
  			byte[] inbuffer = new byte[8];
  			byte[] cipherText = iv;
  			int posIn = 0;
  			int posResult = 0;
  			Buffer.BlockCopy(unencryptedData, offset, buffer, 0, length);
  
  			DES des = new DESCryptoServiceProvider();
  			des.Mode = CipherMode.ECB;
  			des.Padding = PaddingMode.None;
  
  			ICryptoTransform transform = des.CreateEncryptor(outKey, null);
  			for (int b = 0; b < div; b++)
  			{
  				for (int i = 0; i < 8; i++)
  				{
  					inbuffer[i] = (byte)(buffer[posIn] ^ cipherText[i]);
  					posIn++;
  				}
  				transform.TransformBlock(inbuffer, 0, inbuffer.Length, cipherText, 0);
  				Buffer.BlockCopy(cipherText, 0, result, posResult, cipherText.Length);
  				posResult += cipherText.Length;
  			}
  
  			des.Clear();
  
  			return result;
  		}
  
  		/// <summary>
  		/// Decrypt DES encrypted ScopedPdu
  		/// </summary>
  		/// <param name="encryptedData">Source data buffer</param>
  		/// <param name="offset">Offset within the buffer to start decryption process</param>
  		/// <param name="length">Length of data to decrypt</param>
  		/// <param name="key">Decryption key. Key length has to be 32 bytes in length or longer (bytes beyond 32 bytes are ignored).</param>
  		/// <param name="engineBoots">Authoritative engine boots value</param>
  		/// <param name="engineTime">Authoritative engine time value</param>
  		/// <param name="privacyParameters">Privacy parameters extracted from USM header</param>
  		/// <returns>Decrypted byte array</returns>
  		/// <exception cref="ArgumentNullException">Thrown when encrypted data is null or length == 0</exception>
  		/// <exception cref="ArgumentOutOfRangeException">Thrown when encryption key length is less then 32 byte or if privacy parameters
  		/// argument is null or length other then 8 bytes</exception>
  		public byte[] Decrypt(byte[] encryptedData, int offset, int length, byte[] key, int engineBoots, int engineTime, byte[] privacyParameters)
  		{
  			if ((length % 8) != 0)
  				throw new ArgumentOutOfRangeException("encryptedData", "Encrypted data buffer has to be divisible by 8.");
  			if (encryptedData == null || encryptedData.Length == 0)
  				throw new ArgumentNullException("cryptedData");
  			if (privacyParameters == null || privacyParameters.Length != PrivacyParametersLength)
  				throw new ArgumentOutOfRangeException("privacyParameters", "Privacy parameters argument has to be 8 bytes long");
  
  			if (key == null || key.Length < MinimumKeyLength)
  				throw new ArgumentOutOfRangeException("decryptionKey", "Decryption key has to be at least 16 bytes long.");
  
  			byte[] iv = new byte[8];
  			for (int i = 0; i < 8; ++i)
  			{
  				iv[i] = (byte)(key[8 + i] ^ privacyParameters[i]);
  			}
  			DES des = new DESCryptoServiceProvider();
  			des.Mode = CipherMode.CBC;
  			des.Padding = PaddingMode.Zeros;
  
  			// .NET implementation only takes an 8 byte key
  			byte[] outKey = new byte[8];
  			Buffer.BlockCopy(key, 0, outKey, 0, 8);
  
  			des.Key = outKey;
  			des.IV = iv;
  			ICryptoTransform transform = des.CreateDecryptor();
  
  			byte[] decryptedData = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
  			des.Clear();
  
  			return decryptedData;
  		}
  
  		/// <summary>
  		/// Returns minimum encryption/decryption key length. For DES, returned value is 16.
  		/// </summary>
  		/// <remarks>
  		/// DES protocol requires an 8 byte encryption key and additional 8 bytes are used for generating the
  		/// encryption IV.
  		/// </remarks>
  		public int MinimumKeyLength
  		{
  			get
  			{
  				return 16;
  			}
  		}
  		/// <summary>
  		/// Return maximum encryption/decryption key length. For DES, returned value is 16
  		/// </summary>
  		/// <remarks>
  		/// DES protocol requires an 8 byte encryption key and additional 8 bytes are used for generating the
  		/// encryption IV.
  		/// </remarks>
  		public int MaximumKeyLength
  		{
  			get { return 16; }
  		}
  
  		/// <summary>
  		/// Get final encrypted length
  		/// </summary>
  		/// <param name="scopedPduLength">BER encoded ScopedPdu data length</param>
  		/// <returns>Length of encrypted byte array</returns>
  		public int GetEncryptedLength(int scopedPduLength)
  		{
  			if (scopedPduLength % 8 == 0)
  			{
  				return scopedPduLength;
  			}
  			return 8 * ((scopedPduLength / 8) + 1);
  		}
  
  		/// <summary>
  		/// Returns the length of privacyParameters USM header field. For DES, field length is 8.
  		/// </summary>
  		public int PrivacyParametersLength
  		{
  			get
  			{
  				return 8;
  			}
  		}
  
  		/// <summary>
  		/// Operation not used by DES. Key length has to be 16 bytes of encryption/decryption operation will fail.
  		/// 
  		/// When called, shortKey is returned.
  		/// </summary>
  		/// <param name="shortKey">Encryption key</param>
  		/// <param name="password">Privacy password</param>
  		/// <param name="engineID">Authoritative engine id</param>
  		/// <param name="authProtocol">Authentication protocol class instance</param>
  		/// <returns>unaltered shortKey value</returns>
  		public byte[] ExtendShortKey(byte[] shortKey, byte[] password, byte[] engineID, IAuthenticationDigest authProtocol)
  		{
  			return shortKey;
  		}
  
  		/// <summary>
  		/// Privacy protocol name
  		/// </summary>
  		public string Name
  		{
  			get { return "DES"; }
  		}
  
  		/// <summary>
  		/// DES implementation does NOT support extending of a short encryption key. Always returns false.
  		/// </summary>
  		public bool CanExtendShortKey
  		{
  			get
  			{
  				return false;
  			}
  		}
  
  		/// <summary>
  		/// Get DES encryption salt value. Salt value is generated by concatenating engineBoots value with
  		/// the random integer value.
  		/// </summary>
  		/// <param name="engineBoots">SNMP engine boots value</param>
  		/// <returns>Salt byte array 8 byte in length</returns>
  		private byte[] GetSalt(int engineBoots)
  		{
  			byte[] salt = new byte[8]; // salt is 8 bytes
  			int s = NextSalt();
  			byte[] eb = BitConverter.GetBytes(engineBoots);
  			// C# is little endian so reverse the values
  			salt[3] = eb[0];
  			salt[2] = eb[1];
  			salt[1] = eb[2];
  			salt[0] = eb[3];
  			byte[] sl = BitConverter.GetBytes(s);
  			salt[7] = sl[0];
  			salt[6] = sl[1];
  			salt[5] = sl[2];
  			salt[4] = sl[3];
  			return salt;
  		}
  		/// <summary>
  		/// Extract and return DES encryption key.
  		/// 
  		/// Privacy password is 16 bytes in length. Only the first 8 bytes are used as DES password. Remaining
  		/// 8 bytes are used as pre-IV value.
  		/// </summary>
  		/// <param name="privacyPassword">16 byte privacy password</param>
  		/// <returns>8 byte DES encryption password</returns>
  		/// <exception cref="SnmpPrivacyException">Thrown when privacy password is less then 16 bytes long</exception>
  		private byte[] GetKey(byte[] privacyPassword)
  		{
  			if (privacyPassword == null || privacyPassword.Length < 16)
  				throw new SnmpPrivacyException("Invalid privacy key length.");
  			byte[] key = new byte[8];
  			Buffer.BlockCopy(privacyPassword, 0, key, 0, 8);
  			return key;
  		}
  		/// <summary>
  		/// Generate IV from the privacy key and salt value returned by GetSalt method.
  		/// </summary>
  		/// <param name="privacyKey">16 byte privacy key</param>
  		/// <param name="salt">Salt value returned by GetSalt method</param>
  		/// <returns>IV value used in the encryption process</returns>
  		/// <exception cref="SnmpPrivacyException">Thrown when privacy key is less then 16 bytes long.</exception>
  		private byte[] GetIV(byte[] privacyKey, byte[] salt)
  		{
  			if (privacyKey.Length < 16)
  
gt: 				throw new SnmpPrivacyException("Invalid privacy key length");