Skip to content

Commit

Permalink
Added encryption based on Libsodium
Browse files Browse the repository at this point in the history
  • Loading branch information
geek-at committed Jan 7, 2020
1 parent 6cab15f commit 021fbad
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 44 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 1 addition & 7 deletions api/base64.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,7 @@
$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);
Expand Down
16 changes: 2 additions & 14 deletions api/geturl.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,7 @@
}


// 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')
Expand All @@ -98,13 +92,7 @@
$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);
Expand Down
8 changes: 1 addition & 7 deletions api/upload.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,7 @@
}


// 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);
Expand Down
50 changes: 46 additions & 4 deletions inc/core.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
85 changes: 85 additions & 0 deletions inc/encryption.php
Original file line number Diff line number Diff line change
@@ -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');
}
}
}
5 changes: 3 additions & 2 deletions interfaces/storagecontroller.interface.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ function hashExists($hash);
* 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
Expand All @@ -47,7 +48,7 @@ function pullFile($hash);
*
* @return bool true if successful
*/
function pushFile($hash);
function pushFile($source,$hash);

/**
* If deletion of a file is requested, this method is called
Expand Down
1 change: 1 addition & 0 deletions rtfm/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down
41 changes: 41 additions & 0 deletions rtfm/ENCRYPTION.md
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 5 deletions storage-controllers/altfolder.controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,26 @@ function hashExists($hash)
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)
Expand Down
11 changes: 6 additions & 5 deletions storage-controllers/s3.controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* (optional) S3_ENDPOINT
*/

class S3Storage //implements StorageController
class S3Storage implements StorageController
{
private $s3;
function connect(){
Expand Down Expand Up @@ -38,27 +38,28 @@ function hashExists($hash)
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;
Expand Down

0 comments on commit 021fbad

Please sign in to comment.