Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Encryption;
4
 
5
use Illuminate\Contracts\Encryption\DecryptException;
6
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
7
use Illuminate\Contracts\Encryption\EncryptException;
8
use Illuminate\Contracts\Encryption\StringEncrypter;
9
use RuntimeException;
10
 
11
class Encrypter implements EncrypterContract, StringEncrypter
12
{
13
    /**
14
     * The encryption key.
15
     *
16
     * @var string
17
     */
18
    protected $key;
19
 
20
    /**
21
     * The algorithm used for encryption.
22
     *
23
     * @var string
24
     */
25
    protected $cipher;
26
 
27
    /**
28
     * The supported cipher algorithms and their properties.
29
     *
30
     * @var array
31
     */
32
    private static $supportedCiphers = [
33
        'aes-128-cbc' => ['size' => 16, 'aead' => false],
34
        'aes-256-cbc' => ['size' => 32, 'aead' => false],
35
        'aes-128-gcm' => ['size' => 16, 'aead' => true],
36
        'aes-256-gcm' => ['size' => 32, 'aead' => true],
37
    ];
38
 
39
    /**
40
     * Create a new encrypter instance.
41
     *
42
     * @param  string  $key
43
     * @param  string  $cipher
44
     * @return void
45
     *
46
     * @throws \RuntimeException
47
     */
48
    public function __construct($key, $cipher = 'aes-128-cbc')
49
    {
50
        $key = (string) $key;
51
 
52
        if (! static::supported($key, $cipher)) {
53
            $ciphers = implode(', ', array_keys(self::$supportedCiphers));
54
 
55
            throw new RuntimeException("Unsupported cipher or incorrect key length. Supported ciphers are: {$ciphers}.");
56
        }
57
 
58
        $this->key = $key;
59
        $this->cipher = $cipher;
60
    }
61
 
62
    /**
63
     * Determine if the given key and cipher combination is valid.
64
     *
65
     * @param  string  $key
66
     * @param  string  $cipher
67
     * @return bool
68
     */
69
    public static function supported($key, $cipher)
70
    {
71
        if (! isset(self::$supportedCiphers[strtolower($cipher)])) {
72
            return false;
73
        }
74
 
75
        return mb_strlen($key, '8bit') === self::$supportedCiphers[strtolower($cipher)]['size'];
76
    }
77
 
78
    /**
79
     * Create a new encryption key for the given cipher.
80
     *
81
     * @param  string  $cipher
82
     * @return string
83
     */
84
    public static function generateKey($cipher)
85
    {
86
        return random_bytes(self::$supportedCiphers[strtolower($cipher)]['size'] ?? 32);
87
    }
88
 
89
    /**
90
     * Encrypt the given value.
91
     *
92
     * @param  mixed  $value
93
     * @param  bool  $serialize
94
     * @return string
95
     *
96
     * @throws \Illuminate\Contracts\Encryption\EncryptException
97
     */
98
    public function encrypt($value, $serialize = true)
99
    {
100
        $iv = random_bytes(openssl_cipher_iv_length(strtolower($this->cipher)));
101
 
102
        $value = \openssl_encrypt(
103
            $serialize ? serialize($value) : $value,
104
            strtolower($this->cipher), $this->key, 0, $iv, $tag
105
        );
106
 
107
        if ($value === false) {
108
            throw new EncryptException('Could not encrypt the data.');
109
        }
110
 
111
        $iv = base64_encode($iv);
112
        $tag = base64_encode($tag ?? '');
113
 
114
        $mac = self::$supportedCiphers[strtolower($this->cipher)]['aead']
115
            ? '' // For AEAD-algoritms, the tag / MAC is returned by openssl_encrypt...
116
            : $this->hash($iv, $value);
117
 
118
        $json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES);
119
 
120
        if (json_last_error() !== JSON_ERROR_NONE) {
121
            throw new EncryptException('Could not encrypt the data.');
122
        }
123
 
124
        return base64_encode($json);
125
    }
126
 
127
    /**
128
     * Encrypt a string without serialization.
129
     *
130
     * @param  string  $value
131
     * @return string
132
     *
133
     * @throws \Illuminate\Contracts\Encryption\EncryptException
134
     */
135
    public function encryptString($value)
136
    {
137
        return $this->encrypt($value, false);
138
    }
139
 
140
    /**
141
     * Decrypt the given value.
142
     *
143
     * @param  string  $payload
144
     * @param  bool  $unserialize
145
     * @return mixed
146
     *
147
     * @throws \Illuminate\Contracts\Encryption\DecryptException
148
     */
149
    public function decrypt($payload, $unserialize = true)
150
    {
151
        $payload = $this->getJsonPayload($payload);
152
 
153
        $iv = base64_decode($payload['iv']);
154
 
155
        $this->ensureTagIsValid(
156
            $tag = empty($payload['tag']) ? null : base64_decode($payload['tag'])
157
        );
158
 
159
        // Here we will decrypt the value. If we are able to successfully decrypt it
160
        // we will then unserialize it and return it out to the caller. If we are
161
        // unable to decrypt this value we will throw out an exception message.
162
        $decrypted = \openssl_decrypt(
163
            $payload['value'], strtolower($this->cipher), $this->key, 0, $iv, $tag ?? ''
164
        );
165
 
166
        if ($decrypted === false) {
167
            throw new DecryptException('Could not decrypt the data.');
168
        }
169
 
170
        return $unserialize ? unserialize($decrypted) : $decrypted;
171
    }
172
 
173
    /**
174
     * Decrypt the given string without unserialization.
175
     *
176
     * @param  string  $payload
177
     * @return string
178
     *
179
     * @throws \Illuminate\Contracts\Encryption\DecryptException
180
     */
181
    public function decryptString($payload)
182
    {
183
        return $this->decrypt($payload, false);
184
    }
185
 
186
    /**
187
     * Create a MAC for the given value.
188
     *
189
     * @param  string  $iv
190
     * @param  mixed  $value
191
     * @return string
192
     */
193
    protected function hash($iv, $value)
194
    {
195
        return hash_hmac('sha256', $iv.$value, $this->key);
196
    }
197
 
198
    /**
199
     * Get the JSON array from the given payload.
200
     *
201
     * @param  string  $payload
202
     * @return array
203
     *
204
     * @throws \Illuminate\Contracts\Encryption\DecryptException
205
     */
206
    protected function getJsonPayload($payload)
207
    {
208
        $payload = json_decode(base64_decode($payload), true);
209
 
210
        // If the payload is not valid JSON or does not have the proper keys set we will
211
        // assume it is invalid and bail out of the routine since we will not be able
212
        // to decrypt the given value. We'll also check the MAC for this encryption.
213
        if (! $this->validPayload($payload)) {
214
            throw new DecryptException('The payload is invalid.');
215
        }
216
 
217
        if (! self::$supportedCiphers[strtolower($this->cipher)]['aead'] && ! $this->validMac($payload)) {
218
            throw new DecryptException('The MAC is invalid.');
219
        }
220
 
221
        return $payload;
222
    }
223
 
224
    /**
225
     * Verify that the encryption payload is valid.
226
     *
227
     * @param  mixed  $payload
228
     * @return bool
229
     */
230
    protected function validPayload($payload)
231
    {
232
        if (! is_array($payload)) {
233
            return false;
234
        }
235
 
236
        foreach (['iv', 'value', 'mac'] as $item) {
237
            if (! isset($payload[$item]) || ! is_string($payload[$item])) {
238
                return false;
239
            }
240
        }
241
 
242
        if (isset($payload['tag']) && ! is_string($payload['tag'])) {
243
            return false;
244
        }
245
 
246
        return strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length(strtolower($this->cipher));
247
    }
248
 
249
    /**
250
     * Determine if the MAC for the given payload is valid.
251
     *
252
     * @param  array  $payload
253
     * @return bool
254
     */
255
    protected function validMac(array $payload)
256
    {
257
        return hash_equals(
258
            $this->hash($payload['iv'], $payload['value']), $payload['mac']
259
        );
260
    }
261
 
262
    /**
263
     * Ensure the given tag is a valid tag given the selected cipher.
264
     *
265
     * @param  string  $tag
266
     * @return void
267
     */
268
    protected function ensureTagIsValid($tag)
269
    {
270
        if (self::$supportedCiphers[strtolower($this->cipher)]['aead'] && strlen($tag) !== 16) {
271
            throw new DecryptException('Could not decrypt the data.');
272
        }
273
 
274
        if (! self::$supportedCiphers[strtolower($this->cipher)]['aead'] && is_string($tag)) {
275
            throw new DecryptException('Unable to use tag because the cipher algorithm does not support AEAD.');
276
        }
277
    }
278
 
279
    /**
280
     * Get the encryption key that the encrypter is currently using.
281
     *
282
     * @return string
283
     */
284
    public function getKey()
285
    {
286
        return $this->key;
287
    }
288
}