Bootstrapped admin.php for multisite installation (#2155)
[openemr.git] / library / crypto.php
blob4077da7ce0b9d65d40c6d99f3b830c463fb2a239
1 <?php
2 /**
3 * Crypto library.
5 * @package OpenEMR
6 * @link http://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 Do not use this unless supporting legacy stuff(ie. the download encrypted file feature).
22 function encryptStandard($value, $customPassword = null)
24 # This is the current encrypt/decrypt version
25 # (this will always be a three digit number that we will
26 # increment when update the encrypt/decrypt methodology
27 # which allows being able to maintain backward compatibility
28 # to decrypt values from prior versions)
29 $encryptionVersion = "003";
31 $encryptedValue = $encryptionVersion . aes256Encrypt($value, $customPassword);
33 return $encryptedValue;
36 /**
37 * Standard function to decrypt
39 * @param string $value This is the data to encrypt.
40 * @param string $customPassword Do not use this unless supporting legacy stuff(ie. the download encrypted file feature).
43 function decryptStandard($value, $customPassword = null)
45 if (empty($value)) {
46 return "";
49 # Collect the encrypt/decrypt version and remove it from the value
50 $encryptionVersion = intval(mb_substr($value, 0, 3, '8bit'));
51 $trimmedValue = mb_substr($value, 3, null, '8bit');
53 # Map the encrypt/decrypt version to the correct decryption function
54 if (($encryptionVersion == 2) || ($encryptionVersion == 3)) {
55 return aes256DecryptTwo($trimmedValue, $customPassword);
56 } else if ($encryptionVersion == 1) {
57 return aes256DecryptOne($trimmedValue, $customPassword);
58 } else {
59 error_log("OpenEMR Error : Decryption is not working because of unknown encrypt/decrypt version.");
60 return false;
64 /**
65 * Check if a crypt block is valid to use for the standard method
66 * (basically checks if first 3 values are numbers)
68 function cryptCheckStandard($value)
70 if (empty($value)) {
71 return false;
74 if (preg_match('/^\d\d\d/', $value)) {
75 return true;
76 } else {
77 return false;
81 /**
82 * Function to AES256 encrypt a given string
84 * @param string $sValue Raw data that will be encrypted.
85 * @param string $customPassword If null, then use standard key. If provide a password, then will derive key from this.
86 * @return string returns the encrypted data.
88 function aes256Encrypt($sValue, $customPassword = null)
90 if (!extension_loaded('openssl')) {
91 error_log("OpenEMR Error : Encryption is not working because missing openssl extension.");
94 if (empty($customPassword)) {
95 // Collect the encryption keys. If they does not exist, then create them
96 // The first key is for encryption. Then second key is for the HMAC hash
97 $sSecretKey = aes256PrepKey("two", "a");
98 $sSecretKeyHmac = aes256PrepKey("two", "b");
99 } else {
100 // Turn the password into a hash(note use binary) to use as the keys
101 $sSecretKey = hash("sha256", $customPassword, true);
102 $sSecretKeyHmac = $sSecretKey;
105 if (empty($sSecretKey) || empty($sSecretKeyHmac)) {
106 error_log("OpenEMR Error : Encryption is not working because key(s) is blank.");
109 try {
110 $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
111 } catch (Error $e) {
112 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Error: ' . $e->getMessage());
113 } catch (Exception $e) {
114 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Exception: ' . $e->getMessage());
117 $processedValue = openssl_encrypt(
118 $sValue,
119 'aes-256-cbc',
120 $sSecretKey,
121 OPENSSL_RAW_DATA,
125 $hmacHash = hash_hmac('sha256', $iv.$processedValue, $sSecretKeyHmac, true);
127 if ($sValue != "" && ($processedValue == "" || $hmacHash == "")) {
128 error_log("OpenEMR Error : Encryption is not working.");
131 // prepend the encrypted value with the $hmacHash and $iv
132 $completedValue = $hmacHash . $iv . $processedValue;
134 return base64_encode($completedValue);
139 * Function to AES256 decrypt a given string, version 2
141 * @param string $sValue Encrypted data that will be decrypted.
142 * @param string $customPassword If null, then use standard key. If provide a password, then will derive key from this.
143 * @return string or false returns the decrypted data or false if failed.
145 function aes256DecryptTwo($sValue, $customPassword = null)
147 if (!extension_loaded('openssl')) {
148 error_log("OpenEMR Error : Decryption is not working because missing openssl extension.");
149 return false;
152 if (empty($customPassword)) {
153 // Collect the encryption keys.
154 // The first key is for encryption. Then second key is for the HMAC hash
155 $sSecretKey = aes256PrepKey("two", "a");
156 $sSecretKeyHmac = aes256PrepKey("two", "b");
157 } else {
158 // Turn the password into a hash(note use binary) to use as the keys
159 $sSecretKey = hash("sha256", $customPassword, true);
160 $sSecretKeyHmac = $sSecretKey;
163 if (empty($sSecretKey) || empty($sSecretKeyHmac)) {
164 error_log("OpenEMR Error : Encryption is not working because key(s) is blank.");
165 return false;
168 $raw = base64_decode($sValue, true);
169 if ($raw === false) {
170 error_log("OpenEMR Error : Encryption did not work because illegal characters were noted in base64_encoded data.");
171 return false;
175 $ivLength = openssl_cipher_iv_length('aes-256-cbc');
176 $hmacHash = mb_substr($raw, 0, 32, '8bit');
177 $iv = mb_substr($raw, 32, $ivLength, '8bit');
178 $encrypted_data = mb_substr($raw, ($ivLength+32), null, '8bit');
180 $calculatedHmacHash = hash_hmac('sha256', $iv.$encrypted_data, $sSecretKeyHmac, true);
182 if (hash_equals($hmacHash, $calculatedHmacHash)) {
183 return openssl_decrypt(
184 $encrypted_data,
185 'aes-256-cbc',
186 $sSecretKey,
187 OPENSSL_RAW_DATA,
190 } else {
191 error_log("OpenEMR Error : Decryption failed authentication.");
197 * Function to AES256 decrypt a given string, version 1
199 * @param string $sValue Encrypted data that will be decrypted.
200 * @param string $customPassword If null, then use standard key. If provide a password, then will derive key from this.
201 * @return string returns the decrypted data.
203 function aes256DecryptOne($sValue, $customPassword = null)
205 if (!extension_loaded('openssl')) {
206 error_log("OpenEMR Error : Decryption is not working because missing openssl extension.");
209 if (empty($customPassword)) {
210 // Collect the key. If it does not exist, then create it
211 $sSecretKey = aes256PrepKey();
212 } else {
213 // Turn the password into a hash to use as the key
214 $sSecretKey = hash("sha256", $customPassword);
217 if (empty($sSecretKey)) {
218 error_log("OpenEMR Error : Encryption is not working.");
221 $raw = base64_decode($sValue);
223 $ivLength = openssl_cipher_iv_length('aes-256-cbc');
225 $iv = substr($raw, 0, $ivLength);
226 $encrypted_data = substr($raw, $ivLength);
228 return openssl_decrypt(
229 $encrypted_data,
230 'aes-256-cbc',
231 $sSecretKey,
232 OPENSSL_RAW_DATA,
237 // Function to decrypt a given string
238 // This specific function is only used for backward compatibility
239 function aes256Decrypt_mycrypt($sValue)
241 $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
242 return rtrim(
243 mcrypt_decrypt(
244 MCRYPT_RIJNDAEL_256,
245 $sSecretKey,
246 base64_decode($sValue),
247 MCRYPT_MODE_ECB,
248 mcrypt_create_iv(
249 mcrypt_get_iv_size(
250 MCRYPT_RIJNDAEL_256,
251 MCRYPT_MODE_ECB
253 MCRYPT_RAND
256 "\0"
260 // Function to collect (and create, if needed) the standard key
261 // The key is stored at sites/<site-dir>/documents/logs_and_misc/methods/one
262 // This mechanism will allow easy migration to new keys/ciphers in the future while
263 // also maintaining backward compatibility of encrypted data (for example, if upgrade
264 // to another cipher/mode, then could place the new key for this in
265 // sites/<site-dir>/documents/logs_and_misc/methods/two and then adjust pertinent code).
266 function aes256PrepKey($version = "one", $sub = "")
268 // Build the label
269 $label = $version.$sub;
271 // Collect the key. If it does not exist, then create it
272 if (!file_exists($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label)) {
273 // Create a key file
275 try {
276 // Produce a 256bit key (32 bytes equals 256 bits)
277 $newKey = random_bytes(32);
278 } catch (Error $e) {
279 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Error: ' . $e->getMessage());
280 } catch (Exception $e) {
281 error_log('OpenEMR Error : Encryption is not working because of random_bytes() Exception: ' . $e->getMessage());
284 file_put_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/". $label, base64_encode($newKey));
287 // Collect key from file
288 $key = base64_decode(rtrim(file_get_contents($GLOBALS['OE_SITE_DIR'] . "/documents/logs_and_misc/methods/" . $label)));
290 if (empty($key)) {
291 error_log("OpenEMR Error : Key creation is not working.");
294 // Return the key
295 return $key;