Encryptor   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 242
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 242
rs 10
c 0
b 0
f 0
wmc 27
lcom 1
cbo 2

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A validateCipher() 0 10 2
A encrypt() 0 11 3
D decrypt() 0 43 9
A validateKey() 0 7 2
A setNewKey() 0 7 1
A exportKeys() 0 4 1
C getCrypt() 0 32 8
1
<?php
2
/**
3
 * Copyright © 2017 Toan Nguyen. All rights reserved.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Gojira\Framework\Encryption;
10
11
use Gojira\Framework\App\Configuration\Configuration;
12
use Gojira\Framework\Math\Random;
13
14
/**
15
 * Class Encryptor provides basic logic for hashing strings and encrypting/decrypting misc data
16
 *
17
 * @package Gojira\Framework\Encryption
18
 * @author  Toan Nguyen <[email protected]>
19
 */
20
class Encryptor implements EncryptorInterface
21
{
22
    /**
23
     * Array key of encryption key in deployment config
24
     */
25
    const PARAM_CRYPT_KEY = 'options/encryption_key';
26
27
    /**#@+
28
     * Cipher versions
29
     */
30
    const CIPHER_BLOWFISH = 0;
31
32
    const CIPHER_RIJNDAEL_128 = 1;
33
34
    const CIPHER_RIJNDAEL_256 = 2;
35
36
    const CIPHER_LATEST = 2;
37
    /**#@-*/
38
39
    /**
40
     * Indicate cipher
41
     *
42
     * @var int
43
     */
44
    protected $cipher = self::CIPHER_LATEST;
45
46
    /**
47
     * Version of encryption key
48
     *
49
     * @var int
50
     */
51
    protected $keyVersion;
52
53
    /**
54
     * Array of encryption keys
55
     *
56
     * @var string[]
57
     */
58
    protected $keys = [];
59
60
    /**
61
     * Random generator
62
     *
63
     * @var Random
64
     */
65
    protected $random;
66
67
    /**
68
     * @param Random        $random
69
     * @param Configuration $configuration
70
     */
71
    public function __construct(
72
        Random $random,
73
        Configuration $configuration
74
    ) {
75
        $this->random = $random;
76
77
        // load all possible keys
78
        $this->keys = preg_split('/\s+/s', trim($configuration->getData(self::PARAM_CRYPT_KEY)));
79
        $this->keyVersion = count($this->keys) - 1;
80
    }
81
82
    /**
83
     * Check whether specified cipher version is supported
84
     *
85
     * Returns matched supported version or throws exception
86
     *
87
     * @param int $version
88
     *
89
     * @return int
90
     * @throws \Exception
91
     */
92
    public function validateCipher($version)
93
    {
94
        $types = [self::CIPHER_BLOWFISH, self::CIPHER_RIJNDAEL_128, self::CIPHER_RIJNDAEL_256];
95
96
        $version = (int)$version;
97
        if (!in_array($version, $types, true)) {
98
            throw new \Exception((string)new \Gojira\Framework\Phrase('Not supported cipher version'));
99
        }
100
        return $version;
101
    }
102
103
    /**
104
     * Prepend key and cipher versions to encrypted data after encrypting
105
     *
106
     * @param string $data
107
     *
108
     * @return string
109
     */
110
    public function encrypt($data)
111
    {
112
        $crypt = $this->getCrypt();
113
        if (null === $crypt) {
114
            return $data;
115
        }
116
        return $this->keyVersion . ':' . $this->cipher . ':' . (MCRYPT_MODE_CBC ===
117
            $crypt->getMode() ? $crypt->getInitVector() . ':' : '') . base64_encode(
118
                $crypt->encrypt((string)$data)
119
            );
120
    }
121
122
    /**
123
     * Look for key and crypt versions in encrypted data before decrypting
124
     *
125
     * Unsupported/unspecified key version silently fallback to the oldest we have
126
     * Unsupported cipher versions eventually throw exception
127
     * Unspecified cipher version fallback to the oldest we support
128
     *
129
     * @param string $data
130
     *
131
     * @return string
132
     */
133
    public function decrypt($data)
134
    {
135
        if ($data) {
136
            $parts = explode(':', $data, 4);
137
            $partsCount = count($parts);
138
139
            $initVector = false;
140
            // specified key, specified crypt, specified iv
141
            if (4 === $partsCount) {
142
                list($keyVersion, $cryptVersion, $iv, $data) = $parts;
0 ignored issues
show
Unused Code introduced by
The assignment to $cryptVersion is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
143
                $initVector = $iv ? $iv : false;
144
                $keyVersion = (int)$keyVersion;
145
                $cryptVersion = self::CIPHER_RIJNDAEL_256;
146
                // specified key, specified crypt
147
            } elseif (3 === $partsCount) {
148
                list($keyVersion, $cryptVersion, $data) = $parts;
149
                $keyVersion = (int)$keyVersion;
150
                $cryptVersion = (int)$cryptVersion;
151
                // no key version = oldest key, specified crypt
152
            } elseif (2 === $partsCount) {
153
                list($cryptVersion, $data) = $parts;
154
                $keyVersion = 0;
155
                $cryptVersion = (int)$cryptVersion;
156
                // no key version = oldest key, no crypt version = oldest crypt
157
            } elseif (1 === $partsCount) {
158
                $keyVersion = 0;
159
                $cryptVersion = self::CIPHER_BLOWFISH;
160
                // not supported format
161
            } else {
162
                return '';
163
            }
164
            // no key for decryption
165
            if (!isset($this->keys[$keyVersion])) {
166
                return '';
167
            }
168
            $crypt = $this->getCrypt($this->keys[$keyVersion], $cryptVersion, $initVector);
169
            if (null === $crypt) {
170
                return '';
171
            }
172
            return trim($crypt->decrypt(base64_decode((string)$data)));
173
        }
174
        return '';
175
    }
176
177
    /**
178
     * Return crypt model, instantiate if it is empty
179
     *
180
     * @param string|null $key NULL value means usage of the default key specified on constructor
181
     *
182
     * @return \Gojira\Framework\Encryption\Crypt
183
     * @throws \Exception
184
     */
185
    public function validateKey($key)
186
    {
187
        if (preg_match('/\s/s', $key)) {
188
            throw new \Exception((string)new \Gojira\Framework\Phrase('The encryption key format is invalid.'));
189
        }
190
        return $this->getCrypt($key);
191
    }
192
193
    /**
194
     * Attempt to append new key & version
195
     *
196
     * @param string $key
197
     *
198
     * @return $this
199
     */
200
    public function setNewKey($key)
201
    {
202
        $this->validateKey($key);
203
        $this->keys[] = $key;
204
        $this->keyVersion += 1;
205
        return $this;
206
    }
207
208
    /**
209
     * Export current keys as string
210
     *
211
     * @return string
212
     */
213
    public function exportKeys()
214
    {
215
        return implode("\n", $this->keys);
216
    }
217
218
    /**
219
     * Initialize crypt module if needed
220
     *
221
     * By default initializes with latest key and crypt versions
222
     *
223
     * @param string $key
224
     * @param int    $cipherVersion
225
     * @param bool   $initVector
226
     *
227
     * @return Crypt|null
228
     */
229
    protected function getCrypt($key = null, $cipherVersion = null, $initVector = true)
230
    {
231
        if (null === $key && null === $cipherVersion) {
232
            $cipherVersion = self::CIPHER_RIJNDAEL_256;
233
        }
234
235
        if (null === $key) {
236
            $key = $this->keys[$this->keyVersion];
237
        }
238
239
        if (!$key) {
240
            return null;
241
        }
242
243
        if (null === $cipherVersion) {
244
            $cipherVersion = $this->cipher;
245
        }
246
        $cipherVersion = $this->validateCipher($cipherVersion);
247
248
        if ($cipherVersion === self::CIPHER_RIJNDAEL_128) {
249
            $cipher = MCRYPT_RIJNDAEL_128;
250
            $mode = MCRYPT_MODE_ECB;
251
        } elseif ($cipherVersion === self::CIPHER_RIJNDAEL_256) {
252
            $cipher = MCRYPT_RIJNDAEL_256;
253
            $mode = MCRYPT_MODE_CBC;
254
        } else {
255
            $cipher = MCRYPT_BLOWFISH;
256
            $mode = MCRYPT_MODE_ECB;
257
        }
258
259
        return new Crypt($key, $cipher, $mode, $initVector);
260
    }
261
}
262