< 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
82%
Covered lines: 238
Uncovered lines: 50
Coverable lines: 288
Total lines: 461
Line coverage: 82.6%
Branch coverage
71%
Covered branches: 47
Total branches: 66
Branch coverage: 71.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
CreateCrc32Table()100%66100%
Crc32Update(...)100%11100%
.ctor(...)100%11100%
Create(...)100%11100%
CreateAsync(...)100%11100%
Create(...)100%11100%
.ctor(...)100%11100%
CreateKey(...)100%22100%
CalculateHeader(...)75%4478.94%
EnsureHeader()50%4477.77%
EnsureHeaderAsync(...)50%4477.77%
ReadAndValidateHeaderCore(...)100%6689.65%
UpdateKeys(...)100%11100%
DecryptByte(...)100%11100%
DecryptAndUpdateKeys(...)100%11100%
Flush()100%110%
Read(...)100%11100%
Read(...)75%4480%
Seek(...)100%110%
SetLength(...)100%110%
Write(...)100%11100%
Write(...)83.33%6690.47%
Dispose(...)50%8872.22%
DisposeAsync()50%6666.66%
ReadAsync(...)100%110%
ReadAsync(...)75%4485.71%
WriteAsync(...)100%110%
WriteAsync(...)83.33%6691.3%
FlushAsync(...)100%110%
GetWriteWorkBuffer()50%22100%

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;
 129        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()
 135        {
 136            var table = new uint[256];
 51437            for (uint i = 0; i < 256; i++)
 25638            {
 25639                uint c = i;
 460840                for (int j = 0; j < 8; j++)
 204841                    c = (c & 1) != 0 ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
 25642                table[i] = c;
 25643            }
 144            return table;
 145        }
 46
 42440447        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.
 72751        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 72752        {
 72753            _base = baseStream;
 72754            _key0 = key0;
 72755            _key1 = key1;
 72756            _key2 = key2;
 72757            _encrypting = false;
 72758            _leaveOpen = leaveOpen;
 72759        }
 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
 72465        {
 72466            ArgumentNullException.ThrowIfNull(baseStream);
 72467            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 68
 72469            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 36370            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 36371        }
 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
 72477        {
 72478            ArgumentNullException.ThrowIfNull(baseStream);
 72479            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 80
 72481            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 36482            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 36483        }
 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)
 72494        {
 72495            ArgumentNullException.ThrowIfNull(baseStream);
 72496            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 97
 72498            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 72499        }
 100
 101        // Encryption constructor
 724102        private ZipCryptoStream(Stream baseStream,
 724103                               ZipCryptoKeys keys,
 724104                               ushort passwordVerifierLow2Bytes,
 724105                               uint? crc32,
 724106                               bool leaveOpen)
 724107        {
 724108            _base = baseStream;
 724109            _encrypting = true;
 724110            _leaveOpen = leaveOpen;
 724111            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 724112            _crc32ForHeader = crc32;
 724113            _key0 = keys.Key0;
 724114            _key1 = keys.Key1;
 724115            _key2 = keys.Key2;
 724116        }
 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)
 2172121        {
 122            // Initialize keys with standard ZipCrypto initial values
 2172123            uint key0 = 305419896;
 2172124            uint key1 = 591751049;
 2172125            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.
 2172130            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 131            try
 2172132            {
 2172133                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 81872134                for (int i = 0; i < byteCount; i++)
 38764135                {
 38764136                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 38764137                }
 2172138            }
 139            finally
 2172140            {
 2172141                CryptographicOperations.ZeroMemory(passwordBytes);
 2172142                ArrayPool<byte>.Shared.Return(passwordBytes);
 2172143            }
 144
 2172145            return new ZipCryptoKeys(key0, key1, key2);
 2172146        }
 147
 148        private void CalculateHeader(Span<byte> header)
 724149        {
 724150            Debug.Assert(header.Length == 12);
 151
 152            // bytes 0..9 random
 724153            RandomNumberGenerator.Fill(header.Slice(0, 10));
 154
 155            // bytes 10..11 verifier
 724156            if (_crc32ForHeader.HasValue)
 0157            {
 0158                uint crc = _crc32ForHeader.Value;
 0159                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0160            }
 161            else
 724162            {
 724163                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 724164            }
 165
 166            // encrypt in place
 18824167            for (int i = 0; i < header.Length; i++)
 8688168            {
 8688169                byte p = header[i];
 8688170                byte ks = DecryptByte(_key2);
 8688171                header[i] = (byte)(p ^ ks);
 172
 173                // keys updated with PLAINTEXT per ZIP spec
 8688174                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 8688175            }
 724176        }
 177
 178        private unsafe void EnsureHeader()
 362179        {
 362180            if (!_encrypting || _headerWritten)
 0181            {
 0182                return;
 183            }
 184
 362185            Span<byte> header = stackalloc byte[12];
 362186            CalculateHeader(header);
 362187            _base.Write(header);
 362188            _headerWritten = true;
 362189        }
 190
 191        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 362192        {
 362193            if (!_encrypting || _headerWritten)
 0194            {
 0195                return;
 196            }
 197
 362198            byte[] header = new byte[12];
 362199            CalculateHeader(header);
 362200            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 362201            _headerWritten = true;
 362202        }
 203
 204        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 1448205        {
 206            // Initialize keys from input
 1448207            uint key0 = keys.Key0;
 1448208            uint key1 = keys.Key1;
 1448209            uint key2 = keys.Key2;
 210
 1448211            byte[] hdr = new byte[12];
 212
 213            try
 1448214            {
 1448215                if (isAsync)
 724216                {
 724217                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 724218                }
 219                else
 724220                {
 724221                    baseStream.ReadExactly(hdr);
 724222                }
 1448223            }
 0224            catch (EndOfStreamException)
 0225            {
 0226                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 227            }
 228
 229            // Decrypt header and update keys
 37648230            for (int i = 0; i < hdr.Length; i++)
 17376231            {
 17376232                byte m = DecryptByte(key2);
 17376233                byte plain = (byte)(hdr[i] ^ m);
 17376234                UpdateKeys(ref key0, ref key1, ref key2, plain);
 17376235                hdr[i] = plain;
 17376236            }
 237
 1448238            if (hdr[11] != expectedCheckByte)
 721239            {
 721240                throw new InvalidDataException(SR.InvalidPassword);
 241            }
 242
 727243            return (key0, key1, key2);
 727244        }
 245
 246        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 212202247        {
 212202248            key0 = Crc32Update(key0, b);
 212202249            key1 += (key0 & 0xFF);
 212202250            key1 = key1 * 134775813 + 1;
 212202251            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 212202252        }
 253
 254        private static byte DecryptByte(uint key2)
 173438255        {
 173438256            uint temp = key2 | 2;
 173438257            return (byte)((temp * (temp ^ 1)) >> 8);
 173438258        }
 259
 260        private byte DecryptAndUpdateKeys(byte ciph)
 73782261        {
 73782262            byte m = DecryptByte(_key2);
 73782263            byte plain = (byte)(ciph ^ m);
 73782264            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 73782265            return plain;
 73782266        }
 267
 1454268        public override bool CanRead => !_disposed && !_encrypting;
 724269        public override bool CanSeek => false;
 724270        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)
 363284        {
 363285            ValidateBufferArguments(buffer, offset, count);
 363286            return Read(buffer.AsSpan(offset, count));
 363287        }
 288
 289        public override int Read(Span<byte> destination)
 363290        {
 363291            ObjectDisposedException.ThrowIf(_disposed, this);
 363292            if (_encrypting)
 0293            {
 0294                throw new NotSupportedException(SR.ReadingNotSupported);
 295            }
 363296            int n = _base.Read(destination);
 74590297            for (int i = 0; i < n; i++)
 36932298                destination[i] = DecryptAndUpdateKeys(destination[i]);
 363299            return n;
 300
 363301        }
 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)
 362307        {
 362308            ValidateBufferArguments(buffer, offset, count);
 362309            Write(buffer.AsSpan(offset, count));
 362310        }
 311
 312        public override void Write(ReadOnlySpan<byte> buffer)
 362313        {
 362314            ObjectDisposedException.ThrowIf(_disposed, this);
 362315            if (!_encrypting)
 0316            {
 0317                throw new NotSupportedException(SR.WritingNotSupported);
 318            }
 319
 362320            EnsureHeader();
 321
 362322            byte[] workBuffer = GetWriteWorkBuffer();
 323
 724324            while (!buffer.IsEmpty)
 362325            {
 362326                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 327
 74316328                for (int i = 0; i < chunkSize; i++)
 36796329                {
 36796330                    byte ks = DecryptByte(_key2);
 36796331                    byte p = buffer[i];
 36796332                    workBuffer[i] = (byte)(p ^ ks);
 36796333                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 36796334                }
 335
 362336                _base.Write(workBuffer, 0, chunkSize);
 362337                buffer = buffer[chunkSize..];
 362338            }
 362339        }
 340
 341        protected override void Dispose(bool disposing)
 363342        {
 363343            if (_disposed)
 0344            {
 0345                return;
 346            }
 363347            _disposed = true;
 348
 363349            if (disposing)
 363350            {
 351                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 363352                if (_encrypting)
 0353                {
 0354                    EnsureHeader();
 0355                }
 356
 363357                if (!_leaveOpen)
 363358                {
 363359                    _base.Dispose();
 363360                }
 363361            }
 363362            base.Dispose(disposing);
 363363        }
 364
 365        public override async ValueTask DisposeAsync()
 364366        {
 364367            if (_disposed)
 0368            {
 0369                return;
 370            }
 364371            _disposed = true;
 372
 373            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 364374            if (_encrypting)
 0375            {
 0376                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0377            }
 364378            if (!_leaveOpen)
 364379            {
 364380                await _base.DisposeAsync().ConfigureAwait(false);
 364381            }
 382
 364383            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.
 364387        }
 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
 364396        {
 364397            ObjectDisposedException.ThrowIf(_disposed, this);
 364398            if (_encrypting)
 0399            {
 0400                throw new NotSupportedException(SR.ReadingNotSupported);
 401            }
 402
 364403            cancellationToken.ThrowIfCancellationRequested();
 364404            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 364405            Span<byte> span = buffer.Span;
 406
 74428407            for (int i = 0; i < n; i++)
 36850408            {
 36850409                span[i] = DecryptAndUpdateKeys(span[i]);
 36850410            }
 411
 364412            return n;
 364413        }
 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
 362422        {
 362423            ObjectDisposedException.ThrowIf(_disposed, this);
 362424            if (!_encrypting)
 0425            {
 0426                throw new NotSupportedException(SR.WritingNotSupported);
 427            }
 428
 362429            cancellationToken.ThrowIfCancellationRequested();
 430
 362431            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 432
 362433            byte[] workBuffer = GetWriteWorkBuffer();
 434
 724435            while (!buffer.IsEmpty)
 362436            {
 362437                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 362438                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 439
 74316440                for (int i = 0; i < chunkSize; i++)
 36796441                {
 36796442                    byte ks = DecryptByte(_key2);
 36796443                    byte p = chunk[i];
 36796444                    workBuffer[i] = (byte)(p ^ ks);
 36796445                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 36796446                }
 447
 362448                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 362449                buffer = buffer[chunkSize..];
 362450            }
 362451        }
 452
 453        public override Task FlushAsync(CancellationToken cancellationToken)
 0454        {
 0455            ObjectDisposedException.ThrowIf(_disposed, this);
 0456            return _base.FlushAsync(cancellationToken);
 0457        }
 458
 724459        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()