EncryptionHelper::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 11
rs 10
1
<?php
2
3
namespace BiffBangPow\SSMonitor\Client\Helper;
4
5
use SilverStripe\Core\Config\Configurable;
6
use SilverStripe\Core\Environment;
7
8
class EncryptionHelper
9
{
10
11
    use Configurable;
12
13
    /**
14
     * @config
15
     * @var int $hashlength
16
     */
17
    private static int $hashlength = 50;
18
19
    /**
20
     * @var string $secret
21
     */
22
    private string $secret;
23
24
    /**
25
     * @var string $salt
26
     */
27
    private string $salt;
28
29
30
    public function __construct()
31
    {
32
        $encSecret = Environment::getEnv('MONITORING_ENC_SECRET');
33
        $encSalt = Environment::getEnv('MONITORING_ENC_SALT');
34
35
        if (!$encSalt || !$encSecret) {
36
            throw new \Exception("Missing encryption keys in environment");
37
        }
38
39
        $this->setSecret($encSecret);
40
        $this->setSalt($encSalt);
41
    }
42
43
44
    public function encrypt($plaintext)
45
    {
46
47
        $secret = $this->getSecret();
48
49
        // Create a 32bit password
50
        $key = $this->create_32bit_password($secret);
51
52
        // Create a nonce: a piece of non-secret unique data that is used to randomize the cipher (safety against replay attack).
53
        // The nonce should be stored or shared along with the ciphertext, because the nonce needs to be reused with the same key.
54
        // In this class the nonce is shared with the ciphertext.
55
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
56
57
        // Encrypted
58
        $ciphertext = bin2hex(
59
            sodium_crypto_secretbox($plaintext, $nonce, $key)
60
        );
61
62
        // Hex nonce (in order to send together with ciphertext)
63
        $nonce_hex = bin2hex($nonce);
64
65
        // Create hash from ciphertext+nonce
66
        // It is not necessary, but just an extra layer of defense:
67
        // - more difficult to manipulate the string
68
        // - a nonce is always 48 characters. Because of a trailing hash (of unkown length), the nonce cannot be identified easily.
69
        //   (a nonce does not have to be secret, this is just an extra precaution)
70
        $hash = $this->create_hash($ciphertext . $nonce_hex);
71
72
        // Return ciphertext + nonce + hash
73
        return $ciphertext . $nonce_hex . $hash;
74
    }
75
76
77
    public function decrypt($ciphertext)
78
    {
79
80
        $secret = $this->getSecret();
81
82
        // Create a 32bit password
83
        $key = $this->create_32bit_password($secret);
84
85
        //Get hash
86
        $hash = substr($ciphertext, -self::$hashlength);
87
88
        //Get ciphertext + nonce (remove trailing hash)
89
        $ciphertext = substr($ciphertext, 0, -self::$hashlength);
90
91
        //Re-create hash
92
        $hash_on_the_fly = $this->create_hash($ciphertext);
93
94
        //Check if hash is correct
95
        if ($hash !== $hash_on_the_fly) {
96
            //Do proper error handling
97
            return "error";
98
        } else {
99
            // Get nonce (last 48 chars of string)
100
            $nonce_hex = substr($ciphertext, -48);
101
102
            // Get ciphertext (remove nonce)
103
            $ciphertext = substr($ciphertext, 0, -48);
104
105
            // Bin nonce
106
            $nonce = hex2bin($nonce_hex);
107
108
            // Decrypted
109
            $plaintext = sodium_crypto_secretbox_open(
110
                hex2bin($ciphertext), $nonce, $key
111
            );
112
113
            return $plaintext;
114
        }
115
    }
116
117
118
    private function create_32bit_password($secret)
119
    {
120
        $salt = $this->getSalt();
121
122
        //Openlib needs a 32bit key for encryption
123
        return substr(bin2hex(sodium_crypto_generichash($secret . $salt)), 0, 32);
124
    }
125
126
127
    private function create_hash($ciphertext_and_nonce)
128
    {
129
        $hashlength = $this->config()->get('hashlength');
130
        return substr(bin2hex(sodium_crypto_generichash($ciphertext_and_nonce)), 0, $hashlength);
131
    }
132
133
    /**
134
     * @return mixed
135
     */
136
    public function getSecret()
137
    {
138
        return $this->secret;
139
    }
140
141
    /**
142
     * @param mixed $secret
143
     */
144
    public function setSecret($secret): void
145
    {
146
        $this->secret = $secret;
147
    }
148
149
    /**
150
     * @return mixed
151
     */
152
    public function getSalt()
153
    {
154
        return $this->salt;
155
    }
156
157
    /**
158
     * @param mixed $salt
159
     */
160
    public function setSalt($salt): void
161
    {
162
        $this->salt = $salt;
163
    }
164
}
165