fix calendar search edit dialog
[openemr.git] / library / crypto.php
blob7e52236b7c8571940390839f4df11fef6155e214
1 <?php
2 /**
3 * Crypto library.
5 * @package OpenEMR
6 * @link https://www.open-emr.org
7 * @author Ensoftek, Inc
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @copyright Copyright (c) 2015 Ensoftek, Inc
10 * @copyright Copyright (c) 2018-2019 Brady Miller <brady.g.miller@gmail.com>
11 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
15 /**
16 * Standard function to encrypt
18 * @param string $value This is the data to encrypt.
19 * @param string $customPassword If provide a password, then will derive keys from this.(and will not use the standard keys)
20 * @param string $keySource This is the source of the standard keys. Options are 'drive' and 'database'
23 function encryptStandard($value, $customPassword = null, $keySource = 'drive')
25 # This is the current encrypt/decrypt version
26 # (this will always be a three digit number that we will
27 # increment when update the encrypt/decrypt methodology
28 # which allows being able to maintain backward compatibility
29 # to decrypt values from prior versions)
30 # Remember to update cryptCheckStandard() when increment this.
31 $encryptionVersion = "005";
33 $encryptedValue = $encryptionVersion . coreEncrypt($value, $customPassword, $keySource, "five");
35 return $encryptedValue;
38 /**
39 * Standard function to decrypt
41 * @param string $value This is the data to encrypt.
42 * @param string $customPassword If provide a password, then will derive keys from this.(and will not use the standard keys)
43 * @param string $keySource This is the source of the standard keys. Options are 'drive' and 'database'
46 function decryptStandard($value, $customPassword = null, $keySource = 'drive')
48 if (empty($value)) {
49 return "";
52 # Collect the encrypt/decrypt version and remove it from the value
53 $encryptionVersion = intval(mb_substr($value, 0, 3, '8bit'));
54 $trimmedValue = mb_substr($value, 3, null, '8bit');
56 # Map the encrypt/decrypt version to the correct decryption function
57 if ($encryptionVersion == 5) {
58 return coreDecrypt($trimmedValue, $customPassword, $keySource, "five");
59 } else if ($encryptionVersion == 4) {
60 return coreDecrypt($trimmedValue, $customPassword, $keySource, "four");
61 } else if (($encryptionVersion == 2) || ($encryptionVersion == 3)) {
62 return aes256DecryptTwo($trimmedValue, $customPassword);
63 } else if ($encryptionVersion == 1) {
64 return aes256DecryptOne($trimmedValue, $customPassword);
65 } else {
66 error_log("OpenEMR Error : Decryption is not working because of unknown encrypt/decrypt version.");
67 return false;
71 /**
72 * Check if a crypt block is valid to use for the standard method
73 * (basically checks if correct values are used)
75 function cryptCheckStandard($value)
77 if (empty($value)) {
78 return false;
81 if (preg_match('/^00[1-5]/', $value)) {
82 return true;
83 } else {
84 return false;
88 /**
89 * Function to encrypt data
90 * Should not be called directly (only called by encryptStandard() function)
92 * @param string $sValue Raw data that will be encrypted.
93 * @param string $customPassword If null, then use standard keys. If provide a password, then will derive key from this.
94 * @param string $keySource This is the source of the keys. Options are 'drive' and 'database'
95 * @param string $keyNumber This is the key number/version.
96 * @return string returns the encrypted data.
98 function coreEncrypt($sValue, $customPassword = null, $keySource = 'drive', $keyNumber = "five")
100 if (!extension_loaded('openssl')) {
101 error_log("OpenEMR Error : Encryption is not working because missing openssl extension.");
104 if (empty($customPassword)) {
105 // Collect the encryption keys. If they do not exist, then create them
106 // The first key is for encryption. Then second key is for the HMAC hash
107 $sSecretKey = collectCryptoKey($keyNumber, "a", $keySource);
108 $sSecretKeyHmac = collectCryptoKey($keyNumber, "b", $keySource);
109 } else {
110 // customPassword mode, so turn the password into keys
111 $sSalt = produceRandomBytes(32);
112 if (empty($sSalt)) {
113 error_log('OpenEMR Error : Random Bytes error - exiting');
114 die();
116 $sPreKey = hash_pbkdf2('sha384', $customPassword, $sSalt, 100000, 32, true);
117 $sSecretKey = hash_hkdf('sha384', $sPreKey, 32, 'aes-256-encryption', $sSalt);
118 $sSecretKeyHmac = hash_hkdf('sha384', $sPreKey, 32, 'sha-384-authentication', $sSalt);
121 if (empty($sSecretKey) || empty($sSecretKeyHmac)) {
122 error_log("OpenEMR Error : Encryption is not working because key(s) is blank.");
125 $iv = produceRandomBytes(openssl_cipher_iv_length('aes-256-cbc'));
126 if (empty($iv)) {
127 error_log('OpenEMR Error : Random Bytes error - exiting');
128 die();
131 $processedValue = openssl_encrypt(
132 $sValue,
133 'aes-256-cbc',
134 $sSecretKey,
135 OPENSSL_RAW_DATA,
139 $hmacHash = hash_hmac('sha384', $iv.$processedValue, $sSecretKeyHmac, true);
141 if ($sValue != "" && ($processedValue == "" || $hmacHash == "")) {
142 error_log("OpenEMR Error : Encryption is not working.");
145 if (empty($customPassword)) {
146 // prepend the encrypted value with the $hmacHash and $iv
147 $completedValue = $hmacHash . $iv . $processedValue;
148 } else {
149 // customPassword mode, so prepend the encrypted value with the salts, $hmacHash and $iv
150 $completedValue = $sSalt . $hmacHash . $iv . $processedValue;
153 return base64_encode($completedValue);
158 * Function to decrypt data
159 * Should not be called directly (only called by decryptStandard() function)
161 * @param string $sValue Encrypted data that will be decrypted.
162 * @param string $customPassword If null, then use standard keys. If provide a password, then will derive key from this.
163 * @param string $keySource This is the source of the keys. Options are 'drive' and 'database'
164 * @param string $keyNumber This is the key number/version.
165 * @return string or false returns the decrypted data or false if failed.
167 function coreDecrypt($sValue, $customPassword = null, $keySource = 'drive', $keyNumber = "five")
169 if (!extension_loaded('openssl')) {
170 error_log("OpenEMR Error : Decryption is not working because missing openssl extension.");
171 return false;
174 $raw = base64_decode($sValue, true);
175 if ($raw === false) {
176 error_log("OpenEMR Error : Encryption did not work because illegal characters were noted in base64_encoded data.");
177 return false;
180 if (empty($customPassword)) {
181 // Collect the encryption keys.
182 // The first key is for encryption. Then second key is for the HMAC hash
183 $sSecretKey = collectCryptoKey($keyNumber, "a", $keySource);
184 $sSecretKeyHmac = collectCryptoKey($keyNumber, "b", $keySource);
185 } else {
186 // customPassword mode, so turn the password keys
187 // The first key is for encryption. Then second key is for the HMAC hash
188 // First need to collect the salt from $raw (and then remove it from $raw)
189 $sSalt = mb_substr($raw, 0, 32, '8bit');
190 $raw = mb_substr($raw, 32, null, '8bit');
191 // Now turn the password into keys
192 $sPreKey = hash_pbkdf2('sha384', $customPassword, $sSalt, 100000, 32, true);
193 $sSecretKey = hash_hkdf('sha384', $sPreKey, 32, 'aes-256-encryption', $sSalt);
194 $sSecretKeyHmac = hash_hkdf('sha384', $sPreKey, 32, 'sha-384-authentication', $sSalt);
197 if (empty($sSecretKey) || empty($sSecretKeyHmac)) {
198 error_log("OpenEMR Error : Encryption is not working because key(s) is blank.");
199 return false;
202 $ivLength = openssl_cipher_iv_length('aes-256-cbc');
203 $hmacHash = mb_substr($raw, 0, 48, '8bit');
204 $iv = mb_substr($raw, 48, $ivLength, '8bit');
205 $encrypted_data = mb_substr($raw, ($ivLength+48), null, '8bit');
207 $calculatedHmacHash = hash_hmac('sha384', $iv.$encrypted_data, $sSecretKeyHmac, true);
209 if (hash_equals($hmacHash, $calculatedHmacHash)) {
210 return openssl_decrypt(
211 $encrypted_data,
212 'aes-256-cbc',
213 $sSecretKey,
214 OPENSSL_RAW_DATA,
217 } else {
218 error_log("OpenEMR Error : Decryption failed authentication.");
224 * Function to AES256 decrypt a given string, version 2
226 * @param string $sValue Encrypted data that will be decrypted.
227 * @param string $customPassword If null, then use standard key. If provide a password, then will derive key from this.
228 * @return string or false returns the decrypted data or false if failed.
230 function aes256DecryptTwo($sValue, $customPassword = null)
232 if (!extension_loaded('openssl')) {
233 error_log("OpenEMR Error : Decryption is not working because missing openssl extension.");
234 return false;
237 if (empty($customPassword)) {
238 // Collect the encryption keys.
239 // The first key is for encryption. Then second key is for the HMAC hash
240 $sSecretKey = collectCryptoKey("two", "a");
241 $sSecretKeyHmac = collectCryptoKey("two", "b");
242 } else {
243 // Turn the password into a hash(note use binary) to use as the keys
244 $sSecretKey = hash("sha256", $customPassword, true);
245 $sSecretKeyHmac = $sSecretKey;
248 if (empty($sSecretKey) || empty($sSecretKeyHmac)) {
249 error_log("OpenEMR Error : Encryption is not working because key(s) is blank.");
250 return false;
253 $raw = base64_decode($sValue, true);
254 if ($raw === false) {
255 error_log("OpenEMR Error : Encryption did not work because illegal characters were noted in base64_encoded data.");
256 return false;
260 $ivLength = openssl_cipher_iv_length('aes-256-cbc');
261 $hmacHash = mb_substr($raw, 0, 32, '8bit');
262 $iv = mb_substr($raw, 32, $ivLength, '8bit');
263 $encrypted_data = mb_substr($raw, ($ivLength+32), null, '8bit');
265 $calculatedHmacHash = hash_hmac('sha256', $iv.$encrypted_data, $sSecretKeyHmac, true);
267 if (hash_equals($hmacHash, $calculatedHmacHash)) {
268 return openssl_decrypt(
269 $encrypted_data,
270 'aes-256-cbc',
271 $sSecretKey,
272 OPENSSL_RAW_DATA,
275 } else {
276 error_log("OpenEMR Error : Decryption failed authentication.");
282 * Function to AES256 decrypt a given string, version 1
284 * @param string $sValue Encrypted data that will be decrypted.
285 * @param string $customPassword If null, then use standard key. If provide a password, then will derive key from this.
286 * @return string returns the decrypted data.
288 function aes256DecryptOne($sValue, $customPassword = null)
290 if (!extension_loaded('openssl')) {
291 error_log("OpenEMR Error : Decryption is not working because missing openssl extension.");
294 if (empty($customPassword)) {
295 // Collect the key. If it does not exist, then create it
296 $sSecretKey = collectCryptoKey();
297 } else {
298 // Turn the password into a hash to use as the key
299 $sSecretKey = hash("sha256", $customPassword);
302 if (empty($sSecretKey)) {
303 error_log("OpenEMR Error : Encryption is not working.");
306 $raw = base64_decode($sValue);
308 $ivLength = openssl_cipher_iv_length('aes-256-cbc');
310 $iv = substr($raw, 0, $ivLength);
311 $encrypted_data = substr($raw, $ivLength);
313 return openssl_decrypt(
314 $encrypted_data,
315 'aes-256-cbc',
316 $sSecretKey,
317 OPENSSL_RAW_DATA,
322 // Function to decrypt a given string
323 // This specific function is only used for backward compatibility
324 function aes256Decrypt_mycrypt($sValue)
326 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
327 return rtrim(
328 mcrypt_decrypt(
329 MCRYPT_RIJNDAEL_256,
330 $sSecretKey,
331 base64_decode($sValue),
332 MCRYPT_MODE_ECB,
333 mcrypt_create_iv(
334 mcrypt_get_iv_size(
335 MCRYPT_RIJNDAEL_256,
336 MCRYPT_MODE_ECB
338 MCRYPT_RAND
341 "\0"
346 * Function to collect (and create, if needed) the standard keys
347 * This mechanism will allow easy migration to new keys/ciphers in the future while
348 * also maintaining backward compatibility of encrypted data.
350 * @param string $version This is the number/version of they key.
351 * @param string $sub This is the sublabel of the key
352 * @param string $keySource This is the source of the standard keys. Options are 'drive' and 'database'
353 * The 'drive' keys are stored at sites/<site-dir>/documents/logs_and_misc/methods
354 * The 'database' keys are stored in the 'keys' sql table
355 * @return string Returns the key in raw form.
357 function collectCryptoKey($version = "one", $sub = "", $keySource = 'drive')
359 // Build the label
360 $label = $version.$sub;
362 // If the key does not exist, then create it
363 if ($keySource == 'database') {
364 $sqlValue = sqlQuery("SELECT `value` FROM `keys` WHERE `name` = ?", [$label]);
365 if (empty($sqlValue['value'])) {
366 // Create a new key and place in database
367 // Produce a 256bit key (32 bytes equals 256 bits)
368 $newKey = produceRandomBytes(32);
369 if (empty($newKey)) {
370 error_log('OpenEMR Error : Random Bytes error - exiting');
371 die();
373 sqlInsert("INSERT INTO `keys` (`name`, `value`) VALUES (?, ?)", [$label, base64_encode($newKey)]);
375 } else { //$keySource == 'drive'
376 if (!file_exists($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label)) {
377 // Create a key and place in drive
378 // Produce a 256bit key (32 bytes equals 256 bits)
379 $newKey = produceRandomBytes(32);
380 if (empty($newKey)) {
381 error_log('OpenEMR Error : Random Bytes error - exiting');
382 die();
384 if (($version == "one") || ($version == "two") || ($version == "three") || ($version == "four")) {
385 // older key versions that did not encrypt the key on the drive
386 file_put_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label, base64_encode($newKey));
387 } else {
388 file_put_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label, encryptStandard($newKey, null, 'database'));
393 // Collect key
394 if ($keySource == 'database') {
395 $sqlKey = sqlQuery("SELECT `value` FROM `keys` WHERE `name` = ?", [$label]);
396 $key = base64_decode($sqlKey['value']);
397 } else { //$keySource == 'drive'
398 if (($version == "one") || ($version == "two") || ($version == "three") || ($version == "four")) {
399 // older key versions that did not encrypt the key on the drive
400 $key = base64_decode(rtrim(file_get_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label)));
401 } else {
402 $key = decryptStandard(file_get_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label), null, 'database');
406 if (empty($key)) {
407 error_log("OpenEMR Error : Key creation is not working - Exiting.");
408 die();
411 // Return the key
412 return $key;
415 // Produce random bytes (uses random_bytes with error checking)
416 function produceRandomBytes($length)
418 try {
419 $randomBytes = random_bytes($length);
420 } catch (Error $e) {
421 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Error: ' . $e->getMessage());
422 return '';
423 } catch (Exception $e) {
424 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Exception: ' . $e->getMessage());
425 return '';
428 return $randomBytes;
431 // Produce random string (uses random_int with error checking)
432 function produceRandomString($length = 26, $alphabet = 'abcdefghijklmnopqrstuvwxyz234567')
434 $str = '';
435 $alphamax = strlen($alphabet) - 1;
436 for ($i = 0; $i < $length; ++$i) {
437 try {
438 $str .= $alphabet[random_int(0, $alphamax)];
439 } catch (Error $e) {
440 error_log('OpenEMR Error : Encryption is not working because of random_int() Error: ' . $e->getMessage());
441 return '';
442 } catch (Exception $e) {
443 error_log('OpenEMR Error : Encryption is not working because of random_int() Exception: ' . $e->getMessage());
444 return '';
447 return $str;