| | | 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 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Runtime.Versioning; |
| | | 7 | | using System.Security.Cryptography; |
| | | 8 | | using System.Text; |
| | | 9 | | |
| | | 10 | | namespace System.IO.Compression |
| | | 11 | | { |
| | | 12 | | /// <summary> |
| | | 13 | | /// Represents the parsed components of WinZip AES key material. |
| | | 14 | | /// The key material layout is: [salt][encryption key][HMAC key][password verifier (2 bytes)]. |
| | | 15 | | /// </summary> |
| | | 16 | | internal readonly struct WinZipAesKeyMaterial |
| | | 17 | | { |
| | 0 | 18 | | public byte[] Salt { get; } |
| | 0 | 19 | | public byte[] EncryptionKey { get; } |
| | 0 | 20 | | public byte[] HmacKey { get; } |
| | 0 | 21 | | public byte[] PasswordVerifier { get; } |
| | | 22 | | public int KeySizeBits { get; } |
| | 0 | 23 | | public int SaltSize { get; } |
| | | 24 | | |
| | | 25 | | private WinZipAesKeyMaterial(byte[] salt, byte[] encryptionKey, byte[] hmacKey, byte[] passwordVerifier, int key |
| | 0 | 26 | | { |
| | 0 | 27 | | if (OperatingSystem.IsBrowser()) |
| | 0 | 28 | | { |
| | 0 | 29 | | throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser); |
| | | 30 | | } |
| | 0 | 31 | | Salt = salt; |
| | 0 | 32 | | EncryptionKey = encryptionKey; |
| | 0 | 33 | | HmacKey = hmacKey; |
| | 0 | 34 | | PasswordVerifier = passwordVerifier; |
| | 0 | 35 | | KeySizeBits = keySizeBits; |
| | 0 | 36 | | SaltSize = GetSaltSize(keySizeBits); |
| | 0 | 37 | | } |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Derives key material from a password and optional salt using PBKDF2-SHA1. |
| | | 41 | | /// </summary> |
| | | 42 | | internal static unsafe WinZipAesKeyMaterial Create(ReadOnlySpan<char> password, byte[]? salt, int keySizeBits) |
| | 0 | 43 | | { |
| | 0 | 44 | | if (OperatingSystem.IsBrowser()) |
| | 0 | 45 | | { |
| | 0 | 46 | | throw new PlatformNotSupportedException(SR.WinZipEncryptionNotSupportedOnBrowser); |
| | | 47 | | } |
| | 0 | 48 | | int saltSize = GetSaltSize(keySizeBits); |
| | 0 | 49 | | int keySizeBytes = keySizeBits / 8; |
| | 0 | 50 | | int totalKeySize = checked(keySizeBytes + keySizeBytes + 2); |
| | | 51 | | |
| | | 52 | | byte[] saltBytes; |
| | 0 | 53 | | if (salt is null) |
| | 0 | 54 | | { |
| | 0 | 55 | | saltBytes = new byte[saltSize]; |
| | 0 | 56 | | RandomNumberGenerator.Fill(saltBytes); |
| | 0 | 57 | | } |
| | | 58 | | else |
| | 0 | 59 | | { |
| | 0 | 60 | | Debug.Assert(salt.Length == saltSize, $"Salt must be {saltSize} bytes for AES-{keySizeBits}."); |
| | 0 | 61 | | saltBytes = salt; |
| | 0 | 62 | | } |
| | | 63 | | |
| | 0 | 64 | | int maxPasswordByteCount = Encoding.UTF8.GetMaxByteCount(password.Length); |
| | 0 | 65 | | byte[] rentedPasswordBytes = ArrayPool<byte>.Shared.Rent(maxPasswordByteCount); |
| | 0 | 66 | | Debug.Assert(totalKeySize <= 66, "totalKeySize should be at most 66 bytes (AES-256: 32 + 32 + 2)"); |
| | 0 | 67 | | Span<byte> derivedKey = stackalloc byte[totalKeySize]; |
| | | 68 | | |
| | | 69 | | try |
| | 0 | 70 | | { |
| | 0 | 71 | | int actualByteCount = Encoding.UTF8.GetBytes(password, rentedPasswordBytes); |
| | 0 | 72 | | Span<byte> passwordSpan = rentedPasswordBytes.AsSpan(0, actualByteCount); |
| | | 73 | | |
| | 0 | 74 | | Rfc2898DeriveBytes.Pbkdf2( |
| | 0 | 75 | | passwordSpan, |
| | 0 | 76 | | saltBytes, |
| | 0 | 77 | | derivedKey, |
| | 0 | 78 | | 1000, // iteration count specified by the WinZip AE-1/AE-2 specification |
| | 0 | 79 | | HashAlgorithmName.SHA1); |
| | | 80 | | |
| | | 81 | | // Slice the derived key directly into its components instead of |
| | | 82 | | // round-tripping through a combined array and Parse. |
| | 0 | 83 | | byte[] encryptionKey = derivedKey.Slice(0, keySizeBytes).ToArray(); |
| | 0 | 84 | | byte[] hmacKey = derivedKey.Slice(keySizeBytes, keySizeBytes).ToArray(); |
| | 0 | 85 | | byte[] passwordVerifier = derivedKey.Slice(keySizeBytes + keySizeBytes, 2).ToArray(); |
| | | 86 | | |
| | 0 | 87 | | return new WinZipAesKeyMaterial(saltBytes, encryptionKey, hmacKey, passwordVerifier, keySizeBits); |
| | | 88 | | } |
| | | 89 | | finally |
| | 0 | 90 | | { |
| | 0 | 91 | | CryptographicOperations.ZeroMemory(rentedPasswordBytes); |
| | 0 | 92 | | CryptographicOperations.ZeroMemory(derivedKey); |
| | 0 | 93 | | ArrayPool<byte>.Shared.Return(rentedPasswordBytes); |
| | 0 | 94 | | } |
| | 0 | 95 | | } |
| | | 96 | | |
| | 0 | 97 | | internal static int GetSaltSize(int keySizeBits) => keySizeBits / 16; |
| | | 98 | | } |
| | | 99 | | } |