< Summary

Information
Class: System.IO.Compression.WinZipAesStream
Assembly: System.IO.Compression
File(s): D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\WinZipAesStream.cs
Line coverage
83%
Covered lines: 336
Uncovered lines: 68
Coverable lines: 404
Total lines: 674
Line coverage: 83.1%
Branch coverage
66%
Covered branches: 87
Total branches: 130
Branch coverage: 66.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)66.66%6687.5%
GetSaltSize(...)50%2266.66%
CreateKey(...)50%2266.66%
Create(...)75%4481.81%
CreateAsync(...)50%4481.81%
ReadAndValidateHeaderCore(...)80%101085.71%
FinalizeAndCompareHMAC(...)50%6663.63%
ValidateAuthCode()100%22100%
ValidateAuthCodeAsync(...)100%22100%
WriteHeaderAsync(...)100%11100%
WriteHeader()100%11100%
ProcessBlock(...)83.33%66100%
GenerateKeystreamBuffer()100%22100%
XorBytes(...)100%22100%
WriteAuthCodeCoreAsync(...)62.5%8878.94%
ThrowIfNotReadable()50%2266.66%
GetBytesToRead(...)100%22100%
Read(...)100%11100%
Read(...)60%101088.46%
ReadAsync(...)100%110%
ReadAsync(...)60%101088.46%
GetWriteWorkBuffer()50%22100%
WriteCore(...)100%22100%
ThrowIfNotWritable()50%2266.66%
Write(...)100%11100%
Write(...)50%22100%
WriteAsync(...)100%110%
WriteAsyncCore(...)75%44100%
WriteAsync(...)100%11100%
Dispose(...)75%121291.66%
DisposeAsync()64.28%141490%
FinishEncryptingAsync(...)50%8857.14%
Flush()100%110%
FlushAsync(...)100%110%
Seek(...)100%110%
SetLength(...)100%110%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\WinZipAesStream.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Diagnostics;
 6using System.Runtime.Versioning;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11
 12namespace System.IO.Compression
 13{
 14    internal sealed class WinZipAesStream : Stream
 15    {
 16        private const int BlockSize = 16; // AES block size in bytes
 17        private const int KeystreamBufferSize = 4096; // Pre-generate 4KB of keystream (256 blocks)
 18
 19        private readonly Stream _baseStream;
 20        private readonly bool _encrypting;
 21        private readonly Aes _aes;
 22        private IncrementalHash? _hmac;
 9623        private UInt128 _counter = 1;
 24        private readonly byte[] _salt;
 25        private readonly byte[] _passwordVerifier;
 26        private bool _headerWritten;
 27        private bool _disposed;
 28        // During decryption: set to true after the stored auth code is read and verified.
 29        // During encryption: set to true after the computed auth code has been written.
 30        private bool _authCodeFinalized;
 31        private readonly long _totalStreamSize;
 32        private readonly bool _leaveOpen;
 33        private readonly long _encryptedDataSize;
 34        private long _encryptedDataRemaining;
 35        // Pre-generated keystream buffer for efficiency
 9636        private readonly byte[] _keystreamBuffer = new byte[KeystreamBufferSize];
 9637        private int _keystreamOffset = KeystreamBufferSize; // Start depleted to force initial generation
 38
 39        // Reusable work buffer for write operations, lazily allocated on first write
 40        private byte[]? _writeWorkBuffer;
 41
 42        internal static int GetSaltSize(int keySizeBits)
 4843        {
 4844            if (OperatingSystem.IsBrowser())
 045            {
 046                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 47            }
 48
 4849            return WinZipAesKeyMaterial.GetSaltSize(keySizeBits);
 4850        }
 51
 52        /// <summary>
 53        /// Derives key material from a password and optional salt.
 54        /// </summary>
 55        internal static WinZipAesKeyMaterial CreateKey(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits)
 14456        {
 14457            if (OperatingSystem.IsBrowser())
 058            {
 059                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 60            }
 14461            return WinZipAesKeyMaterial.Create(password, salt, keySizeBits);
 14462        }
 63
 64        /// <summary>
 65        /// Creates a WinZipAesStream synchronously. Reads and validates the header for decryption.
 66        /// </summary>
 67        internal static WinZipAesStream Create(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize
 9668        {
 9669            if (OperatingSystem.IsBrowser())
 070            {
 071                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 72            }
 73
 9674            ArgumentNullException.ThrowIfNull(baseStream);
 75
 9676            if (!encrypting)
 4877            {
 4878                ReadAndValidateHeaderCore(isAsync: false, baseStream, keyMaterial, CancellationToken.None).GetAwaiter().
 2479            }
 80
 7281            return new WinZipAesStream(baseStream, keyMaterial, totalStreamSize, encrypting, leaveOpen);
 7282        }
 83
 84        /// <summary>
 85        /// Creates a WinZipAesStream asynchronously. Reads and validates the header for decryption.
 86        /// </summary>
 87        internal static async Task<WinZipAesStream> CreateAsync(Stream baseStream, WinZipAesKeyMaterial keyMaterial, lon
 4888        {
 4889            if (OperatingSystem.IsBrowser())
 090            {
 091                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 92            }
 93
 4894            ArgumentNullException.ThrowIfNull(baseStream);
 95
 4896            if (!encrypting)
 4897            {
 4898                await ReadAndValidateHeaderCore(isAsync: true, baseStream, keyMaterial, cancellationToken).ConfigureAwai
 2499            }
 100
 24101            return new WinZipAesStream(baseStream, keyMaterial, totalStreamSize, encrypting, leaveOpen);
 24102        }
 103
 104        /// <summary>
 105        /// Reads and validates the WinZip AES header (salt + password verifier) from the stream.
 106        /// </summary>
 107        private static async Task ReadAndValidateHeaderCore(bool isAsync, Stream baseStream, WinZipAesKeyMaterial keyMat
 96108        {
 96109            if (OperatingSystem.IsBrowser())
 0110            {
 0111                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 112            }
 96113            int saltSize = keyMaterial.SaltSize;
 114
 115            // Read salt from stream
 96116            byte[] fileSalt = new byte[saltSize];
 96117            if (isAsync)
 48118            {
 48119                await baseStream.ReadExactlyAsync(fileSalt, cancellationToken).ConfigureAwait(false);
 48120            }
 121            else
 48122            {
 48123                baseStream.ReadExactly(fileSalt);
 48124            }
 125
 126            // Read the 2-byte password verifier from stream
 96127            byte[] verifier = new byte[2];
 96128            if (isAsync)
 48129            {
 48130                await baseStream.ReadExactlyAsync(verifier, cancellationToken).ConfigureAwait(false);
 48131            }
 132            else
 48133            {
 48134                baseStream.ReadExactly(verifier);
 48135            }
 136
 137            // Verify the salt matches. In WinZip AES, the salt is stored in the archive
 138            // header and is not secret; FixedTimeEquals is used here for consistency.
 96139            if (!CryptographicOperations.FixedTimeEquals(fileSalt, keyMaterial.Salt))
 0140            {
 0141                throw new InvalidDataException(SR.LocalFileHeaderCorrupt);
 142            }
 143
 144            // Compare the 2-byte password verifier. This is a weak check (only 2 bytes) used to
 145            // fail fast on an obviously wrong password; it is not a security guarantee.
 96146            if (!CryptographicOperations.FixedTimeEquals(verifier, keyMaterial.PasswordVerifier))
 48147            {
 48148                throw new InvalidDataException(SR.InvalidPassword);
 149            }
 48150        }
 151
 152        /// <summary>
 153        /// Private constructor â€” used by Create/CreateAsync.
 154        /// For decryption, the header must already be validated before calling this constructor.
 155        /// </summary>
 96156        private WinZipAesStream(Stream baseStream, WinZipAesKeyMaterial keyMaterial, long totalStreamSize, bool encrypti
 96157        {
 96158            if (OperatingSystem.IsBrowser())
 0159            {
 0160                throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnPlatform);
 161            }
 162
 96163            _baseStream = baseStream;
 164
 96165            Debug.Assert((totalStreamSize >= 0) == !encrypting, "Total stream size must be known when decrypting");
 166
 96167            _encrypting = encrypting;
 96168            _totalStreamSize = totalStreamSize;
 96169            _leaveOpen = leaveOpen;
 170
 96171            _aes = Aes.Create();
 172
 96173            _salt = keyMaterial.Salt;
 96174            _passwordVerifier = keyMaterial.PasswordVerifier;
 175
 96176            if (encrypting)
 48177            {
 48178                _encryptedDataSize = -1;
 48179                _encryptedDataRemaining = -1;
 48180            }
 181            else
 48182            {
 48183                int headerSize = checked(keyMaterial.SaltSize + 2); // Salt + Password Verifier
 184                const int hmacSize = 10; // 10-byte HMAC
 185
 48186                _encryptedDataSize = _totalStreamSize - headerSize - hmacSize;
 48187                _encryptedDataRemaining = _encryptedDataSize;
 188
 48189                if (_encryptedDataSize < 0)
 0190                {
 0191                    throw new InvalidDataException(SR.InvalidWinZipSize);
 192                }
 48193            }
 194
 96195            _hmac = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, keyMaterial.HmacKey);
 96196            _aes.SetKey(keyMaterial.EncryptionKey);
 96197        }
 198
 199        // Compute and check the HMAC for the entire stream. This is called at the end of the stream, after all data has
 200        // similarly to how CRC is computed for non-encrypted ZIP entries. The HMAC is stored in the last 10 bytes of th
 201        private unsafe void FinalizeAndCompareHMAC(byte[] storedAuth)
 48202        {
 203
 48204            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
 205
 206            // Finalize HMAC computation after reading, so we can use stackalloc
 48207            Span<byte> expectedAuth = stackalloc byte[SHA1.HashSizeInBytes];
 48208            if (!_hmac.TryGetHashAndReset(expectedAuth, out int bytesWritten) || bytesWritten < 10)
 0209            {
 0210                throw new InvalidDataException(SR.WinZipAuthCodeMismatch);
 211            }
 212
 213            // Compare the 10 bytes of the expected hash
 48214            Debug.Assert(storedAuth.Length == 10);
 48215            if (!CryptographicOperations.FixedTimeEquals(storedAuth, expectedAuth.Slice(0, storedAuth.Length)))
 0216            {
 0217                throw new InvalidDataException(SR.WinZipAuthCodeMismatch);
 218            }
 48219        }
 220
 221        private void ValidateAuthCode()
 48222        {
 48223            Debug.Assert(!_encrypting, "ValidateAuthCode should only be called during decryption.");
 224
 48225            if (_authCodeFinalized)
 24226            {
 24227                return;
 228            }
 229
 230            // Read the 10-byte stored authentication code from the stream
 24231            byte[] storedAuth = new byte[10];
 24232            _baseStream.ReadExactly(storedAuth);
 24233            FinalizeAndCompareHMAC(storedAuth);
 24234            _authCodeFinalized = true;
 48235        }
 236
 237        private async Task ValidateAuthCodeAsync(CancellationToken cancellationToken)
 48238        {
 48239            Debug.Assert(!_encrypting, "ValidateAuthCode should only be called during decryption.");
 240
 48241            if (_authCodeFinalized)
 24242            {
 24243                return;
 244            }
 245
 246            // Read the 10-byte stored authentication code from the stream
 24247            byte[] storedAuth = new byte[10];
 24248            await _baseStream.ReadExactlyAsync(storedAuth, cancellationToken).ConfigureAwait(false);
 24249            FinalizeAndCompareHMAC(storedAuth);
 24250            _authCodeFinalized = true;
 48251        }
 252
 253        private async Task WriteHeaderAsync(CancellationToken cancellationToken)
 24254        {
 24255            Debug.Assert(!_headerWritten);
 256
 24257            await _baseStream.WriteAsync(_salt, cancellationToken).ConfigureAwait(false);
 24258            await _baseStream.WriteAsync(_passwordVerifier, cancellationToken).ConfigureAwait(false);
 259
 24260            _headerWritten = true;
 24261        }
 262
 263        private void WriteHeader()
 24264        {
 24265            Debug.Assert(!_headerWritten);
 266
 24267            _baseStream.Write(_salt);
 24268            _baseStream.Write(_passwordVerifier);
 24269            _headerWritten = true;
 24270        }
 271
 272        private void ProcessBlock(Span<byte> buffer)
 96273        {
 96274            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
 275
 192276            while (!buffer.IsEmpty)
 96277            {
 278                // Ensure we have enough keystream bytes available
 96279                int keystreamAvailable = KeystreamBufferSize - _keystreamOffset;
 96280                if (keystreamAvailable == 0)
 96281                {
 96282                    GenerateKeystreamBuffer();
 96283                    keystreamAvailable = KeystreamBufferSize;
 96284                }
 285
 286                // Process as many bytes as possible with the available keystream
 96287                int bytesToProcess = Math.Min(buffer.Length, keystreamAvailable);
 288
 96289                Span<byte> dataSpan = buffer.Slice(0, bytesToProcess);
 96290                ReadOnlySpan<byte> keystreamSpan = _keystreamBuffer.AsSpan(_keystreamOffset, bytesToProcess);
 291
 96292                if (_encrypting)
 48293                {
 294                    // For encryption: XOR first, then HMAC the ciphertext
 48295                    XorBytes(dataSpan, keystreamSpan);
 48296                    _hmac.AppendData(dataSpan);
 48297                }
 298                else
 48299                {
 300                    // For decryption: HMAC first (on ciphertext), then XOR
 48301                    _hmac.AppendData(dataSpan);
 48302                    XorBytes(dataSpan, keystreamSpan);
 48303                }
 304
 96305                _keystreamOffset += bytesToProcess;
 96306                buffer = buffer.Slice(bytesToProcess);
 96307            }
 96308        }
 309
 310        private void GenerateKeystreamBuffer()
 96311        {
 312            // Fill the buffer with all counter values first
 49344313            for (int i = 0; i < KeystreamBufferSize; i += BlockSize)
 24576314            {
 24576315                BinaryPrimitives.WriteUInt128LittleEndian(_keystreamBuffer.AsSpan(i, BlockSize), _counter);
 24576316                _counter++;
 24576317            }
 318
 319            // Encrypt all 256 counter blocks in a single call
 96320            _aes.EncryptEcb(_keystreamBuffer, _keystreamBuffer, PaddingMode.None);
 321
 96322            _keystreamOffset = 0;
 96323        }
 324
 325        private static void XorBytes(Span<byte> dest, ReadOnlySpan<byte> src)
 96326        {
 96327            Debug.Assert(dest.Length <= src.Length);
 328
 1504329            for (int i = 0; i < dest.Length; i++)
 656330            {
 656331                dest[i] ^= src[i];
 656332            }
 96333        }
 334
 335        private async Task WriteAuthCodeCoreAsync(bool isAsync, CancellationToken cancellationToken)
 48336        {
 48337            Debug.Assert(_encrypting, "WriteAuthCode should only be called during encryption.");
 48338            Debug.Assert(_hmac is not null, "HMAC should have been initialized");
 339
 48340            if (_authCodeFinalized)
 0341            {
 0342                return;
 343            }
 344
 345            // WinZip AES spec requires only the first 10 bytes of the HMAC
 346            const int MacSizeInBytes = 10;
 347
 48348            byte[] authCode = new byte[SHA1.HashSizeInBytes];
 349
 48350            if (!_hmac.TryGetHashAndReset(authCode, out int bytesWritten) || bytesWritten < MacSizeInBytes)
 0351            {
 0352                throw new CryptographicException();
 353            }
 48354            if (isAsync)
 24355            {
 356                // WriteAsync requires Memory<byte>, so we must copy to a heap buffer for the async path
 24357                await _baseStream.WriteAsync(authCode.AsMemory(0, MacSizeInBytes), cancellationToken).ConfigureAwait(fal
 24358            }
 359            else
 24360            {
 24361                _baseStream.Write(authCode.AsSpan(0, MacSizeInBytes));
 24362            }
 363
 48364            _authCodeFinalized = true;
 48365        }
 366
 367        private void ThrowIfNotReadable()
 96368        {
 96369            ObjectDisposedException.ThrowIf(_disposed, this);
 370
 96371            if (_encrypting)
 0372            {
 0373                throw new NotSupportedException(SR.ReadingNotSupported);
 374            }
 96375        }
 376
 377        private int GetBytesToRead(int requestedCount)
 96378        {
 96379            if (_encryptedDataRemaining <= 0)
 48380            {
 48381                return 0;
 382            }
 383
 48384            return (int)Math.Min(requestedCount, _encryptedDataRemaining);
 96385        }
 386
 387        public override int Read(byte[] buffer, int offset, int count)
 48388        {
 48389            ValidateBufferArguments(buffer, offset, count);
 48390            return Read(buffer.AsSpan(offset, count));
 48391        }
 392
 393        public override int Read(Span<byte> buffer)
 48394        {
 48395            ThrowIfNotReadable();
 396
 48397            int bytesToRead = GetBytesToRead(buffer.Length);
 48398            if (bytesToRead == 0)
 24399            {
 400                // Only validate auth code when we've actually reached end of encrypted data,
 401                // not when caller simply requested 0 bytes
 24402                if (_encryptedDataRemaining <= 0)
 24403                {
 24404                    ValidateAuthCode();
 24405                }
 24406                return 0;
 407            }
 408
 24409            Span<byte> readBuffer = buffer.Slice(0, bytesToRead);
 24410            int bytesRead = _baseStream.Read(readBuffer);
 411
 24412            if (bytesRead > 0)
 24413            {
 24414                _encryptedDataRemaining -= bytesRead;
 24415                ProcessBlock(readBuffer.Slice(0, bytesRead));
 416
 417                // Validate auth code immediately when we've read all encrypted data
 24418                if (_encryptedDataRemaining <= 0)
 24419                {
 24420                    ValidateAuthCode();
 24421                }
 24422            }
 0423            else if (_encryptedDataRemaining > 0)
 0424            {
 425                // Base stream returned 0 bytes but we expected more encrypted data - stream is truncated
 0426                throw new InvalidDataException(SR.UnexpectedEndOfStream);
 427            }
 428
 24429            return bytesRead;
 48430        }
 431
 432        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0433        {
 0434            ValidateBufferArguments(buffer, offset, count);
 0435            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0436        }
 437
 438        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 48439        {
 48440            cancellationToken.ThrowIfCancellationRequested();
 48441            ThrowIfNotReadable();
 442
 48443            int bytesToRead = GetBytesToRead(buffer.Length);
 48444            if (bytesToRead == 0)
 24445            {
 446                // Only validate auth code when we've actually reached end of encrypted data,
 447                // not when caller simply requested 0 bytes
 24448                if (_encryptedDataRemaining <= 0)
 24449                {
 24450                    await ValidateAuthCodeAsync(cancellationToken).ConfigureAwait(false);
 24451                }
 24452                return 0;
 453            }
 454
 24455            int bytesRead = await _baseStream.ReadAsync(buffer.Slice(0, bytesToRead), cancellationToken).ConfigureAwait(
 456
 24457            if (bytesRead > 0)
 24458            {
 24459                _encryptedDataRemaining -= bytesRead;
 24460                ProcessBlock(buffer.Span.Slice(0, bytesRead));
 461
 462                // Validate auth code immediately when we've read all encrypted data
 24463                if (_encryptedDataRemaining <= 0)
 24464                {
 24465                    await ValidateAuthCodeAsync(cancellationToken).ConfigureAwait(false);
 24466                }
 24467            }
 0468            else if (_encryptedDataRemaining > 0)
 0469            {
 470                // Base stream returned 0 bytes but we expected more encrypted data - stream is truncated
 0471                throw new InvalidDataException(SR.UnexpectedEndOfStream);
 472            }
 473
 24474            return bytesRead;
 48475        }
 476
 48477        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[KeystreamBufferSize];
 478
 479        private void WriteCore(ReadOnlySpan<byte> buffer, byte[] workBuffer)
 24480        {
 48481            while (!buffer.IsEmpty)
 24482            {
 24483                int bytesToProcess = Math.Min(buffer.Length, workBuffer.Length);
 484
 24485                buffer[..bytesToProcess].CopyTo(workBuffer);
 24486                ProcessBlock(workBuffer.AsSpan(0, bytesToProcess));
 24487                _baseStream.Write(workBuffer, 0, bytesToProcess);
 488
 24489                buffer = buffer[bytesToProcess..];
 24490            }
 24491        }
 492
 493        private void ThrowIfNotWritable()
 48494        {
 48495            ObjectDisposedException.ThrowIf(_disposed, this);
 496
 48497            if (!_encrypting)
 0498            {
 0499                throw new NotSupportedException(SR.WritingNotSupported);
 500            }
 48501        }
 502
 503        public override void Write(byte[] buffer, int offset, int count)
 24504        {
 24505            ValidateBufferArguments(buffer, offset, count);
 24506            Write(buffer.AsSpan(offset, count));
 24507        }
 508
 509        public override void Write(ReadOnlySpan<byte> buffer)
 24510        {
 24511            ThrowIfNotWritable();
 24512            if (!_headerWritten)
 24513            {
 24514                WriteHeader();
 24515            }
 516
 24517            WriteCore(buffer, GetWriteWorkBuffer());
 24518        }
 519
 520        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0521        {
 0522            ValidateBufferArguments(buffer, offset, count);
 0523            return WriteAsyncCore(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0524        }
 525
 526        private async ValueTask WriteAsyncCore(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
 24527        {
 24528            cancellationToken.ThrowIfCancellationRequested();
 24529            ThrowIfNotWritable();
 24530            if (!_headerWritten)
 24531            {
 24532                await WriteHeaderAsync(cancellationToken).ConfigureAwait(false);
 24533            }
 534
 24535            byte[] workBuffer = GetWriteWorkBuffer();
 536
 48537            while (!buffer.IsEmpty)
 24538            {
 24539                int bytesToProcess = Math.Min(buffer.Length, workBuffer.Length);
 540
 24541                buffer[..bytesToProcess].CopyTo(workBuffer);
 24542                ProcessBlock(workBuffer.AsSpan(0, bytesToProcess));
 24543                await _baseStream.WriteAsync(workBuffer.AsMemory(0, bytesToProcess), cancellationToken).ConfigureAwait(f
 544
 24545                buffer = buffer[bytesToProcess..];
 24546            }
 24547        }
 548
 549        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
 24550        {
 24551            return WriteAsyncCore(buffer, cancellationToken);
 24552        }
 553
 554        protected override void Dispose(bool disposing)
 48555        {
 48556            if (_disposed)
 0557            {
 0558                return;
 559            }
 560
 48561            if (disposing)
 48562            {
 563                try
 48564                {
 48565                    if (_encrypting && !_authCodeFinalized)
 24566                    {
 24567                        FinishEncryptingAsync(isAsync: false, CancellationToken.None).GetAwaiter().GetResult();
 24568                    }
 48569                }
 570                finally
 48571                {
 48572                    _disposed = true;
 48573                    _aes.Dispose();
 48574                    _hmac?.Dispose();
 575
 48576                    if (!_leaveOpen)
 24577                    {
 24578                        _baseStream.Dispose();
 24579                    }
 48580                }
 48581            }
 582
 48583            base.Dispose(disposing);
 48584        }
 585
 586        public override async ValueTask DisposeAsync()
 48587        {
 48588            if (_disposed)
 0589            {
 0590                return;
 591            }
 592
 593            try
 48594            {
 48595                if (_encrypting && !_authCodeFinalized)
 24596                {
 24597                    await FinishEncryptingAsync(isAsync: true, CancellationToken.None).ConfigureAwait(false);
 24598                }
 48599            }
 600            finally
 48601            {
 48602                _aes.Dispose();
 48603                _hmac?.Dispose();
 604
 48605                if (!_leaveOpen)
 24606                {
 24607                    await _baseStream.DisposeAsync().ConfigureAwait(false);
 24608                }
 48609            }
 610
 48611            _disposed = true;
 48612        }
 613
 614        /// <summary>
 615        /// Completes the encryption sequence: ensures the header is written (even for empty entries),
 616        /// appends the HMAC authentication code, and flushes the base stream.
 617        /// </summary>
 618        private async Task FinishEncryptingAsync(bool isAsync, CancellationToken cancellationToken)
 48619        {
 48620            Debug.Assert(_encrypting && !_authCodeFinalized);
 621
 622            // Ensure header is written even for empty files
 48623            if (!_headerWritten)
 0624            {
 0625                if (isAsync)
 0626                {
 0627                    await WriteHeaderAsync(cancellationToken).ConfigureAwait(false);
 0628                }
 629                else
 0630                {
 0631                    WriteHeader();
 0632                }
 0633            }
 634
 635            // Write Auth Code
 48636            await WriteAuthCodeCoreAsync(isAsync, cancellationToken).ConfigureAwait(false);
 637
 48638            if (isAsync)
 24639            {
 24640                await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
 24641            }
 642            else
 24643            {
 24644                _baseStream.Flush();
 24645            }
 48646        }
 647
 144648        public override bool CanRead => !_encrypting && !_disposed;
 48649        public override bool CanSeek => false;
 48650        public override bool CanWrite => _encrypting && !_disposed;
 0651        public override long Length => throw new NotSupportedException();
 652
 653        public override long Position
 654        {
 0655            get => throw new NotSupportedException();
 0656            set => throw new NotSupportedException();
 657        }
 658
 659        public override void Flush()
 0660        {
 0661            ObjectDisposedException.ThrowIf(_disposed, this);
 0662            _baseStream.Flush();
 0663        }
 664
 665        public override async Task FlushAsync(CancellationToken cancellationToken)
 0666        {
 0667            ObjectDisposedException.ThrowIf(_disposed, this);
 0668            await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
 0669        }
 670
 0671        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 0672        public override void SetLength(long value) => throw new NotSupportedException();
 673    }
 674}

Methods/Properties

.ctor(System.IO.Stream,System.IO.Compression.WinZipAesKeyMaterial,System.Int64,System.Boolean,System.Boolean)
GetSaltSize(System.Int32)
CreateKey(System.ReadOnlySpan`1<System.Char>,System.Byte[],System.Int32)
Create(System.IO.Stream,System.IO.Compression.WinZipAesKeyMaterial,System.Int64,System.Boolean,System.Boolean)
CreateAsync(System.IO.Stream,System.IO.Compression.WinZipAesKeyMaterial,System.Int64,System.Boolean,System.Boolean,System.Threading.CancellationToken)
ReadAndValidateHeaderCore(System.Boolean,System.IO.Stream,System.IO.Compression.WinZipAesKeyMaterial,System.Threading.CancellationToken)
FinalizeAndCompareHMAC(System.Byte[])
ValidateAuthCode()
ValidateAuthCodeAsync(System.Threading.CancellationToken)
WriteHeaderAsync(System.Threading.CancellationToken)
WriteHeader()
ProcessBlock(System.Span`1<System.Byte>)
GenerateKeystreamBuffer()
XorBytes(System.Span`1<System.Byte>,System.ReadOnlySpan`1<System.Byte>)
WriteAuthCodeCoreAsync(System.Boolean,System.Threading.CancellationToken)
ThrowIfNotReadable()
GetBytesToRead(System.Int32)
Read(System.Byte[],System.Int32,System.Int32)
Read(System.Span`1<System.Byte>)
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
GetWriteWorkBuffer()
WriteCore(System.ReadOnlySpan`1<System.Byte>,System.Byte[])
ThrowIfNotWritable()
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsyncCore(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
Dispose(System.Boolean)
DisposeAsync()
FinishEncryptingAsync(System.Boolean,System.Threading.CancellationToken)
CanRead()
CanSeek()
CanWrite()
Length()
Position()
Position(System.Int64)
Flush()
FlushAsync(System.Threading.CancellationToken)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)