Securing Hippa (ePHI) data
Posted: Sat Feb 13, 2010 11:07 am
Problem: I have a client (small physicians office) that wants to allow patients to make/cancel appointments request prescription refills and view lab results. The guidelines for hipaa are sparse and can be viewed at http://www.hhs.gov/ocr/privacy/
Givens: My client is hosted on InMotions VPS. The full audit trails that are require to meet HIPAA guidelines is taken care using MySQL triggers.
Methods: OpenSSL, PHP Mcrypt Extension, SSL conenction
Solution:
1. A client signs up and inputs all their personal identifiable information (ePHI). The input is sterilized using mysql_real_escape_string() and validated on the server side. The patients ePHI is sent to the server using an SSL connection and sent using $_POST.
3. The ePHI data is encrypted using symmetric rijndael-256
4. The AES-256 bit encrypted data is encrypted using OpenSSL with the public key
5. The ePHI data that has been encrypted using first rijndael-256 encrypting and then the OpenSSL public key is store in a BLOB datatype column in a MySQL database.
6. The private key is store encrypted in a non-web root directory.
7. The encrypted ePHI can only be access by the office administrators. The office administrators logs in using their username and password. They then entered a pass phrase (example: The quick brown fox jumps over the lazy dog), which decrypts the hashed OpenSSL key. The ephi is then decrypted using the private OpenSSL key and the decrypted using the symmetric rijndael-256 bit encryption.
Givens: My client is hosted on InMotions VPS. The full audit trails that are require to meet HIPAA guidelines is taken care using MySQL triggers.
Methods: OpenSSL, PHP Mcrypt Extension, SSL conenction
Solution:
1. A client signs up and inputs all their personal identifiable information (ePHI). The input is sterilized using mysql_real_escape_string() and validated on the server side. The patients ePHI is sent to the server using an SSL connection and sent using $_POST.
3. The ePHI data is encrypted using symmetric rijndael-256
4. The AES-256 bit encrypted data is encrypted using OpenSSL with the public key
5. The ePHI data that has been encrypted using first rijndael-256 encrypting and then the OpenSSL public key is store in a BLOB datatype column in a MySQL database.
6. The private key is store encrypted in a non-web root directory.
7. The encrypted ePHI can only be access by the office administrators. The office administrators logs in using their username and password. They then entered a pass phrase (example: The quick brown fox jumps over the lazy dog), which decrypts the hashed OpenSSL key. The ephi is then decrypted using the private OpenSSL key and the decrypted using the symmetric rijndael-256 bit encryption.
Code: Select all
//sample MySQL database table
CREATE TABLE `patients`(
userid NOT NULL auto-increment PRIMARY KEY BIGINT(20),
telephone BLOB,
first_name BLOB,
last_name BLOB,
address BLOB); //BLOB is needed because the encrypted data need to be stored in binary unless base64_encoding is used, which I am against
//PHP code
<?php
/*
* phpFreaksCrypto.class.php5 -> phpFreaksCrypto Class (PHP5)
*/
/**
* @author Dustin Whittle
* @version 0.01
*/
if (isset($_SERVER['HTTPS']) )
{
echo "SECURE: This page is being accessed through a secure connection.<br><br>";
}
else
{
echo "UNSECURE: This page is being access through an unsecure connection.<br><br>";
}
class phpFreaksCrypto
{
private $td;
// this gets called when class is instantiated
public function __construct($key = 'tHsd2134SDgj12SDFiq3547SDFnsdk3298kl3', $iv = false, $algorithm = 'rijndael-256', $mode = 'ecb')
{
if(extension_loaded('mcrypt') === FALSE)
{
$prefix = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
dl($prefix . 'mcrypt.' . PHP_SHLIB_SUFFIX) or die('The Mcrypt module could not be loaded.');
}
if($mode != 'ecb' && $iv === false)
{
/*
the iv must remain the same from encryption to decryption and is usually
passed into the encrypted string in some form, but not always.
*/
die('In order to use encryption modes other then ecb, you must specify a unique and consistent initialization vector.');
}
// set mcrypt mode and cipher
$this->td = mcrypt_module_open($algorithm, '', $mode, '') ;
// Unix has better pseudo random number generator then mcrypt, so if it is available lets use it!
$random_seed = strstr(PHP_OS, "WIN") ? MCRYPT_RAND : MCRYPT_DEV_RANDOM;
// if initialization vector set in constructor use it else, generate from random seed
$iv = ($iv === false) ? mcrypt_create_iv(mcrypt_enc_get_iv_size($this->td), $random_seed) : substr($iv, 0, mcrypt_enc_get_iv_size($this->td));
// get the expected key size based on mode and cipher
$expected_key_size = mcrypt_enc_get_key_size($this->td);
// we dont need to know the real key, we just need to be able to confirm a hashed version
$key = substr(hash('sha512',$key), 0, $expected_key_size);
// initialize mcrypt library with mode/cipher, encryption key, and random initialization vector
mcrypt_generic_init($this->td, $key, $iv);
}
public function encrypt($plain_string)
{
/*
encrypt string using mcrypt and then encode any special characters
and then return the encrypted string
*/
return base64_encode(mcrypt_generic($this->td, $plain_string));
}
public function decrypt($encrypted_string)
{
/*
remove any special characters then decrypt string using mcrypt and then trim null padding
and then finally return the encrypted string
*/
return trim(mdecrypt_generic($this->td, base64_decode($encrypted_string)));
}
// this function gets called when php garbage collection destroys the object
public function __destruct()
{
// shutdown mcrypt
mcrypt_generic_deinit($this->td);
// close mcrypt cipher module
mcrypt_module_close($this->td);
}
}
$crypto = new phpFreaksCrypto();
$string = 'blah.blah.blah';
$string2 = 'foo 124 !@$# foo foo';
$encrypted_string = $crypto->encrypt($string);
$decrypted_string = $crypto->decrypt($encrypted_string);
echo 'Original: ' . $string . '<br />';
echo 'Encrypted: ' . $encrypted_string . '<br />';
echo 'Decrypted: ' . $decrypted_string . '<br />';
//--------
//THIS IS ONLY DONE ONCE TO GENERATE THE PUBLIC AND PRIVATE KEY PAIR
// Create the keypair
$res=openssl_pkey_new();
// Get private key
//this private key will then be encrypted with rijndael-256 using a pass phrase like The quick brown fox jumps over the lazy dog
//the admins can access the private key by typing in the pass phrase an unlocking the private key
openssl_pkey_export($res, $privatekey);
// Get public key
$publickey=openssl_pkey_get_details($res);
$publickey=$publickey["key"];
//---------
echo "Private Key: $privatekey<br><br>Public Key:<BR>$publickey<BR><BR>";
$cleartext = $encrypted_string;
echo "Clear text: $cleartext<BR><BR>";
openssl_public_encrypt($cleartext, $crypttext, $publickey);
echo "Crypt text: $crypttext<BR><BR>";
openssl_private_decrypt($crypttext, $decrypted, $privatekey);
echo "OpenSSL decrypted text: $decrypted<br><br>";
$decrypted_string = $crypto->decrypt($decrypted);
echo "AES decrypted text: $decrypted_string<br><br>";
?>