Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2213_irace/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/WinZipAes.cs @ 18242

Last change on this file since 18242 was 12074, checked in by sraggl, 9 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

File size: 33.0 KB
Line 
1//#define Trace
2
3// WinZipAes.cs
4// ------------------------------------------------------------------
5//
6// Copyright (c) 2009-2011 Dino Chiesa.
7// All rights reserved.
8//
9// This code module is part of DotNetZip, a zipfile class library.
10//
11// ------------------------------------------------------------------
12//
13// This code is licensed under the Microsoft Public License.
14// See the file License.txt for the license details.
15// More info on: http://dotnetzip.codeplex.com
16//
17// ------------------------------------------------------------------
18//
19// last saved (in emacs):
20// Time-stamp: <2011-July-12 13:42:06>
21//
22// ------------------------------------------------------------------
23//
24// This module defines the classes for dealing with WinZip's AES encryption,
25// according to the specifications for the format available on WinZip's website.
26//
27// Created: January 2009
28//
29// ------------------------------------------------------------------
30
31using System;
32using System.IO;
33using System.Collections.Generic;
34using System.Security.Cryptography;
35
36#if AESCRYPTO
37namespace OfficeOpenXml.Packaging.Ionic.Zip
38{
39    /// <summary>
40    ///   This is a helper class supporting WinZip AES encryption.
41    ///   This class is intended for use only by the DotNetZip library.
42    /// </summary>
43    ///
44    /// <remarks>
45    ///   Most uses of the DotNetZip library will not involve direct calls into
46    ///   the WinZipAesCrypto class.  Instead, the WinZipAesCrypto class is
47    ///   instantiated and used by the ZipEntry() class when WinZip AES
48    ///   encryption or decryption on an entry is employed.
49    /// </remarks>
50    internal class WinZipAesCrypto
51    {
52        internal byte[] _Salt;
53        internal byte[] _providedPv;
54        internal byte[] _generatedPv;
55        internal int _KeyStrengthInBits;
56        private byte[] _MacInitializationVector;
57        private byte[] _StoredMac;
58        private byte[] _keyBytes;
59        private Int16 PasswordVerificationStored;
60        private Int16 PasswordVerificationGenerated;
61        private int Rfc2898KeygenIterations = 1000;
62        private string _Password;
63        private bool _cryptoGenerated ;
64
65        private WinZipAesCrypto(string password, int KeyStrengthInBits)
66        {
67            _Password = password;
68            _KeyStrengthInBits = KeyStrengthInBits;
69        }
70
71        public static WinZipAesCrypto Generate(string password, int KeyStrengthInBits)
72        {
73            WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
74
75            int saltSizeInBytes = c._KeyStrengthInBytes / 2;
76            c._Salt = new byte[saltSizeInBytes];
77            Random rnd = new Random();
78            rnd.NextBytes(c._Salt);
79            return c;
80        }
81
82
83
84        public static WinZipAesCrypto ReadFromStream(string password, int KeyStrengthInBits, Stream s)
85        {
86            // from http://www.winzip.com/aes_info.htm
87            //
88            // Size(bytes)   Content
89            // -----------------------------------
90            // Variable      Salt value
91            // 2             Password verification value
92            // Variable      Encrypted file data
93            // 10            Authentication code
94            //
95            // ZipEntry.CompressedSize represents the size of all of those elements.
96
97            // salt size varies with key length:
98            //    128 bit key => 8 bytes salt
99            //    192 bits => 12 bytes salt
100            //    256 bits => 16 bytes salt
101
102            WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
103
104            int saltSizeInBytes = c._KeyStrengthInBytes / 2;
105            c._Salt = new byte[saltSizeInBytes];
106            c._providedPv = new byte[2];
107
108            s.Read(c._Salt, 0, c._Salt.Length);
109            s.Read(c._providedPv, 0, c._providedPv.Length);
110
111            c.PasswordVerificationStored = (Int16)(c._providedPv[0] + c._providedPv[1] * 256);
112            if (password != null)
113            {
114                c.PasswordVerificationGenerated = (Int16)(c.GeneratedPV[0] + c.GeneratedPV[1] * 256);
115                if (c.PasswordVerificationGenerated != c.PasswordVerificationStored)
116                    throw new BadPasswordException("bad password");
117            }
118
119            return c;
120        }
121
122        public byte[] GeneratedPV
123        {
124            get
125            {
126                if (!_cryptoGenerated) _GenerateCryptoBytes();
127                return _generatedPv;
128            }
129        }
130
131
132        public byte[] Salt
133        {
134            get
135            {
136                return _Salt;
137            }
138        }
139
140
141        private int _KeyStrengthInBytes
142        {
143            get
144            {
145                return _KeyStrengthInBits / 8;
146
147            }
148        }
149
150        public int SizeOfEncryptionMetadata
151        {
152            get
153            {
154                // 10 bytes after, (n-10) before the compressed data
155                return _KeyStrengthInBytes / 2 + 10 + 2;
156            }
157        }
158
159        public string Password
160        {
161            set
162            {
163                _Password = value;
164                if (_Password != null)
165                {
166                    PasswordVerificationGenerated = (Int16)(GeneratedPV[0] + GeneratedPV[1] * 256);
167                    if (PasswordVerificationGenerated != PasswordVerificationStored)
168                        throw new Ionic.Zip.BadPasswordException();
169                }
170            }
171            private get
172            {
173                return _Password;
174            }
175        }
176
177
178        private void _GenerateCryptoBytes()
179        {
180            //Console.WriteLine(" provided password: '{0}'", _Password);
181
182            System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
183                new System.Security.Cryptography.Rfc2898DeriveBytes(_Password, Salt, Rfc2898KeygenIterations);
184
185            _keyBytes = rfc2898.GetBytes(_KeyStrengthInBytes); // 16 or 24 or 32 ???
186            _MacInitializationVector = rfc2898.GetBytes(_KeyStrengthInBytes);
187            _generatedPv = rfc2898.GetBytes(2);
188
189            _cryptoGenerated = true;
190        }
191
192
193        public byte[] KeyBytes
194        {
195            get
196            {
197                if (!_cryptoGenerated) _GenerateCryptoBytes();
198                return _keyBytes;
199            }
200        }
201
202
203        public byte[] MacIv
204        {
205            get
206            {
207                if (!_cryptoGenerated) _GenerateCryptoBytes();
208                return _MacInitializationVector;
209            }
210        }
211
212        public byte[] CalculatedMac;
213
214
215        public void ReadAndVerifyMac(System.IO.Stream s)
216        {
217            bool invalid = false;
218
219            // read integrityCheckVector.
220            // caller must ensure that the file pointer is in the right spot!
221            _StoredMac = new byte[10];  // aka "authentication code"
222            s.Read(_StoredMac, 0, _StoredMac.Length);
223
224            if (_StoredMac.Length != CalculatedMac.Length)
225                invalid = true;
226
227            if (!invalid)
228            {
229                for (int i = 0; i < _StoredMac.Length; i++)
230                {
231                    if (_StoredMac[i] != CalculatedMac[i])
232                        invalid = true;
233                }
234            }
235
236            if (invalid)
237                throw new Ionic.Zip.BadStateException("The MAC does not match.");
238        }
239
240    }
241
242
243    #region DONT_COMPILE_BUT_KEEP_FOR_POTENTIAL_FUTURE_USE
244#if NO
245    internal class Util
246    {
247        private static void _Format(System.Text.StringBuilder sb1,
248                                    byte[] b,
249                                    int offset,
250                                    int length)
251        {
252
253            System.Text.StringBuilder sb2 = new System.Text.StringBuilder();
254            sb1.Append("0000    ");
255            int i;
256            for (i = 0; i < length; i++)
257            {
258                int x = offset+i;
259                if (i != 0 && i % 16 == 0)
260                {
261                    sb1.Append("    ")
262                        .Append(sb2)
263                        .Append("\n")
264                        .Append(String.Format("{0:X4}    ", i));
265                    sb2.Remove(0,sb2.Length);
266                }
267                sb1.Append(System.String.Format("{0:X2} ", b[x]));
268                if (b[x] >=32 && b[x] <= 126)
269                    sb2.Append((char)b[x]);
270                else
271                    sb2.Append(".");
272            }
273            if (sb2.Length > 0)
274            {
275                sb1.Append(new String(' ', ((16 - i%16) * 3) + 4))
276                    .Append(sb2);
277            }
278        }
279
280
281
282        internal static string FormatByteArray(byte[] b, int limit)
283        {
284            System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
285
286            if ((limit * 2 > b.Length) || limit == 0)
287            {
288                _Format(sb1, b, 0, b.Length);
289            }
290            else
291            {
292                // first N bytes of the buffer
293                _Format(sb1, b, 0, limit);
294
295                if (b.Length > limit * 2)
296                    sb1.Append(String.Format("\n   ...({0} other bytes here)....\n", b.Length - limit * 2));
297
298                // last N bytes of the buffer
299                _Format(sb1, b, b.Length - limit, limit);
300            }
301
302            return sb1.ToString();
303        }
304
305
306        internal static string FormatByteArray(byte[] b)
307        {
308            return FormatByteArray(b, 0);
309        }
310    }
311
312#endif
313    #endregion
314
315
316
317
318    /// <summary>
319    ///   A stream that encrypts as it writes, or decrypts as it reads.  The
320    ///   Crypto is AES in CTR (counter) mode, which is compatible with the AES
321    ///   encryption employed by WinZip 12.0.
322    /// </summary>
323    /// <remarks>
324    ///   <para>
325    ///     The AES/CTR encryption protocol used by WinZip works like this:
326    ///
327    ///       - start with a counter, initialized to zero.
328    ///
329    ///       - to encrypt, take the data by 16-byte blocks. For each block:
330    ///         - apply the transform to the counter
331    ///         - increement the counter
332    ///         - XOR the result of the transform with the plaintext to
333    ///           get the ciphertext.
334    ///         - compute the mac on the encrypted bytes
335    ///       - when finished with all blocks, store the computed MAC.
336    ///
337    ///       - to decrypt, take the data by 16-byte blocks. For each block:
338    ///         - compute the mac on the encrypted bytes,
339    ///         - apply the transform to the counter
340    ///         - increement the counter
341    ///         - XOR the result of the transform with the ciphertext to
342    ///           get the plaintext.
343    ///       - when finished with all blocks, compare the computed MAC against
344    ///         the stored MAC
345    ///
346    ///   </para>
347    /// </remarks>
348    //
349    internal class WinZipAesCipherStream : Stream
350    {
351        private WinZipAesCrypto _params;
352        private System.IO.Stream _s;
353        private CryptoMode _mode;
354        private int _nonce;
355        private bool _finalBlock;
356
357        internal HMACSHA1 _mac;
358
359        // Use RijndaelManaged from .NET 2.0.
360        // AesManaged came in .NET 3.5, but we want to limit
361        // dependency to .NET 2.0.  AES is just a restricted form
362        // of Rijndael (fixed block size of 128, some crypto modes not supported).
363
364        internal RijndaelManaged _aesCipher;
365        internal ICryptoTransform _xform;
366
367        private const int BLOCK_SIZE_IN_BYTES = 16;
368
369        private byte[] counter = new byte[BLOCK_SIZE_IN_BYTES];
370        private byte[] counterOut = new byte[BLOCK_SIZE_IN_BYTES];
371
372        // I've had a problem when wrapping a WinZipAesCipherStream inside
373        // a DeflateStream. Calling Read() on the DeflateStream results in
374        // a Read() on the WinZipAesCipherStream, but the buffer is larger
375        // than the total size of the encrypted data, and larger than the
376        // initial Read() on the DeflateStream!  When the encrypted
377        // bytestream is embedded within a larger stream (As in a zip
378        // archive), the Read() doesn't fail with EOF.  This causes bad
379        // data to be returned, and it messes up the MAC.
380
381        // This field is used to provide a hard-stop to the size of
382        // data that can be read from the stream.  In Read(), if the buffer or
383        // read request goes beyond the stop, we truncate it.
384
385        private long _length;
386        private long _totalBytesXferred;
387        private byte[] _PendingWriteBlock;
388        private int _pendingCount;
389        private byte[] _iobuf;
390
391        /// <summary>
392        /// The constructor.
393        /// </summary>
394        /// <param name="s">The underlying stream</param>
395        /// <param name="mode">To either encrypt or decrypt.</param>
396        /// <param name="cryptoParams">The pre-initialized WinZipAesCrypto object.</param>
397        /// <param name="length">The maximum number of bytes to read from the stream.</param>
398        internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, long length, CryptoMode mode)
399            : this(s, cryptoParams, mode)
400        {
401            // don't read beyond this limit!
402            _length = length;
403            //Console.WriteLine("max length of AES stream: {0}", _length);
404        }
405
406
407#if WANT_TRACE
408            Stream untransformed;
409        String traceFileUntransformed;
410        Stream transformed;
411        String traceFileTransformed;
412#endif
413
414
415        internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, CryptoMode mode)
416            : base()
417        {
418            TraceOutput("-------------------------------------------------------");
419            TraceOutput("Create {0:X8}", this.GetHashCode());
420
421            _params = cryptoParams;
422            _s = s;
423            _mode = mode;
424            _nonce = 1;
425
426            if (_params == null)
427                throw new BadPasswordException("Supply a password to use AES encryption.");
428
429            int keySizeInBits = _params.KeyBytes.Length * 8;
430            if (keySizeInBits != 256 && keySizeInBits != 128 && keySizeInBits != 192)
431                throw new ArgumentOutOfRangeException("keysize",
432                                                      "size of key must be 128, 192, or 256");
433
434            _mac = new HMACSHA1(_params.MacIv);
435
436            _aesCipher = new System.Security.Cryptography.RijndaelManaged();
437            _aesCipher.BlockSize = 128;
438            _aesCipher.KeySize = keySizeInBits;  // 128, 192, 256
439            _aesCipher.Mode = CipherMode.ECB;
440            _aesCipher.Padding = PaddingMode.None;
441
442            byte[] iv = new byte[BLOCK_SIZE_IN_BYTES]; // all zeroes
443
444            // Create an ENCRYPTOR, regardless whether doing decryption or encryption.
445            // It is reflexive.
446            _xform = _aesCipher.CreateEncryptor(_params.KeyBytes, iv);
447
448            if (_mode == CryptoMode.Encrypt)
449            {
450                _iobuf = new byte[2048];
451                _PendingWriteBlock = new byte[BLOCK_SIZE_IN_BYTES];
452            }
453
454
455#if WANT_TRACE
456                traceFileUntransformed = "unpack\\WinZipAesCipherStream.trace.untransformed.out";
457            traceFileTransformed = "unpack\\WinZipAesCipherStream.trace.transformed.out";
458
459            untransformed = System.IO.File.Create(traceFileUntransformed);
460            transformed = System.IO.File.Create(traceFileTransformed);
461#endif
462        }
463
464        private void XorInPlace(byte[] buffer, int offset, int count)
465        {
466            for (int i = 0; i < count; i++)
467            {
468                buffer[offset + i] = (byte)(counterOut[i] ^ buffer[offset + i]);
469            }
470        }
471
472        private void WriteTransformOneBlock(byte[] buffer, int offset)
473        {
474            System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
475            _xform.TransformBlock(counter,
476                                  0,
477                                  BLOCK_SIZE_IN_BYTES,
478                                  counterOut,
479                                  0);
480            XorInPlace(buffer, offset, BLOCK_SIZE_IN_BYTES);
481            _mac.TransformBlock(buffer, offset, BLOCK_SIZE_IN_BYTES, null, 0);
482        }
483
484
485        private void WriteTransformBlocks(byte[] buffer, int offset, int count)
486        {
487            int posn = offset;
488            int last = count + offset;
489
490            while (posn < buffer.Length && posn < last)
491            {
492                WriteTransformOneBlock (buffer, posn);
493                posn += BLOCK_SIZE_IN_BYTES;
494            }
495        }
496
497
498        private void WriteTransformFinalBlock()
499        {
500            if (_pendingCount == 0)
501                throw new InvalidOperationException("No bytes available.");
502
503            if (_finalBlock)
504                throw new InvalidOperationException("The final block has already been transformed.");
505
506            System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
507            counterOut = _xform.TransformFinalBlock(counter,
508                                                    0,
509                                                    BLOCK_SIZE_IN_BYTES);
510            XorInPlace(_PendingWriteBlock, 0, _pendingCount);
511            _mac.TransformFinalBlock(_PendingWriteBlock, 0, _pendingCount);
512            _finalBlock = true;
513        }
514
515
516
517
518
519        private int ReadTransformOneBlock(byte[] buffer, int offset, int last)
520        {
521            if (_finalBlock)
522                throw new NotSupportedException();
523
524            int bytesRemaining = last - offset;
525            int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES)
526                ? BLOCK_SIZE_IN_BYTES
527                : bytesRemaining;
528
529            // update the counter
530            System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
531
532            // Determine if this is the final block
533            if ((bytesToRead == bytesRemaining) &&
534                (_length > 0) &&
535                (_totalBytesXferred + last == _length))
536            {
537                _mac.TransformFinalBlock(buffer, offset, bytesToRead);
538                counterOut = _xform.TransformFinalBlock(counter,
539                                                        0,
540                                                        BLOCK_SIZE_IN_BYTES);
541                _finalBlock = true;
542            }
543            else
544            {
545                _mac.TransformBlock(buffer, offset, bytesToRead, null, 0);
546                _xform.TransformBlock(counter,
547                                      0, // offset
548                                      BLOCK_SIZE_IN_BYTES,
549                                      counterOut,
550                                      0);  // offset
551            }
552
553            XorInPlace(buffer, offset, bytesToRead);
554            return bytesToRead;
555        }
556
557
558
559        private void ReadTransformBlocks(byte[] buffer, int offset, int count)
560        {
561            int posn = offset;
562            int last = count + offset;
563
564            while (posn < buffer.Length && posn < last )
565            {
566                int n = ReadTransformOneBlock (buffer, posn, last);
567                posn += n;
568            }
569        }
570
571
572
573        public override int Read(byte[] buffer, int offset, int count)
574        {
575            if (_mode == CryptoMode.Encrypt)
576                throw new NotSupportedException();
577
578            if (buffer == null)
579                throw new ArgumentNullException("buffer");
580
581            if (offset < 0)
582                throw new ArgumentOutOfRangeException("offset",
583                                                      "Must not be less than zero.");
584            if (count < 0)
585                throw new ArgumentOutOfRangeException("count",
586                                                      "Must not be less than zero.");
587
588            if (buffer.Length < offset + count)
589                throw new ArgumentException("The buffer is too small");
590
591            // When I wrap a WinZipAesStream in a DeflateStream, the
592            // DeflateStream asks its captive to read 4k blocks, even if the
593            // encrypted bytestream is smaller than that.  This is a way to
594            // limit the number of bytes read.
595
596            int bytesToRead = count;
597
598            if (_totalBytesXferred >= _length)
599            {
600                return 0; // EOF
601            }
602
603            long bytesRemaining = _length - _totalBytesXferred;
604            if (bytesRemaining < count) bytesToRead = (int)bytesRemaining;
605
606            int n = _s.Read(buffer, offset, bytesToRead);
607
608
609#if WANT_TRACE
610                untransformed.Write(buffer, offset, bytesToRead);
611#endif
612
613            ReadTransformBlocks(buffer, offset, bytesToRead);
614
615#if WANT_TRACE
616                transformed.Write(buffer, offset, bytesToRead);
617#endif
618            _totalBytesXferred += n;
619            return n;
620        }
621
622
623
624        /// <summary>
625        /// Returns the final HMAC-SHA1-80 for the data that was encrypted.
626        /// </summary>
627        public byte[] FinalAuthentication
628        {
629            get
630            {
631                if (!_finalBlock)
632                {
633                    // special-case zero-byte files
634                    if ( _totalBytesXferred != 0)
635                        throw new BadStateException("The final hash has not been computed.");
636
637                    // Must call ComputeHash on an empty byte array when no data
638                    // has run through the MAC.
639
640                    byte[] b = {  };
641                    _mac.ComputeHash(b);
642                    // fall through
643                }
644                byte[] macBytes10 = new byte[10];
645                System.Array.Copy(_mac.Hash, 0, macBytes10, 0, 10);
646                return macBytes10;
647            }
648        }
649
650
651        public override void Write(byte[] buffer, int offset, int count)
652        {
653            if (_finalBlock)
654                throw new InvalidOperationException("The final block has already been transformed.");
655
656            if (_mode == CryptoMode.Decrypt)
657                throw new NotSupportedException();
658
659            if (buffer == null)
660                throw new ArgumentNullException("buffer");
661
662            if (offset < 0)
663                throw new ArgumentOutOfRangeException("offset",
664                                                      "Must not be less than zero.");
665            if (count < 0)
666                throw new ArgumentOutOfRangeException("count",
667                                                      "Must not be less than zero.");
668            if (buffer.Length < offset + count)
669                throw new ArgumentException("The offset and count are too large");
670
671            if (count == 0)
672                return;
673
674            TraceOutput("Write off({0}) count({1})", offset, count);
675
676#if WANT_TRACE
677            untransformed.Write(buffer, offset, count);
678#endif
679
680            // For proper AES encryption, an AES encryptor application calls
681            // TransformBlock repeatedly for all 16-byte blocks except the
682            // last. For the last block, it then calls TransformFinalBlock().
683            //
684            // This class is a stream that encrypts via Write().  But, it's not
685            // possible to recognize which are the "last" bytes from within the call
686            // to Write(). The caller can call Write() several times in succession,
687            // with varying buffers. This class only "knows" that the last bytes
688            // have been written when the app calls Close().
689            //
690            // Therefore, this class buffers writes: After completion every Write(),
691            // a 16-byte "pending" block (_PendingWriteBlock) must hold between 1
692            // and 16 bytes, which will be used in TransformFinalBlock if the app
693            // calls Close() immediately thereafter. Also, every write must
694            // transform any pending bytes, before transforming the data passed in
695            // to the current call.
696            //
697            // In operation, after the first call to Write() and before the call to
698            // Close(), one full or partial block of bytes is always available,
699            // pending.  At time of Close(), this class calls
700            // WriteTransformFinalBlock() to flush the pending bytes.
701            //
702            // This approach works whether the caller writes in odd-sized batches,
703            // for example 5000 bytes, or in batches that are neat multiples of the
704            // blocksize (16).
705            //
706            // Logicaly, what we do is this:
707            //
708            //  1. if there are fewer than 16 bytes (pending + current), then
709            //     just copy them into th pending buffer and return.
710            //
711            //  2. there are more than 16 bytes to write. So, take the leading slice
712            //     of bytes from the current buffer, enough to fill the pending
713            //     buffer. Transform the pending block, and write it out.
714            //
715            //  3. Take the trailing slice of bytes (a full block or a partial block),
716            //     and copy it to the pending block for next time.
717            //
718            //  4. transform and write all the other blocks, the middle slice.
719            //
720
721            // There are 16 or fewer bytes, so just buffer the bytes.
722            if (count + _pendingCount <= BLOCK_SIZE_IN_BYTES)
723            {
724                Buffer.BlockCopy(buffer,
725                                 offset,
726                                 _PendingWriteBlock,
727                                 _pendingCount,
728                                 count);
729                _pendingCount += count;
730
731                // At this point, _PendingWriteBlock contains up to
732                // BLOCK_SIZE_IN_BYTES bytes, and _pendingCount ranges from 0 to
733                // BLOCK_SIZE_IN_BYTES. We don't want to xform+write them yet,
734                // because this may have been the last block.  The last block gets
735                // written at Close().
736                return;
737            }
738
739            // We know there are at least 17 bytes, counting those in the current
740            // buffer, along with the (possibly empty) pending block.
741
742            int bytesRemaining = count;
743            int curOffset = offset;
744
745            // workitem 12815
746            //
747            // xform chunkwise ... Cannot transform in place using the original
748            // buffer because that is user-maintained.
749
750            if (_pendingCount != 0)
751            {
752                // We have more than one block of data to write, therefore it is safe
753                // to xform+write.
754                int fillCount = BLOCK_SIZE_IN_BYTES - _pendingCount;
755
756                // fillCount is possibly zero here. That happens when the pending
757                // buffer held 16 bytes (one complete block) before this call to
758                // Write.
759                if (fillCount > 0)
760                {
761                    Buffer.BlockCopy(buffer,
762                                     offset,
763                                     _PendingWriteBlock,
764                                     _pendingCount,
765                                     fillCount);
766
767                    // adjust counts:
768                    bytesRemaining -= fillCount;
769                    curOffset += fillCount;
770                }
771
772                // xform and write:
773                WriteTransformOneBlock(_PendingWriteBlock, 0);
774                _s.Write(_PendingWriteBlock, 0, BLOCK_SIZE_IN_BYTES);
775                _totalBytesXferred += BLOCK_SIZE_IN_BYTES;
776                _pendingCount = 0;
777            }
778
779            // At this point _PendingWriteBlock is empty, and bytesRemaining is
780            // always greater than 0.
781
782            // Now, xform N blocks, where N = floor((bytesRemaining-1)/16).  If
783            // writing 32 bytes, then xform 1 block, and stage the remaining 16.  If
784            // writing 10037 bytes, xform 627 blocks of 16 bytes, then stage the
785            // remaining 5 bytes.
786
787            int blocksToXform = (bytesRemaining-1)/BLOCK_SIZE_IN_BYTES;
788            _pendingCount = bytesRemaining - (blocksToXform * BLOCK_SIZE_IN_BYTES);
789
790            // _pendingCount is ALWAYS between 1 and 16.
791            // Put the last _pendingCount bytes into the pending block.
792            Buffer.BlockCopy(buffer,
793                             curOffset + bytesRemaining - _pendingCount,
794                             _PendingWriteBlock,
795                             0,
796                             _pendingCount);
797            bytesRemaining -= _pendingCount;
798            _totalBytesXferred += bytesRemaining; // will be true after the loop
799
800            // now, transform all the full blocks preceding that.
801            // bytesRemaining is always a multiple of 16 .
802            if (blocksToXform > 0)
803            {
804                do
805                {
806                    int c = _iobuf.Length;
807                    if (c > bytesRemaining) c = bytesRemaining;
808                    Buffer.BlockCopy(buffer,
809                                     curOffset,
810                                     _iobuf,
811                                     0,
812                                     c);
813
814                    WriteTransformBlocks(_iobuf, 0, c);
815                    _s.Write(_iobuf, 0, c);
816                    bytesRemaining -= c;
817                    curOffset += c;
818                } while(bytesRemaining > 0);
819            }
820        }
821
822
823
824        /// <summary>
825        ///   Close the stream.
826        /// </summary>
827        public override void Close()
828        {
829            TraceOutput("Close {0:X8}", this.GetHashCode());
830
831            // In the degenerate case, no bytes have been written to the
832            // stream at all.  Need to check here, and NOT emit the
833            // final block if Write has not been called.
834            if (_pendingCount > 0)
835            {
836                WriteTransformFinalBlock();
837                _s.Write(_PendingWriteBlock, 0, _pendingCount);
838                _totalBytesXferred += _pendingCount;
839                _pendingCount = 0;
840            }
841            _s.Close();
842
843#if WANT_TRACE
844            untransformed.Close();
845            transformed.Close();
846            Console.WriteLine("\nuntransformed bytestream is in  {0}", traceFileUntransformed);
847            Console.WriteLine("\ntransformed bytestream is in  {0}", traceFileTransformed);
848#endif
849            TraceOutput("-------------------------------------------------------");
850        }
851
852
853        /// <summary>
854        /// Returns true if the stream can be read.
855        /// </summary>
856        public override bool CanRead
857        {
858            get
859            {
860                if (_mode != CryptoMode.Decrypt) return false;
861                return true;
862            }
863        }
864
865
866        /// <summary>
867        /// Always returns false.
868        /// </summary>
869        public override bool CanSeek
870        {
871            get { return false; }
872        }
873
874        /// <summary>
875        /// Returns true if the CryptoMode is Encrypt.
876        /// </summary>
877        public override bool CanWrite
878        {
879            get { return (_mode == CryptoMode.Encrypt); }
880        }
881
882        /// <summary>
883        /// Flush the content in the stream.
884        /// </summary>
885        public override void Flush()
886        {
887            _s.Flush();
888        }
889
890        /// <summary>
891        /// Getting this property throws a NotImplementedException.
892        /// </summary>
893        public override long Length
894        {
895            get { throw new NotImplementedException(); }
896        }
897
898        /// <summary>
899        /// Getting or Setting this property throws a NotImplementedException.
900        /// </summary>
901        public override long Position
902        {
903            get { throw new NotImplementedException(); }
904            set { throw new NotImplementedException(); }
905        }
906
907        /// <summary>
908        /// This method throws a NotImplementedException.
909        /// </summary>
910        public override long Seek(long offset, System.IO.SeekOrigin origin)
911        {
912            throw new NotImplementedException();
913        }
914
915        /// <summary>
916        /// This method throws a NotImplementedException.
917        /// </summary>
918        public override void SetLength(long value)
919        {
920            throw new NotImplementedException();
921        }
922
923
924
925        [System.Diagnostics.ConditionalAttribute("Trace")]
926        private void TraceOutput(string format, params object[] varParams)
927        {
928            lock(_outputLock)
929            {
930                int tid = System.Threading.Thread.CurrentThread.GetHashCode();
931                Console.ForegroundColor = (ConsoleColor) (tid % 8 + 8);
932                Console.Write("{0:000} WZACS ", tid);
933                Console.WriteLine(format, varParams);
934                Console.ResetColor();
935            }
936        }
937
938        private object _outputLock = new Object();
939    }
940}
941#endif
Note: See TracBrowser for help on using the repository browser.