Test Setup Failed
Push — master ( 1f658a...0bb757 )
by Ion
02:42
created

BaseModel::generate_mysql_aes_key()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A BaseModel::aesDecrypt() 0 7 2
1
<?php
2
3
namespace IonGhitun\MysqlEncryption\Models;
4
5
use Faker\Factory;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Arr;
8
9
/**
10
 * Class BaseModel
11
 *
12
 * @package IonGhitun\MysqlEncryption\Models
13
 */
14
class BaseModel extends Model
15
{
16
    /**
17
     * Elements should be the names of the fields
18
     *
19
     * @var array
20
     */
21
    protected $encrypted = [];
22
23
    /**
24
     * Elements should be pairs with key column name and value and array with type and optional parameters
25
     *
26
     * @example ['age' => ['randomDigit']]
27
     * @example ['age' => ['numberBetween', '18','50']]
28
     *
29
     * Available types: https://github.com/fzaninotto/Faker
30
     *
31
     * @var array
32
     */
33
    protected $anonymizable = [];
34
35
    /**
36
     * Get model attribute
37
     *
38
     * @param string $key
39
     *
40
     * @return mixed
41
     */
42
    public function getAttribute($key)
43
    {
44
        $value = parent::getAttribute($key);
45
46
        if (array_key_exists($key, $this->relations) || method_exists($this, $key)) {
47
            return $value;
48
        } else {
49
            $value = parent::getAttribute($key);
50
        }
51
52
        if (in_array($key, $this->encrypted)) {
53
            $value = $this->aesDecrypt($value);
54
        }
55
56
        return $value;
57
    }
58
59
    /**
60
     * Descrypt value
61
     *
62
     * @param $val
63
     * @param string $cypher
64
     * @param bool $mySqlKey
65
     *
66
     * @return false|string
67
     */
68
    protected function aesDecrypt($val, $cypher = 'aes-128-ecb', $mySqlKey = true)
69
    {
70
        $secret = env('ENCRYPTION_KEY');
71
72
        $key = $mySqlKey ? $this->generateMysqlAesKey($secret) : $secret;
73
74
        return openssl_decrypt($val, $cypher, $key, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $options of openssl_decrypt(). ( Ignorable by Annotation )

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

74
        return openssl_decrypt($val, $cypher, $key, /** @scrutinizer ignore-type */ true);
Loading history...
75
    }
76
77
    /**
78
     * Generate encryption key
79
     *
80
     * @param $key
81
     *
82
     * @return string
83
     */
84
    protected function generateMysqlAesKey($key)
85
    {
86
        $generatedKey = str_repeat(chr(0), 16);
87
88
        for ($i = 0, $len = strlen($key); $i < $len; $i++) {
89
            $generatedKey[$i % 16] = $generatedKey[$i % 16] ^ $key[$i];
90
        }
91
        return $generatedKey;
92
    }
93
94
    /**
95
     * Set model attribute
96
     *
97
     * @param string $key
98
     * @param mixed $value
99
     *
100
     * @return Model
101
     */
102
    public function setAttribute($key, $value)
103
    {
104
        if (in_array($key, $this->encrypted)) {
105
            $value = $this->aesEncrypt($value);
106
        }
107
108
        return parent::setAttribute($key, $value);
109
    }
110
111
    /**
112
     * Encrypt value
113
     *
114
     * @param $val
115
     * @param string $cypher
116
     * @param bool $mySqlKey
117
     *
118
     * @return string
119
     */
120
    protected function aesEncrypt($val, $cypher = 'aes-128-ecb', $mySqlKey = true)
121
    {
122
        $secret = env('ENCRYPTION_KEY');
123
124
        $key = $mySqlKey ? $this->generateMysqlAesKey($secret) : $secret;
125
126
        return openssl_encrypt($val, $cypher, $key, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $options of openssl_encrypt(). ( Ignorable by Annotation )

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

126
        return openssl_encrypt($val, $cypher, $key, /** @scrutinizer ignore-type */ true);
Loading history...
127
    }
128
129
    /**
130
     * Get attributes array
131
     *
132
     * @return array
133
     */
134
    public function attributesToArray()
135
    {
136
        $attributes = parent::attributesToArray();
137
138
        foreach ($this->encrypted as $key) {
139
            if (isset($attributes[$key])) {
140
                $attributes[$key] = $this->aesDecrypt($attributes[$key]);
141
            }
142
        }
143
144
        return $attributes;
145
    }
146
147
    /**
148
     * Get original not encrypted
149
     *
150
     * @param null $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
151
     * @param null $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
152
     *
153
     * @return array|mixed|string
154
     */
155
    public function getOriginal($key = null, $default = null)
156
    {
157
        if (in_array($key, $this->encrypted)) {
158
            return $this->aesDecrypt(Arr::get($this->original, $key, $default));
159
        }
160
161
        return Arr::get($this->original, $key, $default);
162
    }
163
164
    /**
165
     * Get encrypted columns
166
     *
167
     * @return array
168
     */
169
    public function getEncrypted()
170
    {
171
        return $this->encrypted;
172
    }
173
174
    /**
175
     * Get anonymizable columns
176
     *
177
     * @return array
178
     */
179
    public function getAnonymizable()
180
    {
181
        return $this->anonymizable;
182
    }
183
184
    /**
185
     * where for encrypted columns
186
     *
187
     * @param $query
188
     * @param $field
189
     * @param $value
190
     *
191
     * @return mixed
192
     */
193
    public function scopeWhereEncrypted($query, $field, $value)
194
    {
195
        return $query->whereRaw('AES_DECRYPT(' . $field . ', "' . env("ENCRYPTION_KEY") . '") LIKE "' . $value . '" COLLATE utf8mb4_general_ci');
196
    }
197
198
    /**
199
     * orWhere for encrypted columns
200
     *
201
     * @param $query
202
     * @param $field
203
     * @param $value
204
     *
205
     * @return mixed
206
     */
207
    public function scopeOrWhereEncrypted($query, $field, $value)
208
    {
209
        return $query->orWhereRaw('AES_DECRYPT(' . $field . ', "' . env("ENCRYPTION_KEY") . '") LIKE "' . $value . '" COLLATE utf8mb4_general_ci');
210
    }
211
212
    /**
213
     * Anonymize model fields
214
     *
215
     * @param null $locale
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $locale is correct as it would always require null to be passed?
Loading history...
216
     */
217
    public function anonymize($locale = null)
218
    {
219
        $faker = Factory::create($locale ?? env('FAKER_LOCALE', Factory::DEFAULT_LOCALE));
220
221
        foreach ($this->anonymizable as $field => $type) {
222
            if (in_array($field, $this->attributes)) {
223
                $this->$field = call_user_func([$faker, $type[0]], array_slice($type, 1));
224
            }
225
        }
226
    }
227
}
228