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