Skip to content

Commit

Permalink
ID#336: migrated mcrypt to openssl module for user attribute encrypti…
Browse files Browse the repository at this point in the history
…on in UMGT.

IMPORTANT: This is a breaking change! Manual migration is required:

- Load and decrypt all users
- Save all users w/ unencrypted values
- Update APF
- Load all users and save w/ encrypted values

For details, please see migration documentation.
  • Loading branch information
Christian Achatz committed Aug 12, 2018
1 parent 36d1873 commit 04ece24
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 133 deletions.
203 changes: 70 additions & 133 deletions modules/usermanagement/biz/provider/UserFieldEncryptionProvider.php
Expand Up @@ -28,165 +28,62 @@
* @author Ralf Schubert
* @version
* Version 1.0, 24.06.2013<br />
* Version 1.1, 12.08.2018 (ID#336: migrated from mcrypt to OpenSSL)<br />
*/
class UserFieldEncryptionProvider {

/**
* @var array List of field names to be encrypted.
*/
public static $encryptedFieldNames = null;
public static $encryptionConfigKey = null;
protected static $encryptionHardCodedKey = 'sjhdjhaDSAHKHSLdäASÖdo75&$/6923598(&)(3k;;';
protected static $encryptionConcatenatedKey = null;
protected static $encryptionIV = null;


/**
* Concatenates all encryption key parts to the final key with correct length.
* If key is smaller then all key parts together, a new key will be generated,
* containing parts of each single key part.
*
* @param string $handler The mcrypt handler.
*
* @return String The final encryption/decryption key
* @var string Encryption key to be configured by application.
*/
protected static function getConcatenatedEncryptionKey($handler) {
if (self::$encryptionConcatenatedKey === null) {
$key = '';
$size = mcrypt_enc_get_key_size($handler);
$currentSize = 0;

$sizeHardcoded = strlen(self::$encryptionHardCodedKey);
$sizeConfig = strlen(self::$encryptionConfigKey);

if ($size >= ($sizeHardcoded + $sizeConfig)) {
$key = self::$encryptionHardCodedKey . self::$encryptionConfigKey;
while ($size > strlen($key)) {
$key .= $key;
}
if ($size < strlen($key)) {
$key = substr($key, 0, $size);
}
} else {

$posHardcoded = 0;
$posConfig = 0;

while ($currentSize < $size) {
if (($posHardcoded + 1) < $sizeHardcoded) {
$key .= substr(self::$encryptionHardCodedKey, $posHardcoded, 1);
$posHardcoded++;
$currentSize++;
}
if ((($posConfig + 1) < $sizeConfig) && ($currentSize < $size)) {
$key .= substr(self::$encryptionConfigKey, $posConfig, 1);
$posConfig++;
$currentSize++;
}
}
}

self::$encryptionConcatenatedKey = $key;
}

return self::$encryptionConcatenatedKey;
}
public static $encryptionConfigKey = null;

/**
* Returns encryption handler and creates an IV (or uses cached one)
* For the IV the same parts are used as for the encryption key, but they are concatenated differently.
*
* @return type
* @var string Cipher method. Can be defined from the list of i.e. openssl_get_cipher_methods()
*/
public static function getEncryptionHandler() {
$td = mcrypt_module_open('tripledes', '', 'ecb', '');
if (self::$encryptionIV === null) {
$ivSize = mcrypt_enc_get_iv_size($td);
$iv = self::$encryptionConfigKey . self::$encryptionHardCodedKey;
while (strlen($iv) < $ivSize) {
$iv .= $iv;
}
$iv = substr($iv, 0, $ivSize);
self::$encryptionIV = $iv;
}

return $td;
}
public static $cipherMethod = 'AES-128-CBC';

/**
* Closes the encryption handler
*
* @param type $handler
* @const string Hard-coded encryption key to generate initialization vector.
*/
protected static function closeEncryptionhandler($handler) {
mcrypt_module_close($handler);
self::$encryptionIV = null;
}
const HARD_CODED_ENCRYPTION_KEY = 'sjhdjhaDSAHKHSLdäASÖdo75&$/6923598(&)(3k;;';

/**
* Encrypts the given value.
* If encryption handler is provided it will be used. Otherwise
* one will be generated.
*
* @param String $value Plain value which should be encrypted
* @param type $encryptionHandler
*
* @return String encrypted value
* @param string $string Plain value which should be encrypted.
* @return string Encrypted value.
*/
public static function encrypt($value, $encryptionHandler = null) {

if (empty($value)) {
return $value;
}
public static function encrypt($string) {

$closeHandler = false;
if ($encryptionHandler === null) {
$encryptionHandler = self::getEncryptionHandler();
$closeHandler = true;
if (empty($string)) {
return '';
}

mcrypt_generic_init($encryptionHandler, self::getConcatenatedEncryptionKey($encryptionHandler), self::$encryptionIV);
$crypted = base64_encode(mcrypt_generic($encryptionHandler, $value));
mcrypt_generic_deinit($encryptionHandler);

if ($closeHandler) {
self::closeEncryptionhandler($encryptionHandler);
}

return $crypted;
return self::encryptOpenSSL($string);
}

/**
* Decrypts the given encrypted value
* Decrypts the given encrypted value.
*
* @param String $crypted Encrypted value which should be decrypted.
* @param type $encryptionHandler
*
* @return String The decrypted plain value
* @param string $string Encrypted value which should be decrypted.
* @return string The decrypted plain value.
*/
public static function decrypt($crypted, $encryptionHandler = null) {

if (empty($crypted)) {
return $crypted;
}
public static function decrypt($string) {

$closeHandler = false;
if ($encryptionHandler === null) {
$encryptionHandler = self::getEncryptionHandler();
$closeHandler = true;
if (empty($string)) {
return '';
}

mcrypt_generic_init($encryptionHandler, self::getConcatenatedEncryptionKey($encryptionHandler), self::$encryptionIV);
$plain = mdecrypt_generic($encryptionHandler, base64_decode($crypted));
mcrypt_generic_deinit($encryptionHandler);

if ($closeHandler) {
self::closeEncryptionhandler($encryptionHandler);
}
// the trim is needed, because sometimes there appear some
// invisible characters which lead to string comparison fails
return trim($plain);
return self::decryptOpenSSL($string);
}

/**
* Checks wether the given property is configured to be encrypted
* Checks whether the given property is configured to be encrypted
*
* @param String $propertyName
*
Expand All @@ -209,14 +106,12 @@ public static function encryptProperties(UmgtUser $user) {
if (self::$encryptedFieldNames === null) {
return;
}
$encryptionHandler = self::getEncryptionHandler();
$properties = $user->getProperties();
foreach ($properties as $key => $value) {
if (self::propertyHasEncryptionEnabled($key)) {
$user->setProperty($key, self::encrypt($value, $encryptionHandler));
$user->setProperty($key, self::encrypt($value));
}
}
self::closeEncryptionhandler($encryptionHandler);
}

/**
Expand All @@ -229,14 +124,56 @@ public static function decryptProperties(UmgtUser $user) {
return;
}

$encryptionHandler = self::getEncryptionHandler();
$properties = $user->getProperties();
foreach ($properties as $key => $value) {
if (self::propertyHasEncryptionEnabled($key)) {
$user->setProperty($key, self::decrypt($value, $encryptionHandler));
$user->setProperty($key, self::decrypt($value));
}
}
self::closeEncryptionhandler($encryptionHandler);
}

/**
* Generates an initialization vector (IV) for encryption and decryption.
*
* @param int $length Desired length of the initialization vector (IV).
* @return string The initialization vector (IV).
*/
protected static function generateInitializationVector($length) {
$iv = self::$encryptionConfigKey . self::HARD_CODED_ENCRYPTION_KEY;
while (strlen($iv) < $length) {
$iv .= $iv;
}
return substr($iv, 0, $length);
}

protected static function encryptOpenSSL($value) {

$length = openssl_cipher_iv_length(self::$cipherMethod);
$iV = self::generateInitializationVector($length);

return base64_encode(
openssl_encrypt(
$value,
self::$cipherMethod,
self::$encryptionConfigKey,
OPENSSL_RAW_DATA,
$iV
)
);
}

protected static function decryptOpenSSL($value) {

$length = openssl_cipher_iv_length(self::$cipherMethod);
$iV = self::generateInitializationVector($length);

return openssl_decrypt(
base64_decode($value),
self::$cipherMethod,
self::$encryptionConfigKey,
OPENSSL_RAW_DATA,
$iV
);
}

}
@@ -0,0 +1,129 @@
<?php
/**
* <!--
* This file is part of the adventure php framework (APF) published under
* https://adventure-php-framework.org.
*
* The APF is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The APF is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the APF. If not, see http://www.gnu.org/licenses/lgpl-3.0.txt.
* -->
*/
namespace APF\tests\suites\modules\usermanagement\biz\provider;

use APF\modules\usermanagement\biz\model\UmgtUser;
use APF\modules\usermanagement\biz\provider\UserFieldEncryptionProvider;
use PHPUnit\Framework\TestCase;

class UserFieldEncryptionProviderTest extends TestCase {

public static function setUpBeforeClass() {
ini_set('error_reporting', E_ALL & ~E_DEPRECATED);
}

public static function tearDownAfterClass() {
ini_set('error_reporting', E_ALL);
}

public function testEncrypt() {
$this->assertEquals('Q7N1fzCJIAczFsyzG+Mz5w==', UserFieldEncryptionProvider::encrypt('foo'));
$this->assertEquals('', UserFieldEncryptionProvider::encrypt(''));
}

public function testDecrypt() {
$this->assertEquals('foo', UserFieldEncryptionProvider::decrypt('Q7N1fzCJIAczFsyzG+Mz5w=='));
$this->assertEquals('', UserFieldEncryptionProvider::decrypt(''));
}

public function testPropertyHasEncryptionEnabled() {
$this->assertFalse(UserFieldEncryptionProvider::propertyHasEncryptionEnabled('foo'));

UserFieldEncryptionProvider::$encryptedFieldNames = ['Username'];
$this->assertFalse(UserFieldEncryptionProvider::propertyHasEncryptionEnabled('FirstName'));
$this->assertTrue(UserFieldEncryptionProvider::propertyHasEncryptionEnabled('Username'));

UserFieldEncryptionProvider::$encryptedFieldNames = null;
}

public function testEncryptProperties1() {

$user = new UmgtUser();

$firstName = 'First name';
$userName = 'User name';
$user->setFirstName($firstName);
$user->setUsername($userName);
UserFieldEncryptionProvider::$encryptedFieldNames = ['Username'];
UserFieldEncryptionProvider::encryptProperties($user);

$this->assertEquals($firstName, $user->getFirstName());
$this->assertNotEquals($userName, $user->getUsername());
$this->assertEquals('8Xx3rHFKdWCnAwUrV0rq9A==', $user->getUsername());

UserFieldEncryptionProvider::$encryptedFieldNames = null;

}

public function testEncryptProperties2() {

$user = new UmgtUser();

$firstName = 'First name';
$userName = 'User name';
$user->setFirstName($firstName);
$user->setUsername($userName);
UserFieldEncryptionProvider::$encryptedFieldNames = null;
UserFieldEncryptionProvider::encryptProperties($user);

$this->assertEquals($firstName, $user->getFirstName());
$this->assertEquals($userName, $user->getUsername());

UserFieldEncryptionProvider::$encryptedFieldNames = null;

}

public function testDecryptProperties1() {

$user = new UmgtUser();

$firstName = 'First name';
$userName = '8Xx3rHFKdWCnAwUrV0rq9A==';
$user->setFirstName($firstName);
$user->setUsername($userName);
UserFieldEncryptionProvider::$encryptedFieldNames = ['Username'];
UserFieldEncryptionProvider::decryptProperties($user);

$this->assertEquals($firstName, $user->getFirstName());
$this->assertNotEquals($userName, $user->getUsername());
$this->assertEquals('User name', $user->getUsername());

UserFieldEncryptionProvider::$encryptedFieldNames = null;
}

public function testDecryptProperties2() {

$user = new UmgtUser();

$firstName = 'First name';
$userName = 's0FvKrPjZPkY1mXKhU2tHQ==';
$user->setFirstName($firstName);
$user->setUsername($userName);
UserFieldEncryptionProvider::$encryptedFieldNames = null;
UserFieldEncryptionProvider::decryptProperties($user);

$this->assertEquals($firstName, $user->getFirstName());
$this->assertEquals($userName, $user->getUsername());

UserFieldEncryptionProvider::$encryptedFieldNames = null;
}

}

0 comments on commit 04ece24

Please sign in to comment.