File: aes_encryption.rb

package info (click to toggle)
ruby-zip 3.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 11,120 kB
  • sloc: ruby: 9,958; makefile: 23
file content (120 lines) | stat: -rw-r--r-- 3,054 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# frozen_string_literal: true

require 'openssl'

module Zip
  module AESEncryption # :nodoc:
    VERIFIER_LENGTH = 2
    BLOCK_SIZE = 16
    AUTHENTICATION_CODE_LENGTH = 10

    VERSION_AE_1 = 0x01
    VERSION_AE_2 = 0x02

    VERSIONS = [
      VERSION_AE_1,
      VERSION_AE_2
    ].freeze

    STRENGTH_128_BIT = 0x01
    STRENGTH_192_BIT = 0x02
    STRENGTH_256_BIT = 0x03

    STRENGTHS = [
      STRENGTH_128_BIT,
      STRENGTH_192_BIT,
      STRENGTH_256_BIT
    ].freeze

    BITS = {
      STRENGTH_128_BIT => 128,
      STRENGTH_192_BIT => 192,
      STRENGTH_256_BIT => 256
    }.freeze

    KEY_LENGTHS = {
      STRENGTH_128_BIT => 16,
      STRENGTH_192_BIT => 24,
      STRENGTH_256_BIT => 32
    }.freeze

    SALT_LENGTHS = {
      STRENGTH_128_BIT => 8,
      STRENGTH_192_BIT => 12,
      STRENGTH_256_BIT => 16
    }.freeze

    def initialize(password, strength)
      @password = password
      @strength = strength
      @bits = BITS[@strength]
      @key_length = KEY_LENGTHS[@strength]
      @salt_length = SALT_LENGTHS[@strength]
    end

    def header_bytesize
      @salt_length + VERIFIER_LENGTH
    end

    def gp_flags
      0x0001
    end
  end

  class AESDecrypter < Decrypter # :nodoc:
    include AESEncryption

    def decrypt(encrypted_data)
      @hmac.update(encrypted_data)

      idx = 0
      decrypted_data = +''
      amount_to_read = encrypted_data.size

      while amount_to_read.positive?
        @cipher.iv = [@counter + 1].pack('Vx12')
        begin_index = BLOCK_SIZE * idx
        end_index = begin_index + [BLOCK_SIZE, amount_to_read].min
        decrypted_data << @cipher.update(encrypted_data[begin_index...end_index])
        amount_to_read -= BLOCK_SIZE
        @counter += 1
        idx += 1
      end

      # JRuby requires finalization of the cipher. This is a bug, as noted in
      # jruby/jruby-openssl#182 and jruby/jruby-openssl#183.
      decrypted_data << @cipher.final if defined?(JRUBY_VERSION)
      decrypted_data
    end

    def reset!(header)
      raise Error, "Unsupported encryption AES-#{@bits}" unless STRENGTHS.include? @strength

      salt = header[0...@salt_length]
      pwd_verify = header[-VERIFIER_LENGTH..]
      key_material = OpenSSL::KDF.pbkdf2_hmac(
        @password,
        salt:       salt,
        iterations: 1000,
        length:     (2 * @key_length) + VERIFIER_LENGTH,
        hash:       'sha1'
      )
      enc_key = key_material[0...@key_length]
      enc_hmac_key = key_material[@key_length...(2 * @key_length)]
      enc_pwd_verify = key_material[-VERIFIER_LENGTH..]

      raise Error, 'Bad password' if enc_pwd_verify != pwd_verify

      @counter = 0
      @cipher = OpenSSL::Cipher::AES.new(@bits, :CTR)
      @cipher.decrypt
      @cipher.key = enc_key
      @hmac = OpenSSL::HMAC.new(enc_hmac_key, OpenSSL::Digest.new('SHA1'))
    end

    def check_integrity!(io)
      auth_code = io.read(AUTHENTICATION_CODE_LENGTH)
      raise Error, 'Integrity fault' if @hmac.digest[0...AUTHENTICATION_CODE_LENGTH] != auth_code
    end
  end
end