Completed
Push — master ( 206c33...9e77c2 )
by Oscar
05:44
created

CryptTrait::hash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
namespace Psr7Middlewares\Utils;
4
5
use RuntimeException;
6
7
/**
8
 * Trait used by all middlewares that needs encrypt/decrypt functions
9
 * Most of code is from https://github.com/illuminate/encryption.
10
 */
11
trait CryptTrait
12
{
13
    protected $cipher;
14
    protected $key;
15
16
    /**
17
     * Set the key and cipher used by the crypt.
18
     * 
19
     * @param string $key
20
     * @param string $cipher
21
     *
22
     * @return self
23
     */
24
    public function crypt($key, $cipher = 'AES-128-CBC')
25
    {
26
        $length = mb_strlen($key, '8bit');
27
28
        if (!(($cipher === 'AES-128-CBC' && $length === 16) || ($cipher === 'AES-256-CBC' && $length === 32))) {
29
            throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.');
30
        }
31
32
        $this->key = $key;
33
        $this->cipher = $cipher;
34
35
        return $this;
36
    }
37
38
    /**
39
     * Generates the default cipher and key.
40
     */
41
    protected function generateCryptKey()
42
    {
43
        $this->crypt(substr(md5(__DIR__), 0, 16));
44
    }
45
46
    /**
47
     * Encrypt the given value.
48
     *
49
     * @param string $value
50
     * 
51
     * @return string
52
     */
53
    protected function encrypt($value)
54
    {
55
        $iv = random_bytes(16);
0 ignored issues
show
Unused Code introduced by
The call to random_bytes() has too many arguments starting with 16.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
56
        $value = openssl_encrypt(serialize($value), $this->cipher, $this->key, 0, $iv);
57
58
        if ($value === false) {
59
            throw new RuntimeException('Could not encrypt the data.');
60
        }
61
62
        $iv = base64_encode($iv);
63
        $mac = hash_hmac('sha256', $iv.$value, $this->key);
64
65
        return base64_encode(json_encode(compact('iv', 'value', 'mac')));
66
    }
67
68
    /**
69
     * Decrypt the given value.
70
     *
71
     * @param string $payload
72
     * 
73
     * @return string
74
     */
75
    protected function decrypt($payload)
76
    {
77
        $payload = json_decode(base64_decode($payload), true);
78
79
        if (!$payload || $this->invalidPayload($payload)) {
80
            throw new RuntimeException('The payload is invalid.');
81
        }
82
83
        if (!$this->validMac($payload)) {
84
            throw new RuntimeException('The MAC is invalid.');
85
        }
86
87
        $iv = base64_decode($payload['iv']);
88
        $decrypted = openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv);
89
90
        if ($decrypted === false) {
91
            throw new RuntimeException('Could not decrypt the data.');
92
        }
93
94
        return unserialize($decrypted);
95
    }
96
97
    /**
98
     * Create a MAC for the given value.
99
     *
100
     * @param string $iv
101
     * @param string $value
102
     * 
103
     * @return string
104
     */
105
    protected function hash($iv, $value)
106
    {
107
        return hash_hmac('sha256', $iv.$value, $this->key);
108
    }
109
110
    /**
111
     * Verify that the encryption payload is valid.
112
     *
113
     * @param array|mixed $data
114
     * 
115
     * @return bool
116
     */
117
    protected function invalidPayload($data)
118
    {
119
        return !is_array($data) || !isset($data['iv']) || !isset($data['value']) || !isset($data['mac']);
120
    }
121
122
    /**
123
     * Determine if the MAC for the given payload is valid.
124
     *
125
     * @param array $payload
126
     * 
127
     * @throws \RuntimeException
128
     *
129
     * @return bool
130
     */
131
    protected function validMac(array $payload)
132
    {
133
        $bytes = random_bytes(16);
0 ignored issues
show
Unused Code introduced by
The call to random_bytes() has too many arguments starting with 16.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
134
        $calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true);
135
136
        return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac);
137
    }
138
}
139