using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;
namespace ICSharpCode.SharpZipLib.Encryption
{
///
/// Encrypts and decrypts AES ZIP
///
///
/// Based on information from http://www.winzip.com/aes_info.htm
/// and http://www.gladman.me.uk/cryptography_technology/fileencrypt/
///
internal class ZipAESStream : CryptoStream
{
///
/// Constructor
///
/// The stream on which to perform the cryptographic transformation.
/// Instance of ZipAESTransform
/// Read or Write
public ZipAESStream(Stream stream, ZipAESTransform transform, CryptoStreamMode mode)
: base(stream, transform, mode)
{
_stream = stream;
_transform = transform;
_slideBuffer = new byte[1024];
// mode:
// CryptoStreamMode.Read means we read from "stream" and pass decrypted to our Read() method.
// Write bypasses this stream and uses the Transform directly.
if (mode != CryptoStreamMode.Read)
{
throw new Exception("ZipAESStream only for read");
}
}
// The final n bytes of the AES stream contain the Auth Code.
private const int AUTH_CODE_LENGTH = 10;
// Blocksize is always 16 here, even for AES-256 which has transform.InputBlockSize of 32.
private const int CRYPTO_BLOCK_SIZE = 16;
// total length of block + auth code
private const int BLOCK_AND_AUTH = CRYPTO_BLOCK_SIZE + AUTH_CODE_LENGTH;
private Stream _stream;
private ZipAESTransform _transform;
private byte[] _slideBuffer;
private int _slideBufStartPos;
private int _slideBufFreePos;
// Buffer block transforms to enable partial reads
private byte[] _transformBuffer = null;// new byte[CRYPTO_BLOCK_SIZE];
private int _transformBufferFreePos;
private int _transformBufferStartPos;
// Do we have some buffered data available?
private bool HasBufferedData =>_transformBuffer != null && _transformBufferStartPos < _transformBufferFreePos;
///
/// Reads a sequence of bytes from the current CryptoStream into buffer,
/// and advances the position within the stream by the number of bytes read.
///
public override int Read(byte[] buffer, int offset, int count)
{
// Nothing to do
if (count == 0)
return 0;
// If we have buffered data, read that first
int nBytes = 0;
if (HasBufferedData)
{
nBytes = ReadBufferedData(buffer, offset, count);
// Read all requested data from the buffer
if (nBytes == count)
return nBytes;
offset += nBytes;
count -= nBytes;
}
// Read more data from the input, if available
if (_slideBuffer != null)
nBytes += ReadAndTransform(buffer, offset, count);
return nBytes;
}
///
public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var readCount = Read(buffer, offset, count);
return Task.FromResult(readCount);
}
// Read data from the underlying stream and decrypt it
private int ReadAndTransform(byte[] buffer, int offset, int count)
{
int nBytes = 0;
while (nBytes < count)
{
int bytesLeftToRead = count - nBytes;
// Calculate buffer quantities vs read-ahead size, and check for sufficient free space
int byteCount = _slideBufFreePos - _slideBufStartPos;
// Need to handle final block and Auth Code specially, but don't know total data length.
// Maintain a read-ahead equal to the length of (crypto block + Auth Code).
// When that runs out we can detect these final sections.
int lengthToRead = BLOCK_AND_AUTH - byteCount;
if (_slideBuffer.Length - _slideBufFreePos < lengthToRead)
{
// Shift the data to the beginning of the buffer
int iTo = 0;
for (int iFrom = _slideBufStartPos; iFrom < _slideBufFreePos; iFrom++, iTo++)
{
_slideBuffer[iTo] = _slideBuffer[iFrom];
}
_slideBufFreePos -= _slideBufStartPos; // Note the -=
_slideBufStartPos = 0;
}
int obtained = StreamUtils.ReadRequestedBytes(_stream, _slideBuffer, _slideBufFreePos, lengthToRead);
_slideBufFreePos += obtained;
// Recalculate how much data we now have
byteCount = _slideBufFreePos - _slideBufStartPos;
if (byteCount >= BLOCK_AND_AUTH)
{
var read = TransformAndBufferBlock(buffer, offset, bytesLeftToRead, CRYPTO_BLOCK_SIZE);
nBytes += read;
offset += read;
}
else
{
// Last round.
if (byteCount > AUTH_CODE_LENGTH)
{
// At least one byte of data plus auth code
int finalBlock = byteCount - AUTH_CODE_LENGTH;
nBytes += TransformAndBufferBlock(buffer, offset, bytesLeftToRead, finalBlock);
}
else if (byteCount < AUTH_CODE_LENGTH)
throw new ZipException("Internal error missed auth code"); // Coding bug
// Final block done. Check Auth code.
byte[] calcAuthCode = _transform.GetAuthCode();
for (int i = 0; i < AUTH_CODE_LENGTH; i++)
{
if (calcAuthCode[i] != _slideBuffer[_slideBufStartPos + i])
{
throw new ZipException("AES Authentication Code does not match. This is a super-CRC check on the data in the file after compression and encryption. \r\n"
+ "The file may be damaged.");
}
}
// don't need this any more, so use it as a 'complete' flag
_slideBuffer = null;
break; // Reached the auth code
}
}
return nBytes;
}
// read some buffered data
private int ReadBufferedData(byte[] buffer, int offset, int count)
{
int copyCount = Math.Min(count, _transformBufferFreePos - _transformBufferStartPos);
Array.Copy(_transformBuffer, _transformBufferStartPos, buffer, offset, copyCount);
_transformBufferStartPos += copyCount;
return copyCount;
}
// Perform the crypto transform, and buffer the data if less than one block has been requested.
private int TransformAndBufferBlock(byte[] buffer, int offset, int count, int blockSize)
{
// If the requested data is greater than one block, transform it directly into the output
// If it's smaller, do it into a temporary buffer and copy the requested part
bool bufferRequired = (blockSize > count);
if (bufferRequired && _transformBuffer == null)
_transformBuffer = new byte[CRYPTO_BLOCK_SIZE];
var targetBuffer = bufferRequired ? _transformBuffer : buffer;
var targetOffset = bufferRequired ? 0 : offset;
// Transform the data
_transform.TransformBlock(_slideBuffer,
_slideBufStartPos,
blockSize,
targetBuffer,
targetOffset);
_slideBufStartPos += blockSize;
if (!bufferRequired)
{
return blockSize;
}
else
{
Array.Copy(_transformBuffer, 0, buffer, offset, count);
_transformBufferStartPos = count;
_transformBufferFreePos = blockSize;
return count;
}
}
///
/// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///
/// An array of bytes. This method copies count bytes from buffer to the current stream.
/// The byte offset in buffer at which to begin copying bytes to the current stream.
/// The number of bytes to be written to the current stream.
public override void Write(byte[] buffer, int offset, int count)
{
// ZipAESStream is used for reading but not for writing. Writing uses the ZipAESTransform directly.
throw new NotImplementedException();
}
}
}