using System; namespace Renci.SshNet.Security.Cryptography.Ciphers { /// /// Implements ARCH4 cipher algorithm /// public sealed class Arc4Cipher : StreamCipher { private static readonly int STATE_LENGTH = 256; /// /// Holds the state of the RC4 engine /// private byte[] _engineState; private int _x; private int _y; private byte[] _workingKey; /// /// Gets the minimum data size. /// /// /// The minimum data size. /// public override byte MinimumSize { get { return 0; } } /// /// Initializes a new instance of the class. /// /// The key. /// if set to true will disharged first 1536 bytes. /// is null. public Arc4Cipher(byte[] key, bool dischargeFirstBytes) : base(key) { _workingKey = key; SetKey(_workingKey); // The first 1536 bytes of keystream // generated by the cipher MUST be discarded, and the first byte of the // first encrypted packet MUST be encrypted using the 1537th byte of // keystream. if (dischargeFirstBytes) Encrypt(new byte[1536]); } /// /// Encrypts the specified region of the input byte array and copies the encrypted data to the specified region of the output byte array. /// /// The input data to encrypt. /// The offset into the input byte array from which to begin using data. /// The number of bytes in the input byte array to use as data. /// The output to which to write encrypted data. /// The offset into the output byte array from which to begin writing data. /// /// The number of bytes encrypted. /// public override int EncryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { return ProcessBytes(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset); } /// /// Decrypts the specified region of the input byte array and copies the decrypted data to the specified region of the output byte array. /// /// The input data to decrypt. /// The offset into the input byte array from which to begin using data. /// The number of bytes in the input byte array to use as data. /// The output to which to write decrypted data. /// The offset into the output byte array from which to begin writing data. /// /// The number of bytes decrypted. /// public override int DecryptBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { return ProcessBytes(inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset); } /// /// Encrypts the specified input. /// /// The input. /// The zero-based offset in at which to begin encrypting. /// The number of bytes to encrypt from . /// /// Encrypted data. /// public override byte[] Encrypt(byte[] input, int offset, int length) { var output = new byte[length]; ProcessBytes(input, offset, length, output, 0); return output; } /// /// Decrypts the specified input. /// /// The input. /// /// The decrypted data. /// public override byte[] Decrypt(byte[] input) { return Decrypt(input, 0, input.Length); } /// /// Decrypts the specified input. /// /// The input. /// The zero-based offset in at which to begin decrypting. /// The number of bytes to decrypt from . /// /// The decrypted data. /// public override byte[] Decrypt(byte[] input, int offset, int length) { var output = new byte[length]; ProcessBytes(input, offset, length, output, 0); return output; } private int ProcessBytes(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) { if ((inputOffset + inputCount) > inputBuffer.Length) { throw new IndexOutOfRangeException("input buffer too short"); } if ((outputOffset + inputCount) > outputBuffer.Length) { throw new IndexOutOfRangeException("output buffer too short"); } for (var i = 0; i < inputCount; i++) { _x = (_x + 1) & 0xff; _y = (_engineState[_x] + _y) & 0xff; // swap var tmp = _engineState[_x]; _engineState[_x] = _engineState[_y]; _engineState[_y] = tmp; // xor outputBuffer[i + outputOffset] = (byte)(inputBuffer[i + inputOffset] ^ _engineState[(_engineState[_x] + _engineState[_y]) & 0xff]); } return inputCount; } private void SetKey(byte[] keyBytes) { _workingKey = keyBytes; _x = 0; _y = 0; if (_engineState == null) { _engineState = new byte[STATE_LENGTH]; } // reset the state of the engine for (var i = 0; i < STATE_LENGTH; i++) { _engineState[i] = (byte) i; } var i1 = 0; var i2 = 0; for (var i = 0; i < STATE_LENGTH; i++) { i2 = ((keyBytes[i1] & 0xff) + _engineState[i] + i2) & 0xff; // do the byte-swap inline var tmp = _engineState[i]; _engineState[i] = _engineState[i2]; _engineState[i2] = tmp; i1 = (i1 + 1) % keyBytes.Length; } } } }