mirror of
https://github.com/HaschekSolutions/pictshare.git
synced 2025-11-11 18:56:21 +00:00
Added encryption based on Libsodium
This commit is contained in:
@@ -36,6 +36,8 @@ Table of contents
|
||||
|
||||
## New Features in v2
|
||||
|
||||
- Added support for external storage
|
||||
- [Encryption of files in external storage](/rtfm/ENCRYPTION.md)
|
||||
- Added text hosting (like pastebin)
|
||||
- Added URL shortening
|
||||
- Added WebP to images (and conversion from jpg,png to webp)
|
||||
|
||||
@@ -78,13 +78,7 @@ if($_REQUEST['base64'])
|
||||
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
|
||||
}
|
||||
|
||||
// Lets' check all storage controllers and tell them that a new file was uploaded
|
||||
$sc = getStorageControllers();
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
if((new $contr())->isEnabled()===true)
|
||||
(new $contr())->pushFile($answer['hash']);
|
||||
}
|
||||
storageControllerUpload($answer['hash']);
|
||||
}
|
||||
|
||||
echo json_encode($answer);
|
||||
|
||||
@@ -78,13 +78,7 @@ if($answer['hash'] && $answer['status']=='ok')
|
||||
}
|
||||
|
||||
|
||||
// Lets' check all storage controllers and tell them that a new file was uploaded
|
||||
$sc = getStorageControllers();
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
if((new $contr())->isEnabled()===true)
|
||||
(new $contr())->pushFile($answer['hash']);
|
||||
}
|
||||
storageControllerUpload($answer['hash']);
|
||||
}
|
||||
|
||||
if($answer['hash'] && $answer['status']=='ok')
|
||||
@@ -98,13 +92,7 @@ if($answer['hash'] && $answer['status']=='ok')
|
||||
$answer['delete_url'] = URL.'delete_'.getDeleteCodeOfHash($answer['hash']).'/'.$answer['hash'];
|
||||
}
|
||||
|
||||
// Lets' check all storage controllers and tell them that a new file was uploaded
|
||||
$sc = getStorageControllers();
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
if((new $contr())->isEnabled()===true)
|
||||
(new $contr())->pushFile($answer['hash']);
|
||||
}
|
||||
storageControllerUpload($answer['hash']);
|
||||
}
|
||||
|
||||
echo json_encode($answer);
|
||||
|
||||
@@ -73,13 +73,7 @@ if ($_FILES['file']["error"] == UPLOAD_ERR_OK)
|
||||
}
|
||||
|
||||
|
||||
// Lets' check all storage controllers and tell them that a new file was uploaded
|
||||
$sc = getStorageControllers();
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
if((new $contr())->isEnabled()===true)
|
||||
(new $contr())->pushFile($answer['hash']);
|
||||
}
|
||||
storageControllerUpload($answer['hash']);
|
||||
}
|
||||
|
||||
echo json_encode($answer);
|
||||
|
||||
50
inc/core.php
50
inc/core.php
@@ -51,11 +51,27 @@ function architect($url)
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
$c = new $contr();
|
||||
if($c->isEnabled()===true && $c->hashExists($el))
|
||||
if($c->isEnabled()===true && $c->hashExists($el))
|
||||
{
|
||||
$c->pullFile($el);
|
||||
$hash = $el;
|
||||
break; // we brake here because we already have the file. no need to check other storage controllers
|
||||
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
|
||||
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
|
||||
|
||||
break; // we break here because we already have the file. no need to check other storage controllers
|
||||
}
|
||||
else if($c->isEnabled()===true && defined('ENCRYPTION_KEY') && ENCRYPTION_KEY !='' && $c->hashExists($el.'.enc')) //this is an encrypted file. Let's decrypt it
|
||||
{
|
||||
$hash = $el.'.enc';
|
||||
$c->pullFile($hash,ROOT.DS.'tmp'.DS.$hash);
|
||||
|
||||
$enc = new Encryption;
|
||||
$hash = substr($hash,0,-4);
|
||||
$enc->decryptFile(ROOT.DS.'tmp'.DS.$el.'.enc', ROOT.DS.'tmp'.DS.$hash,base64_decode(ENCRYPTION_KEY));
|
||||
|
||||
storeFile(ROOT.DS.'tmp'.DS.$hash,$hash,true);
|
||||
unlink(ROOT.DS.'tmp'.DS.$el.'.enc');
|
||||
|
||||
break; // we break here because we already have the file. no need to check other storage controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +139,30 @@ function architect($url)
|
||||
//var_dump($u);
|
||||
}
|
||||
|
||||
function storageControllerUpload($hash)
|
||||
{
|
||||
// Lets' check all storage controllers and tell them that a new file was uploaded
|
||||
$sc = getStorageControllers();
|
||||
foreach($sc as $contr)
|
||||
{
|
||||
if((new $contr())->isEnabled()===true)
|
||||
{
|
||||
$source = ROOT.DS.'data'.DS.$hash.DS.$hash;
|
||||
if(defined('ENCRYPTION_KEY') && ENCRYPTION_KEY) //ok so we got an encryption key which means we'll store only the encrypted file
|
||||
{
|
||||
$enc = new Encryption;
|
||||
$encoded_file = ROOT.DS.'tmp'.DS.$hash.'.enc';
|
||||
$enc->encryptFile($source,$encoded_file,base64_decode(ENCRYPTION_KEY));
|
||||
(new $contr())->pushFile($encoded_file,$hash.'.enc');
|
||||
}
|
||||
else // not encrypted
|
||||
(new $contr())->pushFile($source,$hash);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function getNewHash($type,$length=10)
|
||||
{
|
||||
while(1)
|
||||
@@ -155,7 +195,9 @@ function autoload($className)
|
||||
if (file_exists(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php'))
|
||||
require_once(ROOT . DS . 'content-controllers' . DS . strtolower($className) . '.php');
|
||||
if (file_exists(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php'))
|
||||
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
|
||||
require_once(ROOT . DS . 'interfaces' . DS . strtolower($className) . '.interface.php');
|
||||
if ($className=='Encryption')
|
||||
require_once(ROOT . DS . 'inc' . DS . 'encryption.php');
|
||||
}
|
||||
|
||||
function renderTemplate($template,$vars=false)
|
||||
|
||||
85
inc/encryption.php
Normal file
85
inc/encryption.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
class Encryption{
|
||||
|
||||
/**
|
||||
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
|
||||
*/
|
||||
function encryptText($text,$key)
|
||||
{
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
$ciphertext = sodium_crypto_secretbox($text, $nonce, $key);
|
||||
$encoded = base64_encode($nonce . $ciphertext);
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* $key must have been generated at some point with: random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES)
|
||||
*/
|
||||
function decryptText($encoded,$key)
|
||||
{
|
||||
$decoded = base64_decode($encoded);
|
||||
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
|
||||
|
||||
|
||||
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
|
||||
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
|
||||
*/
|
||||
function encryptFile($infile,$enc_outfile,$key)
|
||||
{
|
||||
$chunk_size = 4096;
|
||||
|
||||
$fd_in = fopen($infile, 'rb');
|
||||
$fd_out = fopen($enc_outfile, 'wb');
|
||||
|
||||
list($stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($key);
|
||||
|
||||
fwrite($fd_out, $header);
|
||||
|
||||
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
|
||||
do {
|
||||
$chunk = fread($fd_in, $chunk_size);
|
||||
if (feof($fd_in)) {
|
||||
$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
|
||||
}
|
||||
$encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
|
||||
fwrite($fd_out, $encrypted_chunk);
|
||||
} while ($tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
|
||||
|
||||
fclose($fd_out);
|
||||
fclose($fd_in);
|
||||
}
|
||||
|
||||
/**
|
||||
* $key must have been generated at some point with: sodium_crypto_secretstream_xchacha20poly1305_keygen();
|
||||
*/
|
||||
function decryptFile($enc_infile,$outfile,$key)
|
||||
{
|
||||
$fd_in = fopen($enc_infile, 'rb');
|
||||
$fd_out = fopen($outfile, 'wb');
|
||||
$chunk_size = 4096;
|
||||
|
||||
$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);
|
||||
|
||||
$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key);
|
||||
do {
|
||||
$chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
|
||||
list($decrypted_chunk, $tag) = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
|
||||
fwrite($fd_out, $decrypted_chunk);
|
||||
} while (!feof($fd_in) && $tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);
|
||||
$ok = feof($fd_in);
|
||||
|
||||
fclose($fd_out);
|
||||
fclose($fd_in);
|
||||
|
||||
if (!$ok) {
|
||||
die('Invalid/corrupted input');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,11 @@ interface StorageController
|
||||
* a folder that you might have to create first before putting the file in
|
||||
*
|
||||
* @param string $hash is the hash of the file that should be pulled from this storage system
|
||||
* @param string $location is the location where the downloaded file should be placed
|
||||
*
|
||||
* @return bool true if successful
|
||||
*/
|
||||
function pullFile($hash);
|
||||
function pullFile($hash,$location);
|
||||
|
||||
/**
|
||||
* Whenever a new file is uploaded this method will be called
|
||||
@@ -47,7 +48,7 @@ interface StorageController
|
||||
*
|
||||
* @return bool true if successful
|
||||
*/
|
||||
function pushFile($hash);
|
||||
function pushFile($source,$hash);
|
||||
|
||||
/**
|
||||
* If deletion of a file is requested, this method is called
|
||||
|
||||
@@ -22,6 +22,7 @@ PictShare has an extention system that allows handling of multiple storage solut
|
||||
|
||||
|Option | value type | What it does|
|
||||
|--- | --- | ---|
|
||||
|ENCRYPTION_KEY | base64 string | The key used to encrypt/decrypt files stored in storage controllers. See [/rtfm/ENCRYPTION.md] for setup guide |
|
||||
| ALT_FOLDER | string | All uploaded files will be copied to this location. This location can be a mounted network share (eg NFS or FTP, etc). If a file is not found in the normal upload direcotry, ALT_FOLDER will be checked. [more info about scaling PictShare](/rtfm/SCALING.md) |
|
||||
|S3_BUCKET | string | Name of your [S3 bucket](https://aws.amazon.com/s3/) |
|
||||
|S3_ACCESS_KEY | string | Access key for your bucket|
|
||||
|
||||
41
rtfm/ENCRYPTION.md
Normal file
41
rtfm/ENCRYPTION.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Encryption
|
||||
|
||||
As of Jan. 2020 you can set up an encryption key in your config which will encrypt all images stored on [external storage](/rtfm/CONFIG.md#storage-controllers)
|
||||
|
||||
The files on the PictShare server are not encrypted, only the ones on external storage providers, eg if you want to use S3 as storage.
|
||||
|
||||
## Dependencies
|
||||
|
||||
To be able to use encryption you'll need the following extensions:
|
||||
|
||||
- mb-string (`apt-get install php-mbstring`)
|
||||
- libsodium (`apt-get install php-libsodium`)
|
||||
|
||||
Since only files on [storage controllers](/rtfm/CONFIG.md#storage-controllers) are encrypted, you'll need to configure at least one.
|
||||
|
||||
## Preparation
|
||||
|
||||
First you'll need to generate a key and encode it in base64.
|
||||
The easiest way to get it would be to run this command:
|
||||
|
||||
`php -r "echo base64_encode(sodium_crypto_secretstream_xchacha20poly1305_keygen());"`
|
||||
|
||||
This will output something like `SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=`
|
||||
|
||||
Now put this output in your /inc/config.inc.php like this:
|
||||
|
||||
`define('ENCRYPTION_KEY','SSdoJvp10ZvOY5v+vAcprxQKjNX1AzD52cAnFwr6yXc=');`
|
||||
|
||||
**Warning: If you change or lose the ENCRYPTION_KEY, all encrypted data will be unrecoverably lost**
|
||||
|
||||
# How it works
|
||||
|
||||
If you have everything running you can upload a new image and it will get encrypted and uploaded to your storage container(s). This means you could even host on untrusted servers/buckets since nobody without the key will be able to decrypt it.
|
||||
|
||||
If you have uploaded a few files and see them on your storage container (eg S3) you'll notice the file has the '.enc' extension.
|
||||
|
||||
When you now wipe your PictShare instances local data folder and request the file again via the URL, the storage controller will pull the encrypted file, decrypt it and save it locally (unencrypted)
|
||||
|
||||
# Todo
|
||||
|
||||
- Automatically encrypt all existing (unencrypted) files on the storage controllers
|
||||
@@ -13,23 +13,26 @@ class AltfolderStorage implements StorageController
|
||||
return file_exists($altname);
|
||||
}
|
||||
|
||||
function pullFile($hash)
|
||||
function pullFile($hash,$location)
|
||||
{
|
||||
$altname=ALT_FOLDER.DS.$hash;
|
||||
if(file_exists($altname))
|
||||
{
|
||||
storeFile($altname,$hash,false);
|
||||
copy($altname,$location);
|
||||
}
|
||||
}
|
||||
|
||||
function pushFile($hash)
|
||||
function pushFile($source,$hash)
|
||||
{
|
||||
$altname=ALT_FOLDER.DS.$hash;
|
||||
$orig = ROOT.DS.'data'.DS.$hash.DS.$hash;
|
||||
if(file_exists($orig) && !$this->hashExists($hash))
|
||||
{
|
||||
copy($orig,$altname);
|
||||
}
|
||||
copy($source,$altname);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteFile($hash)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* (optional) S3_ENDPOINT
|
||||
*/
|
||||
|
||||
class S3Storage //implements StorageController
|
||||
class S3Storage implements StorageController
|
||||
{
|
||||
private $s3;
|
||||
function connect(){
|
||||
@@ -38,27 +38,28 @@ class S3Storage //implements StorageController
|
||||
return $this->s3->doesObjectExist(S3_BUCKET,$hash);
|
||||
}
|
||||
|
||||
function pullFile($hash)
|
||||
function pullFile($hash,$location)
|
||||
{
|
||||
if(!$this->s3)$this->connect();
|
||||
|
||||
if(!$this->hashExists($hash)) return false;
|
||||
|
||||
$this->s3->getObject([
|
||||
'Bucket' => S3_BUCKET,
|
||||
'Key' => $hash,
|
||||
'SaveAs' => ROOT.DS.'data'.DS.$hash.DS.$hash
|
||||
'SaveAs' => $location
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function pushFile($hash)
|
||||
function pushFile($source,$hash)
|
||||
{
|
||||
if(!$this->s3)$this->connect();
|
||||
|
||||
$this->s3->putObject([
|
||||
'Bucket' => S3_BUCKET,
|
||||
'Key' => $hash,
|
||||
'SourceFile' => ROOT.DS.'data'.DS.$hash.DS.$hash
|
||||
'SourceFile' => $source
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user