Passed
Push — master ( 45a3bb...2cf7c0 )
by Thomas
03:37
created

EncryptHelper::getProviderWithKey()   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
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace LeKoala\Encrypt;
4
5
use Exception;
6
use SilverStripe\Assets\File;
7
use ParagonIE\ConstantTime\Hex;
8
use SilverStripe\Core\Environment;
9
use SilverStripe\Core\Config\Config;
10
use ParagonIE\CipherSweet\CipherSweet;
11
use ParagonIE\CipherSweet\Backend\ModernCrypto;
12
use ParagonIE\CipherSweet\Contract\BackendInterface;
13
use ParagonIE\CipherSweet\KeyProvider\StringProvider;
14
use SilverStripe\ORM\FieldType\DBHTMLText;
15
use SilverStripe\ORM\FieldType\DBText;
16
use SilverStripe\ORM\FieldType\DBVarchar;
17
18
/**
19
 * @link https://ciphersweet.paragonie.com/php
20
 * @link https://paragonie.com/blog/2017/05/building-searchable-encrypted-databases-with-php-and-sql
21
 * @link https://paragonie.com/book/pecl-libsodium/read/09-recipes.md
22
 */
23
class EncryptHelper
24
{
25
    /**
26
     * @var CipherSweet
27
     */
28
    protected static $ciphersweet;
29
30
    /**
31
     * @var array
32
     */
33
    protected static $field_cache = [];
34
35
    /**
36
     * Attempting to pass a key of an invalid size (i.e. not 256-bit) will result in a CryptoOperationException being thrown.
37
     * The recommended way to generate a key is to use this method
38
     *
39
     * @return string Something like 4e1c44f87b4cdf21808762970b356891db180a9dd9850e7baf2a79ff3ab8a2fc
40
     */
41
    public static function generateKey()
42
    {
43
        return Hex::encode(random_bytes(32));
44
    }
45
46
    /**
47
     * Get app encryption key
48
     * Encryption key should be provided in your $_ENV or .env file
49
     *
50
     * @return string
51
     */
52
    public static function getKey()
53
    {
54
        $key = Environment::getEnv('ENCRYPTION_KEY');
55
        if (!$key) {
56
            $key = self::generateKey();
57
            throw new Exception("Please define an ENCRYPTION_KEY in your environment. You can use this one: $key");
58
        }
59
        return $key;
60
    }
61
62
    /**
63
     * @return StringProvider
64
     */
65
    public static function getProviderWithKey()
66
    {
67
        return new StringProvider(
68
            self::getKey()
69
        );
70
    }
71
72
    /**
73
     * @return CipherSweet
74
     */
75
    public static function getCipherSweet()
76
    {
77
        if (self::$ciphersweet) {
78
            return self::$ciphersweet;
79
        }
80
        $provider = self::getProviderWithKey();
81
        $backend = new ModernCrypto();
82
        self::$ciphersweet = new CipherSweet($provider, $backend);
83
        return self::$ciphersweet;
84
    }
85
86
    /**
87
     * @return BackendInterface
88
     */
89
    public static function getCipherSweetBackend()
90
    {
91
        return self::getCipherSweet()->getBackend();
92
    }
93
94
    /**
95
     * Check if a value is encrypted
96
     *
97
     * @param string $value
98
     * @return boolean
99
     */
100
    public static function isEncrypted($value)
101
    {
102
        if (strpos($value, 'nacl:') === 0) {
103
            return true;
104
        }
105
        return false;
106
    }
107
108
    /**
109
     * Check if a field is encrypted on a class
110
     * This relies on a field class starting with Encrypted
111
     *
112
     * @param string $class
113
     * @param string $field
114
     * @return boolean
115
     */
116
    public static function isEncryptedField($class, $field)
117
    {
118
        $key = $class . '_' . $field;
119
        if (isset(self::$field_cache[$key])) {
120
            return self::$field_cache[$key];
121
        }
122
123
        $fields = $class::config()->db;
124
125
        if (isset($fields[$field])) {
126
            $dbClass = $fields[$field];
127
            self::$field_cache[$key] = strpos($dbClass, 'Encrypted') !== false;
128
        } else {
129
            self::$field_cache[$key] = false;
130
        }
131
        return self::$field_cache[$key];
132
    }
133
134
    /**
135
     * A simple encryption
136
     * @param string $value
137
     * @return string
138
     */
139
    public static function encrypt($value)
140
    {
141
        // Do not encrypt twice
142
        $encryption = self::isEncrypted($value);
143
        if ($encryption) {
144
            return $value;
145
        }
146
        $provider = self::getProviderWithKey();
147
        $backend = self::getCipherSweetBackend();
148
        return $backend->encrypt($value, $provider->getSymmetricKey());
149
    }
150
151
    /**
152
     * A simple decryption
153
     * @param string $value
154
     * @return string
155
     */
156
    public static function decrypt($value)
157
    {
158
        // Only decrypt what we can decrypt
159
        if (!self::isEncrypted($value)) {
160
            return $value;
161
        }
162
        $provider = self::getProviderWithKey();
163
        $backend = self::getCipherSweetBackend();
164
        return $backend->decrypt($value, $provider->getSymmetricKey());
165
    }
166
167
    /**
168
     * Return a map of fields with their encrypted counterpart
169
     *
170
     * @return array
171
     */
172
    public static function mapEncryptionDBField()
173
    {
174
        return [
175
            DBHTMLText::class => EncryptedDBHTMLText::class,
176
            DBText::class => EncryptedDBText::class,
177
            DBVarchar::class => EncryptedDBVarchar::class,
178
        ];
179
    }
180
181
    /**
182
     * Send a decrypted file
183
     *
184
     * @param File $file
185
     * @return void
186
     */
187
    public static function sendEncryptedFile(File $file)
188
    {
189
        header('Content-disposition: attachment; filename="' . basename($file->getFilename()) . '"');
190
        header('Content-type: application/octetstream');
191
        header('Pragma: no-cache');
192
        header('Expires: 0');
193
        $file->sendDecryptedFile();
194
    }
195
}
196