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 |
|
---|
31 | using System;
|
---|
32 | using System.IO;
|
---|
33 | using System.Collections.Generic;
|
---|
34 | using System.Security.Cryptography;
|
---|
35 |
|
---|
36 | #if AESCRYPTO
|
---|
37 | namespace 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
|
---|