< Summary

Information
Class: System.IO.Compression.ZipCryptoStream
Assembly: System.IO.Compression
File(s): D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 288
Coverable lines: 288
Total lines: 461
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 66
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
CreateCrc32Table()0%660%
Crc32Update(...)100%110%
.ctor(...)100%110%
Create(...)100%110%
CreateAsync(...)100%110%
Create(...)100%110%
.ctor(...)100%110%
CreateKey(...)0%220%
CalculateHeader(...)0%440%
EnsureHeader()0%440%
EnsureHeaderAsync(...)0%440%
ReadAndValidateHeaderCore(...)0%660%
UpdateKeys(...)100%110%
DecryptByte(...)100%110%
DecryptAndUpdateKeys(...)100%110%
Flush()100%110%
Read(...)100%110%
Read(...)0%440%
Seek(...)100%110%
SetLength(...)100%110%
Write(...)100%110%
Write(...)0%660%
Dispose(...)0%880%
DisposeAsync()0%660%
ReadAsync(...)100%110%
ReadAsync(...)0%440%
WriteAsync(...)100%110%
WriteAsync(...)0%660%
FlushAsync(...)100%110%
GetWriteWorkBuffer()0%220%

File(s)

D:\runner\runtime\src\libraries\System.IO.Compression\src\System\IO\Compression\ZipCryptoStream.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;
 5using System.Buffers.Binary;
 6using System.Diagnostics;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11
 12namespace System.IO.Compression
 13{
 14    internal sealed class ZipCryptoStream : Stream
 15    {
 16        private const int EncryptionBufferSize = 4096;
 17
 18        private readonly bool _encrypting;
 19        private readonly Stream _base;
 20        private readonly bool _leaveOpen;
 21        private bool _headerWritten;
 22        private bool _disposed;
 23        private readonly ushort _verifierLow2Bytes;       // (DOS time low word when streaming)
 24        private readonly uint? _crc32ForHeader;           // (CRC-based header when not streaming)
 25
 26        private uint _key0;
 27        private uint _key1;
 28        private uint _key2;
 029        private static readonly uint[] s_crc2Table = CreateCrc32Table();
 30
 31        // Reusable work buffer for write operations, lazily allocated on first write
 32        private byte[]? _writeWorkBuffer;
 33
 34        private static uint[] CreateCrc32Table()
 035        {
 036            var table = new uint[256];
 037            for (uint i = 0; i < 256; i++)
 038            {
 039                uint c = i;
 040                for (int j = 0; j < 8; j++)
 041                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
 042                table[i] = c;
 043            }
 044            return table;
 045        }
 46
 047        private static uint Crc32Update(uint crc, byte b) => s_crc2Table[(crc ^ b) & 0xFF] ^ (crc >> 8);
 48
 49        // Private decryption constructor - use Create/CreateAsync factory methods instead.
 50        // Keys must already be validated before calling this constructor.
 051        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 052        {
 053            _base = baseStream;
 054            _key0 = key0;
 055            _key1 = key1;
 056            _key2 = key2;
 057            _encrypting = false;
 058            _leaveOpen = leaveOpen;
 059        }
 60
 61        /// <summary>
 62        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header synchronously.
 63        /// </summary>
 64        internal static ZipCryptoStream Create(Stream baseStream, ZipCryptoKeys keys, byte expectedCheckByte, bool encry
 065        {
 066            ArgumentNullException.ThrowIfNull(baseStream);
 067            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 68
 069            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 070            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 071        }
 72
 73        /// <summary>
 74        /// Creates a ZipCryptoStream for decryption. Reads and validates the 12-byte header asynchronously.
 75        /// </summary>
 76        internal static async Task<ZipCryptoStream> CreateAsync(Stream baseStream, ZipCryptoKeys keys, byte expectedChec
 077        {
 078            ArgumentNullException.ThrowIfNull(baseStream);
 079            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 80
 081            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 082            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 083        }
 84
 85        /// <summary>
 86        /// Creates a ZipCryptoStream for encryption. Only synchronous creation is needed since no I/O is performed here
 87        /// </summary>
 88        internal static ZipCryptoStream Create(Stream baseStream,
 89                                             ZipCryptoKeys keys,
 90                                             ushort passwordVerifierLow2Bytes,
 91                                             bool encrypting,
 92                                             uint? crc32 = null,
 93                                             bool leaveOpen = false)
 094        {
 095            ArgumentNullException.ThrowIfNull(baseStream);
 096            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 97
 098            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 099        }
 100
 101        // Encryption constructor
 0102        private ZipCryptoStream(Stream baseStream,
 0103                               ZipCryptoKeys keys,
 0104                               ushort passwordVerifierLow2Bytes,
 0105                               uint? crc32,
 0106                               bool leaveOpen)
 0107        {
 0108            _base = baseStream;
 0109            _encrypting = true;
 0110            _leaveOpen = leaveOpen;
 0111            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 0112            _crc32ForHeader = crc32;
 0113            _key0 = keys.Key0;
 0114            _key1 = keys.Key1;
 0115            _key2 = keys.Key2;
 0116        }
 117
 118        // Creates the persisted key material from a password.
 119        // Returns a struct of 3 integers to keep the key off the heap.
 120        internal static ZipCryptoKeys CreateKey(ReadOnlySpan<char> password)
 0121        {
 122            // Initialize keys with standard ZipCrypto initial values
 0123            uint key0 = 305419896;
 0124            uint key1 = 591751049;
 0125            uint key2 = 878082192;
 126
 127            // Feed the password's UTF-8 bytes into the key schedule. UTF-8 (rather than ASCII)
 128            // preserves non-ASCII passwords; interop with tools that assume a different code page
 129            // is documented as a caveat.
 0130            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 131            try
 0132            {
 0133                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 0134                for (int i = 0; i < byteCount; i++)
 0135                {
 0136                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 0137                }
 0138            }
 139            finally
 0140            {
 0141                CryptographicOperations.ZeroMemory(passwordBytes);
 0142                ArrayPool<byte>.Shared.Return(passwordBytes);
 0143            }
 144
 0145            return new ZipCryptoKeys(key0, key1, key2);
 0146        }
 147
 148        private void CalculateHeader(Span<byte> header)
 0149        {
 0150            Debug.Assert(header.Length == 12);
 151
 152            // bytes 0..9 random
 0153            RandomNumberGenerator.Fill(header.Slice(0, 10));
 154
 155            // bytes 10..11 verifier
 0156            if (_crc32ForHeader.HasValue)
 0157            {
 0158                uint crc = _crc32ForHeader.Value;
 0159                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0160            }
 161            else
 0162            {
 0163                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 0164            }
 165
 166            // encrypt in place
 0167            for (int i = 0; i < header.Length; i++)
 0168            {
 0169                byte p = header[i];
 0170                byte ks = DecryptByte(_key2);
 0171                header[i] = (byte)(p ^ ks);
 172
 173                // keys updated with PLAINTEXT per ZIP spec
 0174                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0175            }
 0176        }
 177
 178        private unsafe void EnsureHeader()
 0179        {
 0180            if (!_encrypting || _headerWritten)
 0181            {
 0182                return;
 183            }
 184
 0185            Span<byte> header = stackalloc byte[12];
 0186            CalculateHeader(header);
 0187            _base.Write(header);
 0188            _headerWritten = true;
 0189        }
 190
 191        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 0192        {
 0193            if (!_encrypting || _headerWritten)
 0194            {
 0195                return;
 196            }
 197
 0198            byte[] header = new byte[12];
 0199            CalculateHeader(header);
 0200            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 0201            _headerWritten = true;
 0202        }
 203
 204        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 0205        {
 206            // Initialize keys from input
 0207            uint key0 = keys.Key0;
 0208            uint key1 = keys.Key1;
 0209            uint key2 = keys.Key2;
 210
 0211            byte[] hdr = new byte[12];
 212
 213            try
 0214            {
 0215                if (isAsync)
 0216                {
 0217                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 0218                }
 219                else
 0220                {
 0221                    baseStream.ReadExactly(hdr);
 0222                }
 0223            }
 0224            catch (EndOfStreamException)
 0225            {
 0226                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 227            }
 228
 229            // Decrypt header and update keys
 0230            for (int i = 0; i < hdr.Length; i++)
 0231            {
 0232                byte m = DecryptByte(key2);
 0233                byte plain = (byte)(hdr[i] ^ m);
 0234                UpdateKeys(ref key0, ref key1, ref key2, plain);
 0235                hdr[i] = plain;
 0236            }
 237
 0238            if (hdr[11] != expectedCheckByte)
 0239            {
 0240                throw new InvalidDataException(SR.InvalidPassword);
 241            }
 242
 0243            return (key0, key1, key2);
 0244        }
 245
 246        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 0247        {
 0248            key0 = Crc32Update(key0, b);
 0249            key1 += (key0 & 0xFF);
 0250            key1 = key1 * 134775813 + 1;
 0251            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 0252        }
 253
 254        private static byte DecryptByte(uint key2)
 0255        {
 0256            uint temp = key2 | 2;
 0257            return (byte)((temp * (temp ^ 1)) >> 8);
 0258        }
 259
 260        private byte DecryptAndUpdateKeys(byte ciph)
 0261        {
 0262            byte m = DecryptByte(_key2);
 0263            byte plain = (byte)(ciph ^ m);
 0264            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 0265            return plain;
 0266        }
 267
 0268        public override bool CanRead => !_disposed && !_encrypting;
 0269        public override bool CanSeek => false;
 0270        public override bool CanWrite => !_disposed && _encrypting;
 0271        public override long Length => throw new NotSupportedException();
 272        public override long Position
 273        {
 0274            get => throw new NotSupportedException();
 0275            set => throw new NotSupportedException();
 276        }
 277        public override void Flush()
 0278        {
 0279            ObjectDisposedException.ThrowIf(_disposed, this);
 0280            _base.Flush();
 0281        }
 282
 283        public override int Read(byte[] buffer, int offset, int count)
 0284        {
 0285            ValidateBufferArguments(buffer, offset, count);
 0286            return Read(buffer.AsSpan(offset, count));
 0287        }
 288
 289        public override int Read(Span<byte> destination)
 0290        {
 0291            ObjectDisposedException.ThrowIf(_disposed, this);
 0292            if (_encrypting)
 0293            {
 0294                throw new NotSupportedException(SR.ReadingNotSupported);
 295            }
 0296            int n = _base.Read(destination);
 0297            for (int i = 0; i < n; i++)
 0298                destination[i] = DecryptAndUpdateKeys(destination[i]);
 0299            return n;
 300
 0301        }
 302
 0303        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 0304        public override void SetLength(long value) => throw new NotSupportedException();
 305
 306        public override void Write(byte[] buffer, int offset, int count)
 0307        {
 0308            ValidateBufferArguments(buffer, offset, count);
 0309            Write(buffer.AsSpan(offset, count));
 0310        }
 311
 312        public override void Write(ReadOnlySpan<byte> buffer)
 0313        {
 0314            ObjectDisposedException.ThrowIf(_disposed, this);
 0315            if (!_encrypting)
 0316            {
 0317                throw new NotSupportedException(SR.WritingNotSupported);
 318            }
 319
 0320            EnsureHeader();
 321
 0322            byte[] workBuffer = GetWriteWorkBuffer();
 323
 0324            while (!buffer.IsEmpty)
 0325            {
 0326                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 327
 0328                for (int i = 0; i < chunkSize; i++)
 0329                {
 0330                    byte ks = DecryptByte(_key2);
 0331                    byte p = buffer[i];
 0332                    workBuffer[i] = (byte)(p ^ ks);
 0333                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0334                }
 335
 0336                _base.Write(workBuffer, 0, chunkSize);
 0337                buffer = buffer[chunkSize..];
 0338            }
 0339        }
 340
 341        protected override void Dispose(bool disposing)
 0342        {
 0343            if (_disposed)
 0344            {
 0345                return;
 346            }
 0347            _disposed = true;
 348
 0349            if (disposing)
 0350            {
 351                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 0352                if (_encrypting)
 0353                {
 0354                    EnsureHeader();
 0355                }
 356
 0357                if (!_leaveOpen)
 0358                {
 0359                    _base.Dispose();
 0360                }
 0361            }
 0362            base.Dispose(disposing);
 0363        }
 364
 365        public override async ValueTask DisposeAsync()
 0366        {
 0367            if (_disposed)
 0368            {
 0369                return;
 370            }
 0371            _disposed = true;
 372
 373            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 0374            if (_encrypting)
 0375            {
 0376                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0377            }
 0378            if (!_leaveOpen)
 0379            {
 0380                await _base.DisposeAsync().ConfigureAwait(false);
 0381            }
 382
 0383            GC.SuppressFinalize(this);
 384
 385            // Don't call base.DisposeAsync() as it would call Dispose() synchronously,
 386            // which could fail on async-only streams. We've already handled all cleanup.
 0387        }
 388
 389        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0390        {
 0391            ValidateBufferArguments(buffer, offset, count);
 0392            return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0393        }
 394
 395        public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = defaul
 0396        {
 0397            ObjectDisposedException.ThrowIf(_disposed, this);
 0398            if (_encrypting)
 0399            {
 0400                throw new NotSupportedException(SR.ReadingNotSupported);
 401            }
 402
 0403            cancellationToken.ThrowIfCancellationRequested();
 0404            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 0405            Span<byte> span = buffer.Span;
 406
 0407            for (int i = 0; i < n; i++)
 0408            {
 0409                span[i] = DecryptAndUpdateKeys(span[i]);
 0410            }
 411
 0412            return n;
 0413        }
 414
 415        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
 0416        {
 0417            ValidateBufferArguments(buffer, offset, count);
 0418            return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
 0419        }
 420
 421        public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = de
 0422        {
 0423            ObjectDisposedException.ThrowIf(_disposed, this);
 0424            if (!_encrypting)
 0425            {
 0426                throw new NotSupportedException(SR.WritingNotSupported);
 427            }
 428
 0429            cancellationToken.ThrowIfCancellationRequested();
 430
 0431            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 432
 0433            byte[] workBuffer = GetWriteWorkBuffer();
 434
 0435            while (!buffer.IsEmpty)
 0436            {
 0437                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 0438                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 439
 0440                for (int i = 0; i < chunkSize; i++)
 0441                {
 0442                    byte ks = DecryptByte(_key2);
 0443                    byte p = chunk[i];
 0444                    workBuffer[i] = (byte)(p ^ ks);
 0445                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 0446                }
 447
 0448                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 0449                buffer = buffer[chunkSize..];
 0450            }
 0451        }
 452
 453        public override Task FlushAsync(CancellationToken cancellationToken)
 0454        {
 0455            ObjectDisposedException.ThrowIf(_disposed, this);
 0456            return _base.FlushAsync(cancellationToken);
 0457        }
 458
 0459        private byte[] GetWriteWorkBuffer() => _writeWorkBuffer ??= new byte[EncryptionBufferSize];
 460    }
 461}

Methods/Properties

.cctor()
CreateCrc32Table()
Crc32Update(System.UInt32,System.Byte)
.ctor(System.IO.Stream,System.UInt32,System.UInt32,System.UInt32,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Boolean)
CreateAsync(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Boolean,System.Threading.CancellationToken,System.Boolean)
Create(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Boolean,System.Nullable`1<System.UInt32>,System.Boolean)
.ctor(System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.UInt16,System.Nullable`1<System.UInt32>,System.Boolean)
CreateKey(System.ReadOnlySpan`1<System.Char>)
CalculateHeader(System.Span`1<System.Byte>)
EnsureHeader()
EnsureHeaderAsync(System.Threading.CancellationToken)
ReadAndValidateHeaderCore(System.Boolean,System.IO.Stream,System.IO.Compression.ZipCryptoKeys,System.Byte,System.Threading.CancellationToken)
UpdateKeys(System.UInt32&,System.UInt32&,System.UInt32&,System.Byte)
DecryptByte(System.UInt32)
DecryptAndUpdateKeys(System.Byte)
CanRead()
CanSeek()
CanWrite()
Length()
Position()
Position(System.Int64)
Flush()
Read(System.Byte[],System.Int32,System.Int32)
Read(System.Span`1<System.Byte>)
Seek(System.Int64,System.IO.SeekOrigin)
SetLength(System.Int64)
Write(System.Byte[],System.Int32,System.Int32)
Write(System.ReadOnlySpan`1<System.Byte>)
Dispose(System.Boolean)
DisposeAsync()
ReadAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
ReadAsync(System.Memory`1<System.Byte>,System.Threading.CancellationToken)
WriteAsync(System.Byte[],System.Int32,System.Int32,System.Threading.CancellationToken)
WriteAsync(System.ReadOnlyMemory`1<System.Byte>,System.Threading.CancellationToken)
FlushAsync(System.Threading.CancellationToken)
GetWriteWorkBuffer()