< 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
 72420447        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.
 130551        private ZipCryptoStream(Stream baseStream, uint key0, uint key1, uint key2, bool leaveOpen = false)
 130552        {
 130553            _base = baseStream;
 130554            _key0 = key0;
 130555            _key1 = key1;
 130556            _key2 = key2;
 130557            _encrypting = false;
 130558            _leaveOpen = leaveOpen;
 130559        }
 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
 129665        {
 129666            ArgumentNullException.ThrowIfNull(baseStream);
 129667            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 68
 129669            (uint key0, uint key1, uint key2) = ReadAndValidateHeaderCore(isAsync: false, baseStream, keys, expectedChec
 65470            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 65471        }
 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
 129677        {
 129678            ArgumentNullException.ThrowIfNull(baseStream);
 129679            Debug.Assert(!encrypting, "Use the overload with passwordVerifierLow2Bytes for encryption.");
 80
 129681            (uint key0, uint key1, uint key2) = await ReadAndValidateHeaderCore(isAsync: true, baseStream, keys, expecte
 65182            return new ZipCryptoStream(baseStream, key0, key1, key2, leaveOpen);
 65183        }
 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)
 129694        {
 129695            ArgumentNullException.ThrowIfNull(baseStream);
 129696            Debug.Assert(encrypting, "Use the overload with expectedCheckByte for decryption.");
 97
 129698            return new ZipCryptoStream(baseStream, keys, passwordVerifierLow2Bytes, crc32, leaveOpen);
 129699        }
 100
 101        // Encryption constructor
 1296102        private ZipCryptoStream(Stream baseStream,
 1296103                               ZipCryptoKeys keys,
 1296104                               ushort passwordVerifierLow2Bytes,
 1296105                               uint? crc32,
 1296106                               bool leaveOpen)
 1296107        {
 1296108            _base = baseStream;
 1296109            _encrypting = true;
 1296110            _leaveOpen = leaveOpen;
 1296111            _verifierLow2Bytes = passwordVerifierLow2Bytes;
 1296112            _crc32ForHeader = crc32;
 1296113            _key0 = keys.Key0;
 1296114            _key1 = keys.Key1;
 1296115            _key2 = keys.Key2;
 1296116        }
 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)
 3888121        {
 122            // Initialize keys with standard ZipCrypto initial values
 3888123            uint key0 = 305419896;
 3888124            uint key1 = 591751049;
 3888125            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.
 3888130            byte[] passwordBytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(password.Length));
 131            try
 3888132            {
 3888133                int byteCount = Encoding.UTF8.GetBytes(password, passwordBytes);
 175776134                for (int i = 0; i < byteCount; i++)
 84000135                {
 84000136                    UpdateKeys(ref key0, ref key1, ref key2, passwordBytes[i]);
 84000137                }
 3888138            }
 139            finally
 3888140            {
 3888141                CryptographicOperations.ZeroMemory(passwordBytes);
 3888142                ArrayPool<byte>.Shared.Return(passwordBytes);
 3888143            }
 144
 3888145            return new ZipCryptoKeys(key0, key1, key2);
 3888146        }
 147
 148        private void CalculateHeader(Span<byte> header)
 1296149        {
 1296150            Debug.Assert(header.Length == 12);
 151
 152            // bytes 0..9 random
 1296153            RandomNumberGenerator.Fill(header.Slice(0, 10));
 154
 155            // bytes 10..11 verifier
 1296156            if (_crc32ForHeader.HasValue)
 0157            {
 0158                uint crc = _crc32ForHeader.Value;
 0159                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), (ushort)(crc >> 16));
 0160            }
 161            else
 1296162            {
 1296163                BinaryPrimitives.WriteUInt16LittleEndian(header.Slice(10), _verifierLow2Bytes);
 1296164            }
 165
 166            // encrypt in place
 33696167            for (int i = 0; i < header.Length; i++)
 15552168            {
 15552169                byte p = header[i];
 15552170                byte ks = DecryptByte(_key2);
 15552171                header[i] = (byte)(p ^ ks);
 172
 173                // keys updated with PLAINTEXT per ZIP spec
 15552174                UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 15552175            }
 1296176        }
 177
 178        private unsafe void EnsureHeader()
 648179        {
 648180            if (!_encrypting || _headerWritten)
 0181            {
 0182                return;
 183            }
 184
 648185            Span<byte> header = stackalloc byte[12];
 648186            CalculateHeader(header);
 648187            _base.Write(header);
 648188            _headerWritten = true;
 648189        }
 190
 191        private async ValueTask EnsureHeaderAsync(CancellationToken cancellationToken)
 648192        {
 648193            if (!_encrypting || _headerWritten)
 0194            {
 0195                return;
 196            }
 197
 648198            byte[] header = new byte[12];
 648199            CalculateHeader(header);
 648200            await _base.WriteAsync(header, cancellationToken).ConfigureAwait(false);
 648201            _headerWritten = true;
 648202        }
 203
 204        private static async Task<(uint key0, uint key1, uint key2)> ReadAndValidateHeaderCore(bool isAsync, Stream base
 2592205        {
 206            // Initialize keys from input
 2592207            uint key0 = keys.Key0;
 2592208            uint key1 = keys.Key1;
 2592209            uint key2 = keys.Key2;
 210
 2592211            byte[] hdr = new byte[12];
 212
 213            try
 2592214            {
 2592215                if (isAsync)
 1296216                {
 1296217                    await baseStream.ReadExactlyAsync(hdr, cancellationToken).ConfigureAwait(false);
 1296218                }
 219                else
 1296220                {
 1296221                    baseStream.ReadExactly(hdr);
 1296222                }
 2592223            }
 0224            catch (EndOfStreamException)
 0225            {
 0226                throw new InvalidDataException(SR.TruncatedZipCryptoHeader);
 227            }
 228
 229            // Decrypt header and update keys
 67392230            for (int i = 0; i < hdr.Length; i++)
 31104231            {
 31104232                byte m = DecryptByte(key2);
 31104233                byte plain = (byte)(hdr[i] ^ m);
 31104234                UpdateKeys(ref key0, ref key1, ref key2, plain);
 31104235                hdr[i] = plain;
 31104236            }
 237
 2592238            if (hdr[11] != expectedCheckByte)
 1287239            {
 1287240                throw new InvalidDataException(SR.InvalidPassword);
 241            }
 242
 1305243            return (key0, key1, key2);
 1305244        }
 245
 246        private static void UpdateKeys(ref uint key0, ref uint key1, ref uint key2, byte b)
 362102247        {
 362102248            key0 = Crc32Update(key0, b);
 362102249            key1 += (key0 & 0xFF);
 362102250            key1 = key1 * 134775813 + 1;
 362102251            key2 = Crc32Update(key2, (byte)(key1 >> 24));
 362102252        }
 253
 254        private static byte DecryptByte(uint key2)
 278102255        {
 278102256            uint temp = key2 | 2;
 278102257            return (byte)((temp * (temp ^ 1)) >> 8);
 278102258        }
 259
 260        private byte DecryptAndUpdateKeys(byte ciph)
 116070261        {
 116070262            byte m = DecryptByte(_key2);
 116070263            byte plain = (byte)(ciph ^ m);
 116070264            UpdateKeys(ref _key0, ref _key1, ref _key2, plain);
 116070265            return plain;
 116070266        }
 267
 2610268        public override bool CanRead => !_disposed && !_encrypting;
 1296269        public override bool CanSeek => false;
 1296270        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)
 655284        {
 655285            ValidateBufferArguments(buffer, offset, count);
 655286            return Read(buffer.AsSpan(offset, count));
 655287        }
 288
 289        public override int Read(Span<byte> destination)
 655290        {
 655291            ObjectDisposedException.ThrowIf(_disposed, this);
 655292            if (_encrypting)
 0293            {
 0294                throw new NotSupportedException(SR.ReadingNotSupported);
 295            }
 655296            int n = _base.Read(destination);
 117692297            for (int i = 0; i < n; i++)
 58191298                destination[i] = DecryptAndUpdateKeys(destination[i]);
 655299            return n;
 300
 655301        }
 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)
 648307        {
 648308            ValidateBufferArguments(buffer, offset, count);
 648309            Write(buffer.AsSpan(offset, count));
 648310        }
 311
 312        public override void Write(ReadOnlySpan<byte> buffer)
 648313        {
 648314            ObjectDisposedException.ThrowIf(_disposed, this);
 648315            if (!_encrypting)
 0316            {
 0317                throw new NotSupportedException(SR.WritingNotSupported);
 318            }
 319
 648320            EnsureHeader();
 321
 648322            byte[] workBuffer = GetWriteWorkBuffer();
 323
 1296324            while (!buffer.IsEmpty)
 648325            {
 648326                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 327
 116672328                for (int i = 0; i < chunkSize; i++)
 57688329                {
 57688330                    byte ks = DecryptByte(_key2);
 57688331                    byte p = buffer[i];
 57688332                    workBuffer[i] = (byte)(p ^ ks);
 57688333                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 57688334                }
 335
 648336                _base.Write(workBuffer, 0, chunkSize);
 648337                buffer = buffer[chunkSize..];
 648338            }
 648339        }
 340
 341        protected override void Dispose(bool disposing)
 654342        {
 654343            if (_disposed)
 0344            {
 0345                return;
 346            }
 654347            _disposed = true;
 348
 654349            if (disposing)
 654350            {
 351                // If encrypted empty entry (no payload written), still must emit 12-byte header:
 654352                if (_encrypting)
 0353                {
 0354                    EnsureHeader();
 0355                }
 356
 654357                if (!_leaveOpen)
 654358                {
 654359                    _base.Dispose();
 654360                }
 654361            }
 654362            base.Dispose(disposing);
 654363        }
 364
 365        public override async ValueTask DisposeAsync()
 651366        {
 651367            if (_disposed)
 0368            {
 0369                return;
 370            }
 651371            _disposed = true;
 372
 373            // If encrypted empty entry (no payload written), still must emit 12-byte header:
 651374            if (_encrypting)
 0375            {
 0376                await EnsureHeaderAsync(CancellationToken.None).ConfigureAwait(false);
 0377            }
 651378            if (!_leaveOpen)
 651379            {
 651380                await _base.DisposeAsync().ConfigureAwait(false);
 651381            }
 382
 651383            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.
 651387        }
 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
 651396        {
 651397            ObjectDisposedException.ThrowIf(_disposed, this);
 651398            if (_encrypting)
 0399            {
 0400                throw new NotSupportedException(SR.ReadingNotSupported);
 401            }
 402
 651403            cancellationToken.ThrowIfCancellationRequested();
 651404            int n = await _base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
 651405            Span<byte> span = buffer.Span;
 406
 117060407            for (int i = 0; i < n; i++)
 57879408            {
 57879409                span[i] = DecryptAndUpdateKeys(span[i]);
 57879410            }
 411
 651412            return n;
 651413        }
 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
 648422        {
 648423            ObjectDisposedException.ThrowIf(_disposed, this);
 648424            if (!_encrypting)
 0425            {
 0426                throw new NotSupportedException(SR.WritingNotSupported);
 427            }
 428
 648429            cancellationToken.ThrowIfCancellationRequested();
 430
 648431            await EnsureHeaderAsync(cancellationToken).ConfigureAwait(false);
 432
 648433            byte[] workBuffer = GetWriteWorkBuffer();
 434
 1296435            while (!buffer.IsEmpty)
 648436            {
 648437                int chunkSize = Math.Min(buffer.Length, workBuffer.Length);
 648438                ReadOnlySpan<byte> chunk = buffer.Span[..chunkSize];
 439
 116672440                for (int i = 0; i < chunkSize; i++)
 57688441                {
 57688442                    byte ks = DecryptByte(_key2);
 57688443                    byte p = chunk[i];
 57688444                    workBuffer[i] = (byte)(p ^ ks);
 57688445                    UpdateKeys(ref _key0, ref _key1, ref _key2, p);
 57688446                }
 447
 648448                await _base.WriteAsync(workBuffer.AsMemory(0, chunkSize), cancellationToken).ConfigureAwait(false);
 648449                buffer = buffer[chunkSize..];
 648450            }
 648451        }
 452
 453        public override Task FlushAsync(CancellationToken cancellationToken)
 0454        {
 0455            ObjectDisposedException.ThrowIf(_disposed, this);
 0456            return _base.FlushAsync(cancellationToken);
 0457        }
 458
 1296459        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()