Passed
Push — master ( 3b7697...25d5dd )
by Alexey
03:48 queued 29s
created

WinZipAesContext   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 49
c 1
b 0
f 1
dl 0
loc 144
rs 10
wmc 12

7 Methods

Rating   Name   Duplication   Size   Complexity  
A encrypt() 0 6 1
A getPasswordVerifier() 0 3 1
A __construct() 0 32 3
A decryption() 0 5 1
A checkAuthCode() 0 7 2
A getHmac() 0 6 1
A updateIv() 0 13 3
1
<?php
2
3
namespace PhpZip\IO\Filter\Cipher\WinZipAes;
4
5
use PhpZip\Exception\RuntimeException;
6
use PhpZip\Exception\ZipAuthenticationException;
7
use PhpZip\Util\CryptoUtil;
8
9
/**
10
 * WinZip Aes Encryption.
11
 *
12
 * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT APPENDIX E
13
 * @see https://www.winzip.com/win/en/aes_info.html
14
 *
15
 * @author Ne-Lexa [email protected]
16
 * @license MIT
17
 *
18
 * @internal
19
 */
20
class WinZipAesContext
21
{
22
    /** @var int AES Block size */
23
    const BLOCK_SIZE = self::IV_SIZE;
24
25
    /** @var int Footer size */
26
    const FOOTER_SIZE = 10;
27
28
    /** @var int The iteration count for the derived keys of the cipher, KLAC and MAC. */
29
    const ITERATION_COUNT = 1000;
30
31
    /** @var int Password verifier size */
32
    const PASSWORD_VERIFIER_SIZE = 2;
33
34
    /** @var int IV size */
35
    const IV_SIZE = 16;
36
37
    /** @var string */
38
    private $iv;
39
40
    /** @var string */
41
    private $key;
42
43
    /** @var \HashContext|resource */
44
    private $hmacContext;
45
46
    /** @var string */
47
    private $passwordVerifier;
48
49
    /**
50
     * WinZipAesContext constructor.
51
     *
52
     * @param int    $encryptionStrengthBits
53
     * @param string $password
54
     * @param string $salt
55
     */
56
    public function __construct($encryptionStrengthBits, $password, $salt)
57
    {
58
        $encryptionStrengthBits = (int) $encryptionStrengthBits;
59
60
        if ($password === '') {
61
            throw new RuntimeException('$password is empty');
62
        }
63
64
        if (empty($salt)) {
65
            throw new RuntimeException('$salt is empty');
66
        }
67
68
        // WinZip 99-character limit https://sourceforge.net/p/p7zip/discussion/383044/thread/c859a2f0/
69
        $password = substr($password, 0, 99);
70
71
        $this->iv = str_repeat("\0", self::IV_SIZE);
72
        $keyStrengthBytes = (int) ($encryptionStrengthBits / 8);
73
        $hashLength = $keyStrengthBytes * 2 + self::PASSWORD_VERIFIER_SIZE * 8;
74
75
        $hash = hash_pbkdf2(
76
            'sha1',
77
            $password,
78
            $salt,
79
            self::ITERATION_COUNT,
80
            $hashLength,
81
            true
82
        );
83
84
        $this->key = substr($hash, 0, $keyStrengthBytes);
85
        $sha1Mac = substr($hash, $keyStrengthBytes, $keyStrengthBytes);
86
        $this->hmacContext = hash_init('sha1', \HASH_HMAC, $sha1Mac);
87
        $this->passwordVerifier = substr($hash, 2 * $keyStrengthBytes, self::PASSWORD_VERIFIER_SIZE);
88
    }
89
90
    /**
91
     * @return string
92
     */
93
    public function getPasswordVerifier()
94
    {
95
        return $this->passwordVerifier;
96
    }
97
98
    public function updateIv()
99
    {
100
        for ($ivCharIndex = 0; $ivCharIndex < self::IV_SIZE; $ivCharIndex++) {
101
            $ivByte = \ord($this->iv[$ivCharIndex]);
102
103
            if (++$ivByte === 256) {
104
                // overflow, set this one to 0, increment next
105
                $this->iv[$ivCharIndex] = "\0";
106
            } else {
107
                // no overflow, just write incremented number back and abort
108
                $this->iv[$ivCharIndex] = \chr($ivByte);
109
110
                break;
111
            }
112
        }
113
    }
114
115
    /**
116
     * @param string $data
117
     *
118
     * @return string
119
     */
120
    public function decryption($data)
121
    {
122
        hash_update($this->hmacContext, $data);
0 ignored issues
show
Bug introduced by
It seems like $this->hmacContext can also be of type HashContext; however, parameter $context of hash_update() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        hash_update(/** @scrutinizer ignore-type */ $this->hmacContext, $data);
Loading history...
123
124
        return CryptoUtil::decryptAesCtr($data, $this->key, $this->iv);
125
    }
126
127
    /**
128
     * @param string $data
129
     *
130
     * @return string
131
     */
132
    public function encrypt($data)
133
    {
134
        $encryptionData = CryptoUtil::encryptAesCtr($data, $this->key, $this->iv);
135
        hash_update($this->hmacContext, $encryptionData);
0 ignored issues
show
Bug introduced by
It seems like $this->hmacContext can also be of type HashContext; however, parameter $context of hash_update() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

135
        hash_update(/** @scrutinizer ignore-type */ $this->hmacContext, $encryptionData);
Loading history...
136
137
        return $encryptionData;
138
    }
139
140
    /**
141
     * @param string $authCode
142
     *
143
     * @throws ZipAuthenticationException
144
     */
145
    public function checkAuthCode($authCode)
146
    {
147
        $hmac = $this->getHmac();
148
149
        // check authenticationCode
150
        if (strcmp($hmac, $authCode) !== 0) {
151
            throw new ZipAuthenticationException('Authenticated WinZip AES entry content has been tampered with.');
152
        }
153
    }
154
155
    /**
156
     * @return string
157
     */
158
    public function getHmac()
159
    {
160
        return substr(
161
            hash_final($this->hmacContext, true),
0 ignored issues
show
Bug introduced by
It seems like $this->hmacContext can also be of type HashContext; however, parameter $context of hash_final() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
            hash_final(/** @scrutinizer ignore-type */ $this->hmacContext, true),
Loading history...
162
            0,
163
            self::FOOTER_SIZE
164
        );
165
    }
166
}
167