Class: Omnizip::Password::WinzipAesStrategy

Inherits:
EncryptionStrategy show all
Defined in:
lib/omnizip/password/winzip_aes_strategy.rb

Overview

WinZip AES encryption (recommended) Implements WinZip AES-256 encryption standard

Constant Summary collapse

COMPRESSION_METHOD =

WinZip AES compression method

99
KEY_SIZE_128 =

AES key sizes

1
KEY_SIZE_192 =
2
KEY_SIZE_256 =
3
WINZIP_AES_EXTRA_ID =

Extra field header IDs

0x9901

Instance Attribute Summary collapse

Attributes inherited from EncryptionStrategy

#password

Instance Method Summary collapse

Methods inherited from EncryptionStrategy

#method_name, #supports?

Constructor Details

#initialize(password, key_size: 256, compression_method: 8) ⇒ WinzipAesStrategy

Initialize WinZip AES encryption

Parameters:

  • password (String)

    Password to use

  • key_size (Integer) (defaults to: 256)

    Key size (128, 192, or 256 bits)

  • compression_method (Integer) (defaults to: 8)

    Actual compression method to use



25
26
27
28
29
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 25

def initialize(password, key_size: 256, compression_method: 8)
  super(password)
  @key_size = validate_key_size(key_size)
  @actual_compression_method = compression_method
end

Instance Attribute Details

#actual_compression_methodObject (readonly)

Returns the value of attribute actual_compression_method.



19
20
21
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 19

def actual_compression_method
  @actual_compression_method
end

#key_sizeObject (readonly)

Returns the value of attribute key_size.



19
20
21
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 19

def key_size
  @key_size
end

Instance Method Details

#compression_methodInteger

Get compression method for ZIP header

Returns:

  • (Integer)

    Compression method ID (99 for AES)



98
99
100
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 98

def compression_method
  COMPRESSION_METHOD
end

#decrypt(data) ⇒ String

Decrypt data using WinZip AES

Parameters:

  • data (String)

    Encrypted data

Returns:

  • (String)

    Decrypted data



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 62

def decrypt(data)
  require "openssl"

  # Extract components
  salt_size = salt_length
  salt = data[0, salt_size]
  password_verify_bytes = data[salt_size, 2]
  hmac_size = 10
  encrypted_data = data[(salt_size + 2)...-hmac_size]
  stored_hmac = data[-hmac_size..]

  # Derive keys
  encryption_key, expected_verify, hmac_key = derive_winzip_keys(salt)

  # Verify password
  unless password_verify_bytes == expected_verify
    raise Omnizip::PasswordError, "Incorrect password"
  end

  # Verify HMAC
  calculated_hmac = calculate_hmac(hmac_key, encrypted_data)
  unless stored_hmac == calculated_hmac
    raise Omnizip::PasswordError, "Data integrity check failed"
  end

  # Decrypt data
  cipher = OpenSSL::Cipher.new("AES-#{key_bits}-CTR")
  cipher.decrypt
  cipher.key = encryption_key
  cipher.iv = "\x00" * 16

  cipher.update(encrypted_data) + cipher.final
end

#encrypt(data) ⇒ String

Encrypt data using WinZip AES

Parameters:

  • data (String)

    Data to encrypt

Returns:

  • (String)

    Encrypted data with authentication



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 34

def encrypt(data)
  require "openssl"

  # Generate salt
  salt = generate_salt

  # Derive keys
  encryption_key, password_verify, hmac_key = derive_winzip_keys(salt)

  # Create cipher
  cipher = OpenSSL::Cipher.new("AES-#{key_bits}-CTR")
  cipher.encrypt
  cipher.key = encryption_key
  cipher.iv = "\x00" * 16 # WinZip AES uses zero IV

  # Encrypt data
  encrypted = cipher.update(data) + cipher.final

  # Calculate HMAC
  hmac = calculate_hmac(hmac_key, encrypted)

  # Return: salt + password_verify + encrypted_data + hmac
  salt + password_verify + encrypted + hmac
end

#encryption_flagsInteger

Get encryption flags

Returns:

  • (Integer)

    Encryption flags



125
126
127
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 125

def encryption_flags
  0x0001 | 0x0040 # Encrypted + strong encryption
end

#extra_field_dataString

Get extra field data for ZIP central directory

Returns:

  • (String)

    Extra field data



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/omnizip/password/winzip_aes_strategy.rb', line 104

def extra_field_data
  # WinZip AES extra field format:
  # 2 bytes: extra field ID (0x9901)
  # 2 bytes: data size
  # 2 bytes: AES version (0x0001 or 0x0002)
  # 2 bytes: vendor ID (0x4145 = "AE")
  # 1 byte: AES strength (1=128, 2=192, 3=256)
  # 2 bytes: actual compression method

  data = [
    0x0002, # AES version 2
    0x4145, # Vendor ID "AE"
    key_size_code,
    actual_compression_method,
  ].pack("vvCv")

  [WINZIP_AES_EXTRA_ID, data.bytesize].pack("vv") + data
end