WinZipAes.cs 32 KB

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