| //#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 |