Encrypter::validPayload()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Stidges\LaravelSodiumEncryption;
4
5
use Throwable;
6
use RuntimeException;
7
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
8
use Illuminate\Contracts\Encryption\DecryptException;
9
use Illuminate\Contracts\Encryption\EncryptException;
10
11
class Encrypter implements EncrypterContract
12
{
13
    /**
14
     * The encryption key.
15
     *
16
     * @var string
17
     */
18
    protected $key;
19
20
    /**
21
     * Create a new encrypter instance.
22
     *
23
     * @param  string  $key
24
     * @return void
25
     *
26
     * @throws \RuntimeException
27
     */
28 21
    public function __construct($key)
29
    {
30 21
        $key = (string) $key;
31
32 21
        if (! static::supported($key)) {
33 6
            throw new RuntimeException('Incorrect key provided.');
34
        }
35
36 15
        $this->key = $key;
37 15
    }
38
39
    /**
40
     * Determine if the given key is valid.
41
     *
42
     * @param  string  $key
43
     * @return bool
44
     */
45 21
    public static function supported($key)
46
    {
47 21
        return mb_strlen($key, '8bit') === SODIUM_CRYPTO_SECRETBOX_KEYBYTES;
48
    }
49
50
    /**
51
     * Create a new encryption key.
52
     *
53
     * @return string
54
     */
55 3
    public static function generateKey()
56
    {
57 3
        return \sodium_crypto_secretbox_keygen();
58
    }
59
60
    /**
61
     * Encrypt the given value.
62
     *
63
     * @param  mixed  $value
64
     * @param  bool  $serialize
65
     * @return mixed
66
     *
67
     * @throws \Illuminate\Contracts\Encryption\EncryptException
68
     */
69 15
    public function encrypt($value, $serialize = true)
70
    {
71 15
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
72
73
        try {
74 15
            $value = \sodium_crypto_secretbox($serialize ? serialize($value) : $value, $nonce, $this->key);
75
        } catch (Throwable $e) {
76
            throw new EncryptException($e->getMessage(), $e->getCode(), $e);
77
        }
78
79 15
        $mac = \sodium_crypto_auth($value, $this->key);
80
81 15
        $json = json_encode(array_map('base64_encode', compact('nonce', 'value', 'mac')));
82
83 15
        if (json_last_error() !== JSON_ERROR_NONE) {
84
            throw new EncryptException('Could not encrypt the data.');
85
        }
86
87 15
        return base64_encode($json);
88
    }
89
90
    /**
91
     * Encrypt a string without serialization.
92
     *
93
     * @param  string  $value
94
     * @return string
95
     *
96
     * @throws \Illuminate\Contracts\Encryption\EncryptException
97
     */
98 3
    public function encryptString($value)
99
    {
100 3
        return $this->encrypt($value, false);
101
    }
102
103
    /**
104
     * Decrypt the given value.
105
     *
106
     * @param  mixed  $payload
107
     * @param  bool  $unserialize
108
     * @return mixed
109
     *
110
     * @throws \Illuminate\Contracts\Encryption\DecryptException
111
     */
112 15
    public function decrypt($payload, $unserialize = true)
113
    {
114 15
        $payload = $this->getJsonPayload($payload);
115
116 6
        $decrypted = \sodium_crypto_secretbox_open($payload['value'], $payload['nonce'], $this->key);
117
118 6
        if ($decrypted === false) {
119
            throw new DecryptException('Could not decrypt the data.');
120
        }
121
122 6
        return $unserialize ? unserialize($decrypted) : $decrypted;
123
    }
124
125
    /**
126
     * Decrypt the given string without unserialization.
127
     *
128
     * @param  string  $payload
129
     * @return string
130
     *
131
     * @throws \Illuminate\Contracts\Encryption\DecryptException
132
     */
133 3
    public function decryptString($payload)
134
    {
135 3
        return $this->decrypt($payload, false);
136
    }
137
138
    /**
139
     * Get the JSON array from the given payload.
140
     *
141
     * @param  string  $payload
142
     * @return array
143
     *
144
     * @throws \Illuminate\Contracts\Encryption\DecryptException
145
     */
146 15
    protected function getJsonPayload($payload)
147
    {
148 15
        $payload = json_decode(base64_decode($payload), true);
149
150 15
        if (! $this->validPayload($payload)) {
151 3
            throw new DecryptException('The payload is invalid.');
152
        }
153
154 12
        $payload = $this->decodePayloadValues($payload);
155
156 12
        if (! $this->validMac($payload)) {
157 6
            throw new DecryptException('The MAC is invalid.');
158
        }
159
160 6
        return $payload;
161
    }
162
163
    /**
164
     * Verify that the encryption payload is valid.
165
     *
166
     * @param  mixed  $payload
167
     * @return bool
168
     */
169 15
    protected function validPayload($payload)
170
    {
171 15
        return is_array($payload) && isset($payload['nonce'], $payload['value'], $payload['mac']);
172
    }
173
174
    /**
175
     * Decode the base64 encoded values of the payload.
176
     *
177
     * @param  array  $payload
178
     * @return array
179
     */
180 12
    protected function decodePayloadValues(array $payload)
181
    {
182
        return array_map(function ($value) {
183 12
            return base64_decode($value, true);
184 12
        }, $payload);
185
    }
186
187
    /**
188
     * Determine if the MAC for the given payload is valid.
189
     *
190
     * @param  array  $payload
191
     * @return bool
192
     */
193 12
    protected function validMac(array $payload)
194
    {
195 12
        return \sodium_crypto_auth_verify($payload['mac'], $payload['value'], $this->key);
196
    }
197
198
    /**
199
     * Get the encryption key.
200
     *
201
     * @return string
202
     */
203
    public function getKey()
204
    {
205
        return $this->key;
206
    }
207
}
208