| | 1 | | using System; |
| | 2 | | using System.IO; |
| | 3 | | using System.Security.Cryptography; |
| | 4 | |
|
| | 5 | | namespace ICSharpCode.SharpZipLib.Encryption |
| | 6 | | { |
| | 7 | | /// <summary> |
| | 8 | | /// Encrypts and decrypts AES ZIP |
| | 9 | | /// </summary> |
| | 10 | | /// <remarks> |
| | 11 | | /// Based on information from http://www.winzip.com/aes_info.htm |
| | 12 | | /// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/ |
| | 13 | | /// </remarks> |
| | 14 | | internal class ZipAESStream : CryptoStream |
| | 15 | | { |
| | 16 | |
|
| | 17 | | /// <summary> |
| | 18 | | /// Constructor |
| | 19 | | /// </summary> |
| | 20 | | /// <param name="stream">The stream on which to perform the cryptographic transformation.</param> |
| | 21 | | /// <param name="transform">Instance of ZipAESTransform</param> |
| | 22 | | /// <param name="mode">Read or Write</param> |
| | 23 | | public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode) |
| 0 | 24 | | : base(stream, transform, mode) |
| | 25 | | { |
| | 26 | |
|
| 0 | 27 | | _stream = stream; |
| 0 | 28 | | _transform = transform; |
| 0 | 29 | | _slideBuffer = new byte[1024]; |
| | 30 | |
|
| 0 | 31 | | _blockAndAuth = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH; |
| | 32 | |
|
| | 33 | | // mode: |
| | 34 | | // CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method. |
| | 35 | | // Write bypasses this stream and uses the Transform directly. |
| 0 | 36 | | if (mode != CryptoStreamMode.Read) { |
| 0 | 37 | | throw new Exception("ZipAESStream only for read"); |
| | 38 | | } |
| 0 | 39 | | } |
| | 40 | |
|
| | 41 | | // The final n bytes of the AES stream contain the Auth Code. |
| | 42 | | private const int AUTH_CODE_LENGTH = 10; |
| | 43 | |
|
| | 44 | | private Stream _stream; |
| | 45 | | private ZipAESTransform _transform; |
| | 46 | | private byte[] _slideBuffer; |
| | 47 | | private int _slideBufStartPos; |
| | 48 | | private int _slideBufFreePos; |
| | 49 | | // Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32. |
| | 50 | | private const int CRYPTO_BLOCK_SIZE = 16; |
| | 51 | | private int _blockAndAuth; |
| | 52 | |
|
| | 53 | | /// <summary> |
| | 54 | | /// Reads a sequence of bytes from the current CryptoStream into buffer, |
| | 55 | | /// and advances the position within the stream by the number of bytes read. |
| | 56 | | /// </summary> |
| | 57 | | public override int Read(byte[] buffer, int offset, int count) |
| | 58 | | { |
| 0 | 59 | | int nBytes = 0; |
| 0 | 60 | | while (nBytes < count) { |
| | 61 | | // Calculate buffer quantities vs read-ahead size, and check for sufficient free space |
| 0 | 62 | | int byteCount = _slideBufFreePos - _slideBufStartPos; |
| | 63 | |
|
| | 64 | | // Need to handle final block and Auth Code specially, but don't know total data length. |
| | 65 | | // Maintain a read-ahead equal to the length of (crypto block + Auth Code). |
| | 66 | | // When that runs out we can detect these final sections. |
| 0 | 67 | | int lengthToRead = _blockAndAuth - byteCount; |
| 0 | 68 | | if (_slideBuffer.Length - _slideBufFreePos < lengthToRead) { |
| | 69 | | // Shift the data to the beginning of the buffer |
| 0 | 70 | | int iTo = 0; |
| 0 | 71 | | for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++) { |
| 0 | 72 | | _slideBuffer[iTo] = _slideBuffer[iFrom]; |
| | 73 | | } |
| 0 | 74 | | _slideBufFreePos -= _slideBufStartPos; // Note the -= |
| 0 | 75 | | _slideBufStartPos = 0; |
| | 76 | | } |
| 0 | 77 | | int obtained = _stream.Read(_slideBuffer, _slideBufFreePos, lengthToRead); |
| 0 | 78 | | _slideBufFreePos += obtained; |
| | 79 | |
|
| | 80 | | // Recalculate how much data we now have |
| 0 | 81 | | byteCount = _slideBufFreePos - _slideBufStartPos; |
| 0 | 82 | | if (byteCount >= _blockAndAuth) { |
| | 83 | | // At least a 16 byte block and an auth code remains. |
| 0 | 84 | | _transform.TransformBlock(_slideBuffer, |
| 0 | 85 | | _slideBufStartPos, |
| 0 | 86 | | CRYPTO_BLOCK_SIZE, |
| 0 | 87 | | buffer, |
| 0 | 88 | | offset); |
| 0 | 89 | | nBytes += CRYPTO_BLOCK_SIZE; |
| 0 | 90 | | offset += CRYPTO_BLOCK_SIZE; |
| 0 | 91 | | _slideBufStartPos += CRYPTO_BLOCK_SIZE; |
| 0 | 92 | | } else { |
| | 93 | | // Last round. |
| 0 | 94 | | if (byteCount > AUTH_CODE_LENGTH) { |
| | 95 | | // At least one byte of data plus auth code |
| 0 | 96 | | int finalBlock = byteCount - AUTH_CODE_LENGTH; |
| 0 | 97 | | _transform.TransformBlock(_slideBuffer, |
| 0 | 98 | | _slideBufStartPos, |
| 0 | 99 | | finalBlock, |
| 0 | 100 | | buffer, |
| 0 | 101 | | offset); |
| | 102 | |
|
| 0 | 103 | | nBytes += finalBlock; |
| 0 | 104 | | _slideBufStartPos += finalBlock; |
| 0 | 105 | | } else if (byteCount < AUTH_CODE_LENGTH) |
| 0 | 106 | | throw new Exception("Internal error missed auth code"); // Coding bug |
| | 107 | | // Final block done. Check Auth code. |
| 0 | 108 | | byte[] calcAuthCode = _transform.GetAuthCode(); |
| 0 | 109 | | for (int i = 0; i < AUTH_CODE_LENGTH; i++) { |
| 0 | 110 | | if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i]) { |
| 0 | 111 | | throw new Exception("AES Authentication Code does not match. This is a super-CRC check on the data in the |
| 0 | 112 | | + "The file may be damaged."); |
| | 113 | | } |
| | 114 | | } |
| | 115 | |
|
| 0 | 116 | | break; // Reached the auth code |
| | 117 | | } |
| | 118 | | } |
| 0 | 119 | | return nBytes; |
| | 120 | | } |
| | 121 | |
|
| | 122 | | /// <summary> |
| | 123 | | /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the num |
| | 124 | | /// </summary> |
| | 125 | | /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream. </para |
| | 126 | | /// <param name="offset">The byte offset in buffer at which to begin copying bytes to the current stream. </param> |
| | 127 | | /// <param name="count">The number of bytes to be written to the current stream. </param> |
| | 128 | | public override void Write(byte[] buffer, int offset, int count) |
| | 129 | | { |
| | 130 | | // ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly. |
| 0 | 131 | | throw new NotImplementedException(); |
| | 132 | | } |
| | 133 | | } |
| | 134 | | } |