Passed
Push — master ( 8995ac...1b609d )
by Nils
06:33
created

decrypt_with_session_key()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 28
nc 14
nop 2
dl 0
loc 46
rs 8.8497
c 1
b 0
f 0
1
<?php
2
/**
3
 * Teampass - a collaborative passwords manager.
4
 * ---
5
 * This file is part of the TeamPass project.
6
 *
7
 * TeamPass is free software: you can redistribute it and/or modify it
8
 * under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, version 3 of the License.
10
 *
11
 * TeamPass is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 *
19
 * Certain components of this file may be under different licenses. For
20
 * details, see the `licenses` directory or individual file headers.
21
 * ---
22
 * @version    API
23
 *
24
 * @file      encryption_utils.php
25
 * @author    Nils Laumaillé ([email protected])
26
 * @copyright 2009-2025 Teampass.net
27
 * @license   GPL-3.0
28
 * @see       https://www.teampass.net
29
 */
30
31
/**
32
 * Encrypt data using AES-256-GCM with a session key
33
 *
34
 * This function encrypts sensitive data (like decrypted private keys) using
35
 * AES-256-GCM which provides both encryption and authentication. The encrypted
36
 * data can only be decrypted with the same session key.
37
 *
38
 * Security features:
39
 * - AES-256-GCM: Industry standard authenticated encryption
40
 * - Random nonce for each encryption (prevents pattern detection)
41
 * - Authentication tag to detect tampering
42
 *
43
 * @param string $data Data to encrypt (e.g., decrypted private key)
44
 * @param string $key Session key (must be 32 bytes / 256 bits)
45
 * @return string|false Encrypted data in base64 format (nonce.tag.ciphertext) or false on error
46
 */
47
function encrypt_with_session_key(string $data, string $key)
48
{
49
    if (strlen($key) !== 32) {
50
        error_log('[API] encrypt_with_session_key: Invalid key length. Expected 32 bytes, got ' . strlen($key));
51
        return false;
52
    }
53
54
    try {
55
        // Generate a random nonce (96 bits is recommended for GCM)
56
        $nonce = random_bytes(12);
57
58
        $tag = '';
59
60
        // Encrypt using AES-256-GCM
61
        $ciphertext = openssl_encrypt(
62
            $data,
63
            'aes-256-gcm',
64
            $key,
65
            OPENSSL_RAW_DATA,
66
            $nonce,
67
            $tag
68
        );
69
70
        if ($ciphertext === false) {
71
            error_log('[API] encrypt_with_session_key: OpenSSL encryption failed');
72
            return false;
73
        }
74
75
        // Combine nonce + tag + ciphertext and encode in base64
76
        // Format: [12 bytes nonce][16 bytes tag][variable ciphertext]
77
        return base64_encode($nonce . $tag . $ciphertext);
78
79
    } catch (Exception $e) {
80
        error_log('[API] encrypt_with_session_key: Exception - ' . $e->getMessage());
81
        return false;
82
    }
83
}
84
85
/**
86
 * Decrypt data using AES-256-GCM with a session key
87
 *
88
 * This function decrypts data that was encrypted with encrypt_with_session_key().
89
 * The authentication tag is automatically verified, ensuring data integrity.
90
 *
91
 * @param string $encryptedData Encrypted data in base64 format (from encrypt_with_session_key)
92
 * @param string $key Session key (must be 32 bytes / 256 bits)
93
 * @return string|false Decrypted data or false on error/tampering
94
 */
95
function decrypt_with_session_key(string $encryptedData, string $key)
96
{
97
    if (strlen($key) !== 32) {
98
        error_log('[API] decrypt_with_session_key: Invalid key length. Expected 32 bytes, got ' . strlen($key));
99
        return false;
100
    }
101
102
    try {
103
        // Decode from base64
104
        $decoded = base64_decode($encryptedData, true);
105
106
        if ($decoded === false) {
107
            error_log('[API] decrypt_with_session_key: Invalid base64 data');
108
            return false;
109
        }
110
111
        // Extract components: nonce (12 bytes) + tag (16 bytes) + ciphertext (rest)
112
        if (strlen($decoded) < 28) {
113
            error_log('[API] decrypt_with_session_key: Encrypted data too short');
114
            return false;
115
        }
116
117
        $nonce = substr($decoded, 0, 12);
118
        $tag = substr($decoded, 12, 16);
119
        $ciphertext = substr($decoded, 28);
120
121
        // Decrypt using AES-256-GCM
122
        $plaintext = openssl_decrypt(
123
            $ciphertext,
124
            'aes-256-gcm',
125
            $key,
126
            OPENSSL_RAW_DATA,
127
            $nonce,
128
            $tag
129
        );
130
131
        if ($plaintext === false) {
132
            error_log('[API] decrypt_with_session_key: OpenSSL decryption failed (wrong key or tampered data)');
133
            return false;
134
        }
135
136
        return $plaintext;
137
138
    } catch (Exception $e) {
139
        error_log('[API] decrypt_with_session_key: Exception - ' . $e->getMessage());
140
        return false;
141
    }
142
}
143