Passed
Branch wip_sessions (2e0cc8)
by Nils
04:59
created

SecureHandler::decrypt()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 16
nc 2
nop 2
dl 0
loc 22
rs 9.7333
c 1
b 0
f 0
1
<?php
2
/**
3
 * Encrypt PHP session data for the internal PHP save handlers
4
 *
5
 * The encryption is built using OpenSSL extension with AES-256-CBC and the
6
 * authentication is provided using HMAC with SHA256.
7
 *
8
 * @author    Enrico Zimuel ([email protected])
9
 * @copyright MIT License
10
 */
11
namespace PHPSecureSession;
12
13
use SessionHandler;
14
15
class SecureHandler extends SessionHandler
16
{
17
    /**
18
     * Encryption and authentication key
19
     * @var string
20
     */
21
    protected $key;
22
23
    /**
24
     * Constructor
25
     */
26
    public function __construct()
27
    {
28
        if (! extension_loaded('openssl')) {
29
            throw new \RuntimeException(sprintf(
30
                "You need the OpenSSL extension to use %s",
31
                __CLASS__
32
            ));
33
        }
34
        if (! extension_loaded('mbstring')) {
35
            throw new \RuntimeException(sprintf(
36
                "You need the Multibytes extension to use %s",
37
                __CLASS__
38
            ));
39
        }
40
    }
41
42
    /**
43
     * Open the session
44
     *
45
     * @param string $save_path
46
     * @param string $session_name
47
     */
48
    public function open($save_path, $session_name): bool
49
    {
50
        $this->key = $this->getKey('KEY_' . $session_name);
51
        return parent::open($save_path, $session_name);
52
    }
53
54
    /**
55
     * Read from session and decrypt
56
     *
57
     * @param string $id
58
     */
59
    public function read($id): string
60
    {
61
        $data = parent::read($id);
62
        return empty($data) ? '' : $this->decrypt($data, $this->key);
63
    }
64
65
    /**
66
     * Encrypt the data and write into the session
67
     *
68
     * @param string $id
69
     * @param string $data
70
     */
71
    public function write($id, $data): bool
72
    {
73
        return parent::write($id, $this->encrypt($data, $this->key));
74
    }
75
76
    /**
77
     * Encrypt and authenticate
78
     *
79
     * @param string $data
80
     * @param string $key
81
     * @return string
82
     */
83
    protected function encrypt($data, $key)
84
    {
85
        $iv = random_bytes(16); // AES block size in CBC mode
86
        // Encryption
87
        $ciphertext = openssl_encrypt(
88
            $data,
89
            'AES-256-CBC',
90
            mb_substr($key, 0, 32, '8bit'),
91
            OPENSSL_RAW_DATA,
92
            $iv
93
        );
94
        // Authentication
95
        $hmac = hash_hmac(
96
            'SHA256',
97
            $iv . $ciphertext,
98
            mb_substr($key, 32, null, '8bit'),
99
            true
100
        );
101
        return $hmac . $iv . $ciphertext;
102
    }
103
104
    /**
105
     * Authenticate and decrypt
106
     *
107
     * @param string $data
108
     * @param string $key
109
     * @return string
110
     */
111
    protected function decrypt($data, $key)
112
    {
113
        $hmac       = mb_substr($data, 0, 32, '8bit');
114
        $iv         = mb_substr($data, 32, 16, '8bit');
115
        $ciphertext = mb_substr($data, 48, null, '8bit');
116
        // Authentication
117
        $hmacNew = hash_hmac(
118
            'SHA256',
119
            $iv . $ciphertext,
120
            mb_substr($key, 32, null, '8bit'),
121
            true
122
        );
123
        if (! hash_equals($hmac, $hmacNew)) {
124
            throw new Exception\AuthenticationFailedException('Authentication failed');
125
        }
126
        // Decrypt
127
        return openssl_decrypt(
128
            $ciphertext,
129
            'AES-256-CBC',
130
            mb_substr($key, 0, 32, '8bit'),
131
            OPENSSL_RAW_DATA,
132
            $iv
133
        );
134
    }
135
136
    /**
137
     * Get the encryption and authentication keys from cookie
138
     *
139
     * @param string $name
140
     * @return string
141
     */
142
    protected function getKey($name)
143
    {
144
        if (empty($_COOKIE[$name])) {
145
            $key         = random_bytes(64); // 32 for encryption and 32 for authentication
146
            $cookieParam = session_get_cookie_params();
147
            $encKey      = base64_encode($key);
148
            // if session cookie lifetime > 0 then add to current time
149
            // otherwise leave it as zero, honoring zero's special meaning
150
            // expire at browser close.
151
            $arr_cookie_options = array (
152
                'expires' => ($cookieParam['lifetime'] > 0) ? time() + $cookieParam['lifetime'] : 0,
153
                'path' => '/',
154
                'secure' => true,
155
                'httponly' => true,
156
                'samesite' => 'Lax' // None || Lax  || Strict
157
            );
158
            setcookie($name, $encKey, $arr_cookie_options);
159
            $_COOKIE[$name] = $encKey;
160
        } else {
161
            $key = base64_decode($_COOKIE[$name]);
162
        }
163
        return $key;
164
    }
165
}