Hi @jelo,
thanks a lot for your help - and thanks for your link to $argv. I still do not get this working
The proposed line
Code:
php encrypted.cmd.txt private_key.pem > data.csv
just copies the content of encrypted.cmd.txt into the data.csv
When I change this to something that looks more like the instructions on
github.com/SamMousa/limesurvey-encrypt
(php decrypt.php > data.csv)
Code:
php decrypt.php private_key.pem encrypted.cmd.txt > data.csv
This just produces an empty data.csv and there are no errors nor warnings.
I also tried some other combinations - no success - and yes: the "helloworld.php" works ...
As I said, I'm not a programmer, so I hope some of you have a better time going through this code than I had today

If you can help here, that would be great!
Btw: The exported file looks like this: the whole content of decrypt.php in the beginning and the encrypted data in the end, merged in one file.
Code:
@echo off
php -r "$f = fopen('%~0', 'rb'); fseek($f, 117); $code = stream_get_contents($f); eval($code); "
exit
function decryptResponses($privateKey, $data, $handle)
{
$first = true;
foreach (explode('.', $data) as $encryptedResponse)
{
echo "Decrypting response...\n";
$decryptedKey = '';
$allData = base64_decode($encryptedResponse);
$encryptedKey = substr($allData, 0, 128);
if (openssl_private_decrypt($encryptedKey, $decryptedKey, $privateKey))
{
$iv = substr($encryptedKey, 0, mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC));
$data = mcrypt_decrypt(MCRYPT_BLOWFISH, $decryptedKey, substr($allData, 128), MCRYPT_MODE_CBC, $iv);
$response = json_decode(trim($data), true);
if ($first)
{
fputcsv($handle, array_keys($response));
$first = false;
}
fputcsv($handle, $response);
unset($response);
}
else
{
echo openssl_error_string() . "\n";
}
}
}
$stdin = fopen('php://stdin', 'r');
if (isset($argv[1]))
{
$privateKey = openssl_pkey_get_private(file_get_contents($argv[1]));
}
else
{
echo "Please enter the path to the private key file: ";
$file = rtrim(fgets($stdin));
echo '"' . $file . '"' . PHP_EOL;
$key = file_get_contents($file);
$privateKey = openssl_pkey_get_private($key);
}
$handle = fopen('php://stdout', 'w');
/**
* The builder script will add the appropriate decrypt command to the end
* of the file. DO NOT ADD A CLOSING TAG.
*/
decryptResponses($privateKey, 'kcZRc1h9wojA6clxkPI+t4Y1e/CKAsmerNcLr+qeoDxJ89GvURYBetPltQOEukQBkU2uIIMMpZNk/ElyJMWu57YZjtb3ABbyq0OxuS1vuxeBJbFG+/w9mh0ecoeiw2pgUKnkPSbW3UnL4JyOb8GhmuxzFQL+7l9XqNICXmoe9rA101K9TUtvi1Li6FFpNIMZ5T4gMKNkmdMqDSKAsn7alf8LPYjBT3Eaix8O8ufPQ4oMMMKz8GuWEplhTJt27ZqalrZSpVjcpGQSxXCRS1vSPlsUihEya9RjgdYCzJvZSU0+LFUPC1QukReorAmSO6mr+GIjVPMz4YsvSVwu/raUUPBMs22oYBRUhZAr2XhmwSI8VHmRE69GChlbaYmh75bynOzihuTURdH9G0OIc2KFTv1xVFTn42ruBueG9whgSoP4IPlLO/5lgaeX5HHvf3Jtc+Fs6bXW7nuMSRiDRWQx7YHtpZj3IH6Fy3nbhTXmGTeJvHMQwAi0RDLoix6gX0WWqE+QwrAlo/amQdM29yNZg8SOk3ValvcDxIt2Ji1XdvEzd2rqO0JsN7S6nzXXWd2svTtDqKJsOvdx0t9jJBQwBPlMI/1wIsKZ5Q5St3w29b6gizMyZW6XpWXdkWRgM1xs8ooXi/3vwgRxD1R1ff8QWRi/T8b0rwCCAKHbGcUnpetbslDdtUNifLZ3KkHN84jwGQU9pRQ7Q+pj2TB3B7fDKFemcT+Tj4lbnMN43FRb4PMrSZtn5dhQLgiCk3QMDhEELdAwWkJzCwARW1be9blgSc4++DannmO08yuA8f18qKdaA7PX7nAVgryydAwABvvTgBMVAOQYu7tjUA24Vwwn4A0PfmlXdSms/1L1f/r3MvbgLWFz+u4Zmchh8KzsEfBD68c/qSxsn/zoK80tGg6IECMA0Qu9T4YBOgz8RCiZBjOT+siAzr2Q7294snixeqZkE9aYBUheTYh6No0GNdCW2Yy5QQ5AkWXZmzBqa6O+QD2scQ2Kd7z9PSaz3wTwiVWWXyl5cdjfcq5l3JgT8kZyyGjGcR9avQOXHOn0l5e+Fml/V0fdyVPclZXhTBIiGXjQz8twcv1XEt9Bl4I4s2/Nk6jZDo2Psvdc8xvnncGWKgMly7gqOcsFxmH46TUNegKRPg4pWnA52w8hjgvUARBEQqhvSkK/LTS6xUI+kxr4tqjX4OArFUMAGJjK4X7UKvNAOYLo6vnT8e8=.ZpMaq8qO+ZbdkF/SmXPvcSmpwJh7I2bo4OjCrWsgGKM2zatfFghBANBQL8EUsA2mJgSXuEs3DGznP+ZPHOV2lIdd2mC61qJxwPQ6X7qxmEERAR4BT8ieoClwsC5+iIYQgol5nyLgfHrBrrHhBhf9fJISgFbOC16e2AZHf/84alQx+HlcVWblgDP+yZSe9nNY+Fwuukukp/jffyS+tf8x/klB4gvmWnr65wKQnX4ObrkX7gS2cwldSOxRCWW2hzjT+fcvCuWMilsToMLmfjeBZkCRfDywqDTFv1J/wWwYYLoSblz4TPIhxszNLL2KKyiwdSMgUhn0O0oqHyV1gNZtrWibaZIOZY6pNQoRaWrp3kPzEfazsFG9m0GagzKV/VHCv1UgQjW0W/3EV1KmWq9VMUES3notMsaoti3pixUS1QoTXcoye6MDzHV44CUGaCTgQk/a0n3lIiEX82EoJWxgoTTwO9z1QrBMEgz9QdUO52+A8eqa4uDicaOMucmK/RUAfFucVJz4FrmcEyXzdwy4bhdl49If9O3mdl4t6eLRTlKef0IbVwFUPNFACnIYOBw6lxPQhkzfzuLHRypwsDCzM2K+luhhVvExrfxv6HrNpWZ9RNId/OQa8llOLIQ7Eg7FT0a4/oEswfPmAkbOhxTbQH04tSYVkGu54dvuy9xwrk2tLXhkepd0uwuvw8rh+Rau6o/UlKwkC0DwdlqX+4gKsN1cxNsZ/mD1WZop5Yb7on17VB+ZYVfgTo1oKnv0f3DicAHLeNFvBva2RMBZBdyrZDvfuua+ybu+8E1DGyT13UFqK8gyoQUTpCIBrmS54ltIluRHyWJRBY2psZbizAwLUwkoJRDEOeLy8JDGQZC7iN+pABv//UXiy5DaIyCzkdoo2awJubVVEGbpHmyVutemjpHB3RPVam6b1axA+mxhICZa2OjnPdckvPc0yQuA9GvYPs7yhRFwPVhckRyhYss1zkhHzAoLQJ3dvOgn22ISTW7G04O01WfV7X8z5tRZX6yKXWO2QRV67LpDUOHzo0atfHltXccsklw2pmVNYhJHlHQ8Y+guiQnWL5Ay+kXEXvZD.T2p+/28BLr5KZ+GvfR90/b4wpsUi6j84LrzTF+pLqCxaIHVy3JieCI2S7RctL5Tv8EM9P5Hbw6YfOMjZp/UAmeb1pOxQjI2bevU4Zud3QbG6DHFm2UtXOg2VtV5bmIeLLe8/bBvfQ/lIKNACKDkmfXy558+Od6mKKyxE3nMPmbPsY0EtdeFOUmQaLgjV45E/89W6N1vMZ3TO/riERY9XVEnrOvOs2y6kWbIEF66XRMOdwYBJ+keyMJR1LA7stXvM1fbnGzwFJlIrCVtykV2SoJ4tPOiXNohZ7h7iShEbhlzvhVxlp2YXce6nT648fV1ol6C4QUgENo2Oydm+2B2tG9ssyHscm/zN5+yiYGFpX+S2uZBtqo7stY5ZdtETV5OWMfZVDwsHcRUDarRChlplDQfzRIABe8nXm90TbDtnzQyTpBe1GpHiys8mejTTNzRsX2nlH6RIvFoqnZge7Ovf8rQBb8tHaMrWuThEEvaeg2tXc9yijFwPAAhUOrSW8A3VegJonfzxsoNkDcjzW7j9OJE6qaEXchRlFBOx4gjCAYFBDSsHenZarNQjhi66d0W1dGraGFt/Kv745qOPT02nX3wxCSXiWNMpqlpbTnyF+GnnVWeKZc769i2tc4E5CKM3Y4VxfyVvtD47m8AiERazTGexSd6aQyyx+eDfQwMskL3IiJUBo8siymhVhTDaTiaMDg5UEOwi+hCEd0RUoRiLiWMKL8lT80RpSk5QyV3YP7ZXY74HAODrlhHXgJL4zlWeh4dXbcRCGbNSRJIXPIKOeTnNLVMVxlAdQxcDxoA/+lx8a3K/qoNHP//udVw+l3UhOt/lenditPuf+NK4vZefKyU+3MyqvRdnIgCaEtBl3g6Cfx+zp+okJrcP/s+vr6QaIj00w/AcZ9Lul4E0nqvyGqsJJJc1JchiaUgsVsOxbLaDPktmTpHR4pl9h8PVPqKzftQ8uX9bSFngO3Qunl5NpR/PqKnVBo9KBHZrqu4T2IsJ+gSbK5Ke/GDEhQlZrrddTYWnuDOTvhfMhLt6wSa8GNq1Utqa14Z159LRQvdVN+pWjW4+LmPdww==', $handle);
fgets($stdin);
I still think I'm doing something wrong here - but if anybody of you with programming experience has time to skim the code that would be great.
The code for Encrypt.php and decrypt.php by Sam Mousa can be found on
github.com/SamMousa/limesurvey-encrypt
(and here below). It wasn't changed for more than three years. Thanks to Mark G. Brown and Sam Mousa for making this open source!
Who has an idea? I think the encryption of our respondents' data is really essential - I started to think about this some time ago when I found myself not completely trusting everybody at my internet-provider and thought that I should not collect unencrypted readable data there.
Thanks for your time and all the best,
G
Encrypt.php
Code:
<?php
/**
* This plugin uses asymmetric encryption to encrypt responses after completion.
* Even if the limesurvey server is compromised an attack will have no way of decrypting the data.
*
* This work was commissioned by Dr Mark Brown
* @author Sam Mousa MSc <sam@befound.nl>
* @license https://opensource.org/licenses/MIT MIT
* @link https://www.markgbrown.com/
*
*/
class Encrypt extends PluginBase
{
static protected $description = 'Encrypt: Encrypt completed surveys.';
static protected $name = 'Encrypt';
/**
* Plugin settings
*/
protected $settings = array(
'publicKey' => array(
'type' => 'text',
'label' => 'Public key'
),
);
protected $storage = 'DbStorage';
public function __construct(PluginManager $manager, $id)
{
parent::__construct($manager, $id);
// Provides survey specific settings.
$this->subscribe('beforeSurveySettings');
// Saves survey specific settings.
$this->subscribe('newSurveySettings');
// Encrypt data on survey completion.
$this->subscribe('afterSurveyComplete');
// Create table for encrypted data.
$this->subscribe('beforeActivate');
$this->subscribe('newDirectRequest');
}
protected function getData($surveyId)
{
$table = $this->api->getTable($this, 'responses');
$responses = $table->findAllByAttributes(array(
'survey_id' => $surveyId
));
$data = '';
return implode('.', array_map(function($response) { return base64_encode($response->response); }, $responses));
}
public function actionExportScript($surveyId)
{
$data = $this->getData($surveyId);
$script = file(__DIR__ . '/decrypt.php', FILE_IGNORE_NEW_LINES);
// Remove first line.
array_shift($script);
$script[] = 'decryptResponses($privateKey, \'' . $data . '\', $handle);';
$lines = [];
$lines[] = '@echo off';
$lines['call'] = "php -r \"\$f = fopen('%~0', 'rb'); fseek(\$f, ----------); \$code = stream_get_contents(\$f); eval(\$code); \"";
$lines[] = 'exit';
$offset = strlen(implode("\n", $lines)) - 1;
$lines['call'] = strtr($lines['call'], ['----------' => $offset]);
// Add data.
$lines = array_merge($lines, $script);
$lines[] = 'fgets($stdin);';
$this->event->get('request')->sendFile('encrypted.cmd', implode("\n", $lines), 'text/plain', true);
}
public function actionExport($surveyId)
{
$this->actionExportScript($surveyId);
return;
$data = $this->getData($surveyId);
$this->event->get('request')->sendFile('encrypted.dat', $data, 'text/plain', true);
}
/**
* This event is fired after the survey has been completed.
* @param PluginEvent $event
*/
public function afterSurveyComplete()
{
$event = $this->getEvent();
if ($event->get('responseId') == null)
{
return;
}
// Get the response information.
$response = $this->api->getResponse($event->get('surveyId'), $event->get('responseId'));
$publicKey = $this->get('publicKey');
$data = json_encode($response);
$encryptedResponse = $this->pluginManager->getAPI()->newModel($this, 'responses');
$encryptedResponse->response = $this->encrypt($publicKey, $data);
$encryptedResponse->survey_id = $event->get('surveyId');
if ($encryptedResponse->save())
{
$this->pluginManager->getAPI()->removeResponse($event->get('surveyId'), $event->get('responseId'));
$this->event->setContent($this, 'Response has been encrypted.');
return;
}
$this->event->setContent($this, openssl_error_string());
}
public function beforeActivate()
{
$event = $this->event;
if (!$this->api->tableExists($this, 'responses'))
{
$this->api->createTable($this, 'responses', array(
'survey_id' => 'int',
'response' => 'binary',
));
}
return true;
}
public function beforeSurveySettings()
{
$event = $this->event;
$table = $this->api->getTable($this, 'responses');
$count = $table->countByAttributes(array('survey_id' => $event->get('survey')));
$settings = array(
'name' => get_class($this),
'settings' => array(
'enabled' => array(
'type' => 'boolean',
'label' => 'Encrypt responses for this survey: ',
'current' => $this->get('enabled', 'Survey', $event->get('survey'), true)
),
'count' => array(
'label' => 'Number of encrypted responses:',
'type' => 'string',
'readOnly' => true,
'current' => $count
)
)
);
if ($count > 0)
{
$settings['settings']['export'] =array(
'label' => 'Export data',
'type' => 'link',
'link' => $this->api->createUrl('plugins/direct', array('plugin' => 'Encrypt', 'function' => 'export', 'sid' => $event->get('survey')))
);
}
$event->set("surveysettings.{$this->id}", $settings);
}
protected function encrypt($publicKey, $data)
{
// Generate a random password for symmetric encryption.
$symmetricKey = openssl_random_pseudo_bytes(50);
$encryptedKey = '';
if (openssl_public_encrypt($symmetricKey, $encryptedKey, $publicKey))
{
// Use the encrypted key as basis for the IV.
$iv = substr($encryptedKey, 0, mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC));
$encryptedData = mcrypt_encrypt(MCRYPT_BLOWFISH, $symmetricKey, $data, MCRYPT_MODE_CBC, $iv);
// Key has constant length so no separator is necessary.
return $encryptedKey . $encryptedData;
}
/**
* @todo Proper error handling.
*
*/
}
public function newDirectRequest()
{
$event = $this->event;
if ($event->get('target') == $this->getName() && $event->get('function') == 'export')
{
if (!$this->api->checkAccess('administrator'))
{
throw new CHttpException(403, 'This action requires you to be logged in as super administrator.');
}
elseif ($event->get('request')->getParam('sid') == null)
{
throw new CHttpException(400, 'Survey id missing; pass it as sid variable.');
}
else
{
$this->actionExport($event->get('request')->getParam('sid'));
}
}
}
public function newSurveySettings()
{
foreach ($this->event->get('settings') as $name => $value)
{
if ($name != 'count')
{
$this->set($name, $value, 'Survey', $this->event->get('survey'));
}
}
}
}
?>
decrypt.php
Code:
<?php
function decryptResponses($privateKey, $data, $handle)
{
$first = true;
foreach (explode('.', $data) as $encryptedResponse)
{
echo "Decrypting response...\n";
$decryptedKey = '';
$allData = base64_decode($encryptedResponse);
$encryptedKey = substr($allData, 0, 128);
if (openssl_private_decrypt($encryptedKey, $decryptedKey, $privateKey))
{
$iv = substr($encryptedKey, 0, mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC));
$data = mcrypt_decrypt(MCRYPT_BLOWFISH, $decryptedKey, substr($allData, 128), MCRYPT_MODE_CBC, $iv);
$response = json_decode(trim($data), true);
if ($first)
{
fputcsv($handle, array_keys($response));
$first = false;
}
fputcsv($handle, $response);
unset($response);
}
else
{
echo openssl_error_string() . "\n";
}
}
}
$stdin = fopen('php://stdin', 'r');
if (isset($argv[1]))
{
$privateKey = openssl_pkey_get_private(file_get_contents($argv[1]));
}
else
{
echo "Please enter the path to the private key file: ";
$file = rtrim(fgets($stdin));
echo '"' . $file . '"' . PHP_EOL;
$key = file_get_contents($file);
$privateKey = openssl_pkey_get_private($key);
}
$handle = fopen('php://stdout', 'w');
/**
* The builder script will add the appropriate decrypt command to the end
* of the file. DO NOT ADD A CLOSING TAG.
*/