|  | //#define Trace | 
|  |  | 
|  | // WinZipAes.cs | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // Copyright (c) 2009-2011 Dino Chiesa. | 
|  | // All rights reserved. | 
|  | // | 
|  | // This code module is part of DotNetZip, a zipfile class library. | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This code is licensed under the Microsoft Public License. | 
|  | // See the file License.txt for the license details. | 
|  | // More info on: http://dotnetzip.codeplex.com | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // last saved (in emacs): | 
|  | // Time-stamp: <2011-July-12 13:42:06> | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  | // | 
|  | // This module defines the classes for dealing with WinZip's AES encryption, | 
|  | // according to the specifications for the format available on WinZip's website. | 
|  | // | 
|  | // Created: January 2009 | 
|  | // | 
|  | // ------------------------------------------------------------------ | 
|  |  | 
|  | using System; | 
|  | using System.IO; | 
|  | using System.Collections.Generic; | 
|  | using System.Security.Cryptography; | 
|  |  | 
|  | #if AESCRYPTO | 
|  | namespace OfficeOpenXml.Packaging.Ionic.Zip | 
|  | { | 
|  | /// <summary> | 
|  | ///   This is a helper class supporting WinZip AES encryption. | 
|  | ///   This class is intended for use only by the DotNetZip library. | 
|  | /// </summary> | 
|  | /// | 
|  | /// <remarks> | 
|  | ///   Most uses of the DotNetZip library will not involve direct calls into | 
|  | ///   the WinZipAesCrypto class.  Instead, the WinZipAesCrypto class is | 
|  | ///   instantiated and used by the ZipEntry() class when WinZip AES | 
|  | ///   encryption or decryption on an entry is employed. | 
|  | /// </remarks> | 
|  | internal class WinZipAesCrypto | 
|  | { | 
|  | internal byte[] _Salt; | 
|  | internal byte[] _providedPv; | 
|  | internal byte[] _generatedPv; | 
|  | internal int _KeyStrengthInBits; | 
|  | private byte[] _MacInitializationVector; | 
|  | private byte[] _StoredMac; | 
|  | private byte[] _keyBytes; | 
|  | private Int16 PasswordVerificationStored; | 
|  | private Int16 PasswordVerificationGenerated; | 
|  | private int Rfc2898KeygenIterations = 1000; | 
|  | private string _Password; | 
|  | private bool _cryptoGenerated ; | 
|  |  | 
|  | private WinZipAesCrypto(string password, int KeyStrengthInBits) | 
|  | { | 
|  | _Password = password; | 
|  | _KeyStrengthInBits = KeyStrengthInBits; | 
|  | } | 
|  |  | 
|  | public static WinZipAesCrypto Generate(string password, int KeyStrengthInBits) | 
|  | { | 
|  | WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits); | 
|  |  | 
|  | int saltSizeInBytes = c._KeyStrengthInBytes / 2; | 
|  | c._Salt = new byte[saltSizeInBytes]; | 
|  | Random rnd = new Random(); | 
|  | rnd.NextBytes(c._Salt); | 
|  | return c; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | public static WinZipAesCrypto ReadFromStream(string password, int KeyStrengthInBits, Stream s) | 
|  | { | 
|  | // from http://www.winzip.com/aes_info.htm | 
|  | // | 
|  | // Size(bytes)   Content | 
|  | // ----------------------------------- | 
|  | // Variable      Salt value | 
|  | // 2             Password verification value | 
|  | // Variable      Encrypted file data | 
|  | // 10            Authentication code | 
|  | // | 
|  | // ZipEntry.CompressedSize represents the size of all of those elements. | 
|  |  | 
|  | // salt size varies with key length: | 
|  | //    128 bit key => 8 bytes salt | 
|  | //    192 bits => 12 bytes salt | 
|  | //    256 bits => 16 bytes salt | 
|  |  | 
|  | WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits); | 
|  |  | 
|  | int saltSizeInBytes = c._KeyStrengthInBytes / 2; | 
|  | c._Salt = new byte[saltSizeInBytes]; | 
|  | c._providedPv = new byte[2]; | 
|  |  | 
|  | s.Read(c._Salt, 0, c._Salt.Length); | 
|  | s.Read(c._providedPv, 0, c._providedPv.Length); | 
|  |  | 
|  | c.PasswordVerificationStored = (Int16)(c._providedPv[0] + c._providedPv[1] * 256); | 
|  | if (password != null) | 
|  | { | 
|  | c.PasswordVerificationGenerated = (Int16)(c.GeneratedPV[0] + c.GeneratedPV[1] * 256); | 
|  | if (c.PasswordVerificationGenerated != c.PasswordVerificationStored) | 
|  | throw new BadPasswordException("bad password"); | 
|  | } | 
|  |  | 
|  | return c; | 
|  | } | 
|  |  | 
|  | public byte[] GeneratedPV | 
|  | { | 
|  | get | 
|  | { | 
|  | if (!_cryptoGenerated) _GenerateCryptoBytes(); | 
|  | return _generatedPv; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public byte[] Salt | 
|  | { | 
|  | get | 
|  | { | 
|  | return _Salt; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private int _KeyStrengthInBytes | 
|  | { | 
|  | get | 
|  | { | 
|  | return _KeyStrengthInBits / 8; | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | public int SizeOfEncryptionMetadata | 
|  | { | 
|  | get | 
|  | { | 
|  | // 10 bytes after, (n-10) before the compressed data | 
|  | return _KeyStrengthInBytes / 2 + 10 + 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | public string Password | 
|  | { | 
|  | set | 
|  | { | 
|  | _Password = value; | 
|  | if (_Password != null) | 
|  | { | 
|  | PasswordVerificationGenerated = (Int16)(GeneratedPV[0] + GeneratedPV[1] * 256); | 
|  | if (PasswordVerificationGenerated != PasswordVerificationStored) | 
|  | throw new Ionic.Zip.BadPasswordException(); | 
|  | } | 
|  | } | 
|  | private get | 
|  | { | 
|  | return _Password; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private void _GenerateCryptoBytes() | 
|  | { | 
|  | //Console.WriteLine(" provided password: '{0}'", _Password); | 
|  |  | 
|  | System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 = | 
|  | new System.Security.Cryptography.Rfc2898DeriveBytes(_Password, Salt, Rfc2898KeygenIterations); | 
|  |  | 
|  | _keyBytes = rfc2898.GetBytes(_KeyStrengthInBytes); // 16 or 24 or 32 ??? | 
|  | _MacInitializationVector = rfc2898.GetBytes(_KeyStrengthInBytes); | 
|  | _generatedPv = rfc2898.GetBytes(2); | 
|  |  | 
|  | _cryptoGenerated = true; | 
|  | } | 
|  |  | 
|  |  | 
|  | public byte[] KeyBytes | 
|  | { | 
|  | get | 
|  | { | 
|  | if (!_cryptoGenerated) _GenerateCryptoBytes(); | 
|  | return _keyBytes; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public byte[] MacIv | 
|  | { | 
|  | get | 
|  | { | 
|  | if (!_cryptoGenerated) _GenerateCryptoBytes(); | 
|  | return _MacInitializationVector; | 
|  | } | 
|  | } | 
|  |  | 
|  | public byte[] CalculatedMac; | 
|  |  | 
|  |  | 
|  | public void ReadAndVerifyMac(System.IO.Stream s) | 
|  | { | 
|  | bool invalid = false; | 
|  |  | 
|  | // read integrityCheckVector. | 
|  | // caller must ensure that the file pointer is in the right spot! | 
|  | _StoredMac = new byte[10];  // aka "authentication code" | 
|  | s.Read(_StoredMac, 0, _StoredMac.Length); | 
|  |  | 
|  | if (_StoredMac.Length != CalculatedMac.Length) | 
|  | invalid = true; | 
|  |  | 
|  | if (!invalid) | 
|  | { | 
|  | for (int i = 0; i < _StoredMac.Length; i++) | 
|  | { | 
|  | if (_StoredMac[i] != CalculatedMac[i]) | 
|  | invalid = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (invalid) | 
|  | throw new Ionic.Zip.BadStateException("The MAC does not match."); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | #region DONT_COMPILE_BUT_KEEP_FOR_POTENTIAL_FUTURE_USE | 
|  | #if NO | 
|  | internal class Util | 
|  | { | 
|  | private static void _Format(System.Text.StringBuilder sb1, | 
|  | byte[] b, | 
|  | int offset, | 
|  | int length) | 
|  | { | 
|  |  | 
|  | System.Text.StringBuilder sb2 = new System.Text.StringBuilder(); | 
|  | sb1.Append("0000    "); | 
|  | int i; | 
|  | for (i = 0; i < length; i++) | 
|  | { | 
|  | int x = offset+i; | 
|  | if (i != 0 && i % 16 == 0) | 
|  | { | 
|  | sb1.Append("    ") | 
|  | .Append(sb2) | 
|  | .Append("\n") | 
|  | .Append(String.Format("{0:X4}    ", i)); | 
|  | sb2.Remove(0,sb2.Length); | 
|  | } | 
|  | sb1.Append(System.String.Format("{0:X2} ", b[x])); | 
|  | if (b[x] >=32 && b[x] <= 126) | 
|  | sb2.Append((char)b[x]); | 
|  | else | 
|  | sb2.Append("."); | 
|  | } | 
|  | if (sb2.Length > 0) | 
|  | { | 
|  | sb1.Append(new String(' ', ((16 - i%16) * 3) + 4)) | 
|  | .Append(sb2); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | internal static string FormatByteArray(byte[] b, int limit) | 
|  | { | 
|  | System.Text.StringBuilder sb1 = new System.Text.StringBuilder(); | 
|  |  | 
|  | if ((limit * 2 > b.Length) || limit == 0) | 
|  | { | 
|  | _Format(sb1, b, 0, b.Length); | 
|  | } | 
|  | else | 
|  | { | 
|  | // first N bytes of the buffer | 
|  | _Format(sb1, b, 0, limit); | 
|  |  | 
|  | if (b.Length > limit * 2) | 
|  | sb1.Append(String.Format("\n   ...({0} other bytes here)....\n", b.Length - limit * 2)); | 
|  |  | 
|  | // last N bytes of the buffer | 
|  | _Format(sb1, b, b.Length - limit, limit); | 
|  | } | 
|  |  | 
|  | return sb1.ToString(); | 
|  | } | 
|  |  | 
|  |  | 
|  | internal static string FormatByteArray(byte[] b) | 
|  | { | 
|  | return FormatByteArray(b, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif | 
|  | #endregion | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   A stream that encrypts as it writes, or decrypts as it reads.  The | 
|  | ///   Crypto is AES in CTR (counter) mode, which is compatible with the AES | 
|  | ///   encryption employed by WinZip 12.0. | 
|  | /// </summary> | 
|  | /// <remarks> | 
|  | ///   <para> | 
|  | ///     The AES/CTR encryption protocol used by WinZip works like this: | 
|  | /// | 
|  | ///       - start with a counter, initialized to zero. | 
|  | /// | 
|  | ///       - to encrypt, take the data by 16-byte blocks. For each block: | 
|  | ///         - apply the transform to the counter | 
|  | ///         - increement the counter | 
|  | ///         - XOR the result of the transform with the plaintext to | 
|  | ///           get the ciphertext. | 
|  | ///         - compute the mac on the encrypted bytes | 
|  | ///       - when finished with all blocks, store the computed MAC. | 
|  | /// | 
|  | ///       - to decrypt, take the data by 16-byte blocks. For each block: | 
|  | ///         - compute the mac on the encrypted bytes, | 
|  | ///         - apply the transform to the counter | 
|  | ///         - increement the counter | 
|  | ///         - XOR the result of the transform with the ciphertext to | 
|  | ///           get the plaintext. | 
|  | ///       - when finished with all blocks, compare the computed MAC against | 
|  | ///         the stored MAC | 
|  | /// | 
|  | ///   </para> | 
|  | /// </remarks> | 
|  | // | 
|  | internal class WinZipAesCipherStream : Stream | 
|  | { | 
|  | private WinZipAesCrypto _params; | 
|  | private System.IO.Stream _s; | 
|  | private CryptoMode _mode; | 
|  | private int _nonce; | 
|  | private bool _finalBlock; | 
|  |  | 
|  | internal HMACSHA1 _mac; | 
|  |  | 
|  | // Use RijndaelManaged from .NET 2.0. | 
|  | // AesManaged came in .NET 3.5, but we want to limit | 
|  | // dependency to .NET 2.0.  AES is just a restricted form | 
|  | // of Rijndael (fixed block size of 128, some crypto modes not supported). | 
|  |  | 
|  | internal RijndaelManaged _aesCipher; | 
|  | internal ICryptoTransform _xform; | 
|  |  | 
|  | private const int BLOCK_SIZE_IN_BYTES = 16; | 
|  |  | 
|  | private byte[] counter = new byte[BLOCK_SIZE_IN_BYTES]; | 
|  | private byte[] counterOut = new byte[BLOCK_SIZE_IN_BYTES]; | 
|  |  | 
|  | // I've had a problem when wrapping a WinZipAesCipherStream inside | 
|  | // a DeflateStream. Calling Read() on the DeflateStream results in | 
|  | // a Read() on the WinZipAesCipherStream, but the buffer is larger | 
|  | // than the total size of the encrypted data, and larger than the | 
|  | // initial Read() on the DeflateStream!  When the encrypted | 
|  | // bytestream is embedded within a larger stream (As in a zip | 
|  | // archive), the Read() doesn't fail with EOF.  This causes bad | 
|  | // data to be returned, and it messes up the MAC. | 
|  |  | 
|  | // This field is used to provide a hard-stop to the size of | 
|  | // data that can be read from the stream.  In Read(), if the buffer or | 
|  | // read request goes beyond the stop, we truncate it. | 
|  |  | 
|  | private long _length; | 
|  | private long _totalBytesXferred; | 
|  | private byte[] _PendingWriteBlock; | 
|  | private int _pendingCount; | 
|  | private byte[] _iobuf; | 
|  |  | 
|  | /// <summary> | 
|  | /// The constructor. | 
|  | /// </summary> | 
|  | /// <param name="s">The underlying stream</param> | 
|  | /// <param name="mode">To either encrypt or decrypt.</param> | 
|  | /// <param name="cryptoParams">The pre-initialized WinZipAesCrypto object.</param> | 
|  | /// <param name="length">The maximum number of bytes to read from the stream.</param> | 
|  | internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, long length, CryptoMode mode) | 
|  | : this(s, cryptoParams, mode) | 
|  | { | 
|  | // don't read beyond this limit! | 
|  | _length = length; | 
|  | //Console.WriteLine("max length of AES stream: {0}", _length); | 
|  | } | 
|  |  | 
|  |  | 
|  | #if WANT_TRACE | 
|  | Stream untransformed; | 
|  | String traceFileUntransformed; | 
|  | Stream transformed; | 
|  | String traceFileTransformed; | 
|  | #endif | 
|  |  | 
|  |  | 
|  | internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, CryptoMode mode) | 
|  | : base() | 
|  | { | 
|  | TraceOutput("-------------------------------------------------------"); | 
|  | TraceOutput("Create {0:X8}", this.GetHashCode()); | 
|  |  | 
|  | _params = cryptoParams; | 
|  | _s = s; | 
|  | _mode = mode; | 
|  | _nonce = 1; | 
|  |  | 
|  | if (_params == null) | 
|  | throw new BadPasswordException("Supply a password to use AES encryption."); | 
|  |  | 
|  | int keySizeInBits = _params.KeyBytes.Length * 8; | 
|  | if (keySizeInBits != 256 && keySizeInBits != 128 && keySizeInBits != 192) | 
|  | throw new ArgumentOutOfRangeException("keysize", | 
|  | "size of key must be 128, 192, or 256"); | 
|  |  | 
|  | _mac = new HMACSHA1(_params.MacIv); | 
|  |  | 
|  | _aesCipher = new System.Security.Cryptography.RijndaelManaged(); | 
|  | _aesCipher.BlockSize = 128; | 
|  | _aesCipher.KeySize = keySizeInBits;  // 128, 192, 256 | 
|  | _aesCipher.Mode = CipherMode.ECB; | 
|  | _aesCipher.Padding = PaddingMode.None; | 
|  |  | 
|  | byte[] iv = new byte[BLOCK_SIZE_IN_BYTES]; // all zeroes | 
|  |  | 
|  | // Create an ENCRYPTOR, regardless whether doing decryption or encryption. | 
|  | // It is reflexive. | 
|  | _xform = _aesCipher.CreateEncryptor(_params.KeyBytes, iv); | 
|  |  | 
|  | if (_mode == CryptoMode.Encrypt) | 
|  | { | 
|  | _iobuf = new byte[2048]; | 
|  | _PendingWriteBlock = new byte[BLOCK_SIZE_IN_BYTES]; | 
|  | } | 
|  |  | 
|  |  | 
|  | #if WANT_TRACE | 
|  | traceFileUntransformed = "unpack\\WinZipAesCipherStream.trace.untransformed.out"; | 
|  | traceFileTransformed = "unpack\\WinZipAesCipherStream.trace.transformed.out"; | 
|  |  | 
|  | untransformed = System.IO.File.Create(traceFileUntransformed); | 
|  | transformed = System.IO.File.Create(traceFileTransformed); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | private void XorInPlace(byte[] buffer, int offset, int count) | 
|  | { | 
|  | for (int i = 0; i < count; i++) | 
|  | { | 
|  | buffer[offset + i] = (byte)(counterOut[i] ^ buffer[offset + i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void WriteTransformOneBlock(byte[] buffer, int offset) | 
|  | { | 
|  | System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4); | 
|  | _xform.TransformBlock(counter, | 
|  | 0, | 
|  | BLOCK_SIZE_IN_BYTES, | 
|  | counterOut, | 
|  | 0); | 
|  | XorInPlace(buffer, offset, BLOCK_SIZE_IN_BYTES); | 
|  | _mac.TransformBlock(buffer, offset, BLOCK_SIZE_IN_BYTES, null, 0); | 
|  | } | 
|  |  | 
|  |  | 
|  | private void WriteTransformBlocks(byte[] buffer, int offset, int count) | 
|  | { | 
|  | int posn = offset; | 
|  | int last = count + offset; | 
|  |  | 
|  | while (posn < buffer.Length && posn < last) | 
|  | { | 
|  | WriteTransformOneBlock (buffer, posn); | 
|  | posn += BLOCK_SIZE_IN_BYTES; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | private void WriteTransformFinalBlock() | 
|  | { | 
|  | if (_pendingCount == 0) | 
|  | throw new InvalidOperationException("No bytes available."); | 
|  |  | 
|  | if (_finalBlock) | 
|  | throw new InvalidOperationException("The final block has already been transformed."); | 
|  |  | 
|  | System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4); | 
|  | counterOut = _xform.TransformFinalBlock(counter, | 
|  | 0, | 
|  | BLOCK_SIZE_IN_BYTES); | 
|  | XorInPlace(_PendingWriteBlock, 0, _pendingCount); | 
|  | _mac.TransformFinalBlock(_PendingWriteBlock, 0, _pendingCount); | 
|  | _finalBlock = true; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | private int ReadTransformOneBlock(byte[] buffer, int offset, int last) | 
|  | { | 
|  | if (_finalBlock) | 
|  | throw new NotSupportedException(); | 
|  |  | 
|  | int bytesRemaining = last - offset; | 
|  | int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES) | 
|  | ? BLOCK_SIZE_IN_BYTES | 
|  | : bytesRemaining; | 
|  |  | 
|  | // update the counter | 
|  | System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4); | 
|  |  | 
|  | // Determine if this is the final block | 
|  | if ((bytesToRead == bytesRemaining) && | 
|  | (_length > 0) && | 
|  | (_totalBytesXferred + last == _length)) | 
|  | { | 
|  | _mac.TransformFinalBlock(buffer, offset, bytesToRead); | 
|  | counterOut = _xform.TransformFinalBlock(counter, | 
|  | 0, | 
|  | BLOCK_SIZE_IN_BYTES); | 
|  | _finalBlock = true; | 
|  | } | 
|  | else | 
|  | { | 
|  | _mac.TransformBlock(buffer, offset, bytesToRead, null, 0); | 
|  | _xform.TransformBlock(counter, | 
|  | 0, // offset | 
|  | BLOCK_SIZE_IN_BYTES, | 
|  | counterOut, | 
|  | 0);  // offset | 
|  | } | 
|  |  | 
|  | XorInPlace(buffer, offset, bytesToRead); | 
|  | return bytesToRead; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | private void ReadTransformBlocks(byte[] buffer, int offset, int count) | 
|  | { | 
|  | int posn = offset; | 
|  | int last = count + offset; | 
|  |  | 
|  | while (posn < buffer.Length && posn < last ) | 
|  | { | 
|  | int n = ReadTransformOneBlock (buffer, posn, last); | 
|  | posn += n; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | public override int Read(byte[] buffer, int offset, int count) | 
|  | { | 
|  | if (_mode == CryptoMode.Encrypt) | 
|  | throw new NotSupportedException(); | 
|  |  | 
|  | if (buffer == null) | 
|  | throw new ArgumentNullException("buffer"); | 
|  |  | 
|  | if (offset < 0) | 
|  | throw new ArgumentOutOfRangeException("offset", | 
|  | "Must not be less than zero."); | 
|  | if (count < 0) | 
|  | throw new ArgumentOutOfRangeException("count", | 
|  | "Must not be less than zero."); | 
|  |  | 
|  | if (buffer.Length < offset + count) | 
|  | throw new ArgumentException("The buffer is too small"); | 
|  |  | 
|  | // When I wrap a WinZipAesStream in a DeflateStream, the | 
|  | // DeflateStream asks its captive to read 4k blocks, even if the | 
|  | // encrypted bytestream is smaller than that.  This is a way to | 
|  | // limit the number of bytes read. | 
|  |  | 
|  | int bytesToRead = count; | 
|  |  | 
|  | if (_totalBytesXferred >= _length) | 
|  | { | 
|  | return 0; // EOF | 
|  | } | 
|  |  | 
|  | long bytesRemaining = _length - _totalBytesXferred; | 
|  | if (bytesRemaining < count) bytesToRead = (int)bytesRemaining; | 
|  |  | 
|  | int n = _s.Read(buffer, offset, bytesToRead); | 
|  |  | 
|  |  | 
|  | #if WANT_TRACE | 
|  | untransformed.Write(buffer, offset, bytesToRead); | 
|  | #endif | 
|  |  | 
|  | ReadTransformBlocks(buffer, offset, bytesToRead); | 
|  |  | 
|  | #if WANT_TRACE | 
|  | transformed.Write(buffer, offset, bytesToRead); | 
|  | #endif | 
|  | _totalBytesXferred += n; | 
|  | return n; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Returns the final HMAC-SHA1-80 for the data that was encrypted. | 
|  | /// </summary> | 
|  | public byte[] FinalAuthentication | 
|  | { | 
|  | get | 
|  | { | 
|  | if (!_finalBlock) | 
|  | { | 
|  | // special-case zero-byte files | 
|  | if ( _totalBytesXferred != 0) | 
|  | throw new BadStateException("The final hash has not been computed."); | 
|  |  | 
|  | // Must call ComputeHash on an empty byte array when no data | 
|  | // has run through the MAC. | 
|  |  | 
|  | byte[] b = {  }; | 
|  | _mac.ComputeHash(b); | 
|  | // fall through | 
|  | } | 
|  | byte[] macBytes10 = new byte[10]; | 
|  | System.Array.Copy(_mac.Hash, 0, macBytes10, 0, 10); | 
|  | return macBytes10; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | public override void Write(byte[] buffer, int offset, int count) | 
|  | { | 
|  | if (_finalBlock) | 
|  | throw new InvalidOperationException("The final block has already been transformed."); | 
|  |  | 
|  | if (_mode == CryptoMode.Decrypt) | 
|  | throw new NotSupportedException(); | 
|  |  | 
|  | if (buffer == null) | 
|  | throw new ArgumentNullException("buffer"); | 
|  |  | 
|  | if (offset < 0) | 
|  | throw new ArgumentOutOfRangeException("offset", | 
|  | "Must not be less than zero."); | 
|  | if (count < 0) | 
|  | throw new ArgumentOutOfRangeException("count", | 
|  | "Must not be less than zero."); | 
|  | if (buffer.Length < offset + count) | 
|  | throw new ArgumentException("The offset and count are too large"); | 
|  |  | 
|  | if (count == 0) | 
|  | return; | 
|  |  | 
|  | TraceOutput("Write off({0}) count({1})", offset, count); | 
|  |  | 
|  | #if WANT_TRACE | 
|  | untransformed.Write(buffer, offset, count); | 
|  | #endif | 
|  |  | 
|  | // For proper AES encryption, an AES encryptor application calls | 
|  | // TransformBlock repeatedly for all 16-byte blocks except the | 
|  | // last. For the last block, it then calls TransformFinalBlock(). | 
|  | // | 
|  | // This class is a stream that encrypts via Write().  But, it's not | 
|  | // possible to recognize which are the "last" bytes from within the call | 
|  | // to Write(). The caller can call Write() several times in succession, | 
|  | // with varying buffers. This class only "knows" that the last bytes | 
|  | // have been written when the app calls Close(). | 
|  | // | 
|  | // Therefore, this class buffers writes: After completion every Write(), | 
|  | // a 16-byte "pending" block (_PendingWriteBlock) must hold between 1 | 
|  | // and 16 bytes, which will be used in TransformFinalBlock if the app | 
|  | // calls Close() immediately thereafter. Also, every write must | 
|  | // transform any pending bytes, before transforming the data passed in | 
|  | // to the current call. | 
|  | // | 
|  | // In operation, after the first call to Write() and before the call to | 
|  | // Close(), one full or partial block of bytes is always available, | 
|  | // pending.  At time of Close(), this class calls | 
|  | // WriteTransformFinalBlock() to flush the pending bytes. | 
|  | // | 
|  | // This approach works whether the caller writes in odd-sized batches, | 
|  | // for example 5000 bytes, or in batches that are neat multiples of the | 
|  | // blocksize (16). | 
|  | // | 
|  | // Logicaly, what we do is this: | 
|  | // | 
|  | //  1. if there are fewer than 16 bytes (pending + current), then | 
|  | //     just copy them into th pending buffer and return. | 
|  | // | 
|  | //  2. there are more than 16 bytes to write. So, take the leading slice | 
|  | //     of bytes from the current buffer, enough to fill the pending | 
|  | //     buffer. Transform the pending block, and write it out. | 
|  | // | 
|  | //  3. Take the trailing slice of bytes (a full block or a partial block), | 
|  | //     and copy it to the pending block for next time. | 
|  | // | 
|  | //  4. transform and write all the other blocks, the middle slice. | 
|  | // | 
|  |  | 
|  | // There are 16 or fewer bytes, so just buffer the bytes. | 
|  | if (count + _pendingCount <= BLOCK_SIZE_IN_BYTES) | 
|  | { | 
|  | Buffer.BlockCopy(buffer, | 
|  | offset, | 
|  | _PendingWriteBlock, | 
|  | _pendingCount, | 
|  | count); | 
|  | _pendingCount += count; | 
|  |  | 
|  | // At this point, _PendingWriteBlock contains up to | 
|  | // BLOCK_SIZE_IN_BYTES bytes, and _pendingCount ranges from 0 to | 
|  | // BLOCK_SIZE_IN_BYTES. We don't want to xform+write them yet, | 
|  | // because this may have been the last block.  The last block gets | 
|  | // written at Close(). | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We know there are at least 17 bytes, counting those in the current | 
|  | // buffer, along with the (possibly empty) pending block. | 
|  |  | 
|  | int bytesRemaining = count; | 
|  | int curOffset = offset; | 
|  |  | 
|  | // workitem 12815 | 
|  | // | 
|  | // xform chunkwise ... Cannot transform in place using the original | 
|  | // buffer because that is user-maintained. | 
|  |  | 
|  | if (_pendingCount != 0) | 
|  | { | 
|  | // We have more than one block of data to write, therefore it is safe | 
|  | // to xform+write. | 
|  | int fillCount = BLOCK_SIZE_IN_BYTES - _pendingCount; | 
|  |  | 
|  | // fillCount is possibly zero here. That happens when the pending | 
|  | // buffer held 16 bytes (one complete block) before this call to | 
|  | // Write. | 
|  | if (fillCount > 0) | 
|  | { | 
|  | Buffer.BlockCopy(buffer, | 
|  | offset, | 
|  | _PendingWriteBlock, | 
|  | _pendingCount, | 
|  | fillCount); | 
|  |  | 
|  | // adjust counts: | 
|  | bytesRemaining -= fillCount; | 
|  | curOffset += fillCount; | 
|  | } | 
|  |  | 
|  | // xform and write: | 
|  | WriteTransformOneBlock(_PendingWriteBlock, 0); | 
|  | _s.Write(_PendingWriteBlock, 0, BLOCK_SIZE_IN_BYTES); | 
|  | _totalBytesXferred += BLOCK_SIZE_IN_BYTES; | 
|  | _pendingCount = 0; | 
|  | } | 
|  |  | 
|  | // At this point _PendingWriteBlock is empty, and bytesRemaining is | 
|  | // always greater than 0. | 
|  |  | 
|  | // Now, xform N blocks, where N = floor((bytesRemaining-1)/16).  If | 
|  | // writing 32 bytes, then xform 1 block, and stage the remaining 16.  If | 
|  | // writing 10037 bytes, xform 627 blocks of 16 bytes, then stage the | 
|  | // remaining 5 bytes. | 
|  |  | 
|  | int blocksToXform = (bytesRemaining-1)/BLOCK_SIZE_IN_BYTES; | 
|  | _pendingCount = bytesRemaining - (blocksToXform * BLOCK_SIZE_IN_BYTES); | 
|  |  | 
|  | // _pendingCount is ALWAYS between 1 and 16. | 
|  | // Put the last _pendingCount bytes into the pending block. | 
|  | Buffer.BlockCopy(buffer, | 
|  | curOffset + bytesRemaining - _pendingCount, | 
|  | _PendingWriteBlock, | 
|  | 0, | 
|  | _pendingCount); | 
|  | bytesRemaining -= _pendingCount; | 
|  | _totalBytesXferred += bytesRemaining; // will be true after the loop | 
|  |  | 
|  | // now, transform all the full blocks preceding that. | 
|  | // bytesRemaining is always a multiple of 16 . | 
|  | if (blocksToXform > 0) | 
|  | { | 
|  | do | 
|  | { | 
|  | int c = _iobuf.Length; | 
|  | if (c > bytesRemaining) c = bytesRemaining; | 
|  | Buffer.BlockCopy(buffer, | 
|  | curOffset, | 
|  | _iobuf, | 
|  | 0, | 
|  | c); | 
|  |  | 
|  | WriteTransformBlocks(_iobuf, 0, c); | 
|  | _s.Write(_iobuf, 0, c); | 
|  | bytesRemaining -= c; | 
|  | curOffset += c; | 
|  | } while(bytesRemaining > 0); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | ///   Close the stream. | 
|  | /// </summary> | 
|  | public override void Close() | 
|  | { | 
|  | TraceOutput("Close {0:X8}", this.GetHashCode()); | 
|  |  | 
|  | // In the degenerate case, no bytes have been written to the | 
|  | // stream at all.  Need to check here, and NOT emit the | 
|  | // final block if Write has not been called. | 
|  | if (_pendingCount > 0) | 
|  | { | 
|  | WriteTransformFinalBlock(); | 
|  | _s.Write(_PendingWriteBlock, 0, _pendingCount); | 
|  | _totalBytesXferred += _pendingCount; | 
|  | _pendingCount = 0; | 
|  | } | 
|  | _s.Close(); | 
|  |  | 
|  | #if WANT_TRACE | 
|  | untransformed.Close(); | 
|  | transformed.Close(); | 
|  | Console.WriteLine("\nuntransformed bytestream is in  {0}", traceFileUntransformed); | 
|  | Console.WriteLine("\ntransformed bytestream is in  {0}", traceFileTransformed); | 
|  | #endif | 
|  | TraceOutput("-------------------------------------------------------"); | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Returns true if the stream can be read. | 
|  | /// </summary> | 
|  | public override bool CanRead | 
|  | { | 
|  | get | 
|  | { | 
|  | if (_mode != CryptoMode.Decrypt) return false; | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /// <summary> | 
|  | /// Always returns false. | 
|  | /// </summary> | 
|  | public override bool CanSeek | 
|  | { | 
|  | get { return false; } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Returns true if the CryptoMode is Encrypt. | 
|  | /// </summary> | 
|  | public override bool CanWrite | 
|  | { | 
|  | get { return (_mode == CryptoMode.Encrypt); } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Flush the content in the stream. | 
|  | /// </summary> | 
|  | public override void Flush() | 
|  | { | 
|  | _s.Flush(); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Getting this property throws a NotImplementedException. | 
|  | /// </summary> | 
|  | public override long Length | 
|  | { | 
|  | get { throw new NotImplementedException(); } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Getting or Setting this property throws a NotImplementedException. | 
|  | /// </summary> | 
|  | public override long Position | 
|  | { | 
|  | get { throw new NotImplementedException(); } | 
|  | set { throw new NotImplementedException(); } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// This method throws a NotImplementedException. | 
|  | /// </summary> | 
|  | public override long Seek(long offset, System.IO.SeekOrigin origin) | 
|  | { | 
|  | throw new NotImplementedException(); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// This method throws a NotImplementedException. | 
|  | /// </summary> | 
|  | public override void SetLength(long value) | 
|  | { | 
|  | throw new NotImplementedException(); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | [System.Diagnostics.ConditionalAttribute("Trace")] | 
|  | private void TraceOutput(string format, params object[] varParams) | 
|  | { | 
|  | lock(_outputLock) | 
|  | { | 
|  | int tid = System.Threading.Thread.CurrentThread.GetHashCode(); | 
|  | Console.ForegroundColor = (ConsoleColor) (tid % 8 + 8); | 
|  | Console.Write("{0:000} WZACS ", tid); | 
|  | Console.WriteLine(format, varParams); | 
|  | Console.ResetColor(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private object _outputLock = new Object(); | 
|  | } | 
|  | } | 
|  | #endif |