Add alternate idea/implementation
This commit is contained in:
parent
cdbefb1268
commit
02787044b6
|
@ -0,0 +1,22 @@
|
||||||
|
#### Here is the idea:
|
||||||
|
1. This script stores and returns binary blobs, so really, anything.
|
||||||
|
2. Every blob has extra attributes which can be set by additional parameters on creation, these are:
|
||||||
|
1. id (required), uniquely identifies each blob when combined with key
|
||||||
|
2. key (required), used to encrypt/decrypt blob on storage/access
|
||||||
|
3. file, the blob to encrypt and store, ideally this is already encrypted with a local key that never leaves your computer before sent to this script.
|
||||||
|
4. time-to-live (HOURS where 1 => time-to-live <= 24), if it hasn't been successfully accessed within X hours, all traces of it will be securely deleted (by a cronjob, not in PHP)
|
||||||
|
5. tmp (true/false), stores the blob in in-memory storage, with the hope that if the machine is powered off everything disappears
|
||||||
|
3. Sending in only an id and key will decrypt the blob and send it back to the browser, if nothing exists for that id/key, a new blob will be created from $new_blob_source with sent in parameters or defaults, stored, and sent back.
|
||||||
|
4. Sending in an id, key, and file will save (and overwrite if id/key was set before) the file to be served back when requested again, with optionally overridden defaults based on the other parameters sent in.
|
||||||
|
5. Every time a blob is successfully accessed (correct id and key), the time will be saved. This will be used by the secure deleting cronjob.
|
||||||
|
|
||||||
|
I am looking for feedback on how *secure* this idea is, if there are flaws in the approach or potential weaknesses I don't see, and ways to improve it.
|
||||||
|
|
||||||
|
Any improvements that can be made in the reference implementations will be appreciated as well.
|
||||||
|
|
||||||
|
#### In this repo
|
||||||
|
|
||||||
|
1. secureblob.php - Reference implementation in PHP
|
||||||
|
2. secureblob_cron.sh - Reference implementation of cleaning cronjob
|
||||||
|
3. secureblob_up.sh - Upload script to test reference implementations
|
||||||
|
4. agpl-3.0.txt - License all code is released under
|
|
@ -0,0 +1,258 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
secureblob.php https://github.com/moparisthebest/secureblob
|
||||||
|
Copyright (C) 2014 moparisthebest (Travis Burtrum)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
global $blob_path, $tmp_blob_path, $new_blob_source, $default_content_type, $max_size, $min_size;
|
||||||
|
$blob_path = '/tmp/secureblob/';
|
||||||
|
$tmp_blob_path = '/run/shm/secureblob/';
|
||||||
|
$new_blob_source = '/dev/urandom';
|
||||||
|
//$new_blob_source = '/dev/zero';
|
||||||
|
//$new_blob_source = '/run/shm/bob';
|
||||||
|
$default_content_type = 'application/octet-stream';
|
||||||
|
//$max_size = 4 * 1024 * 1024; // 4mb
|
||||||
|
$max_size = 64 * 1024; // 64kb
|
||||||
|
$min_size = 16;
|
||||||
|
|
||||||
|
// params for my encrypt/decrypt/stretch_key functions, if you write your own functions, ignore these params
|
||||||
|
global $mcrypt_cipher, $mcrypt_mode, $mcrypt_rand_src, $pbkdf2_hash, $pbkdf2_iterations, $salt_length;
|
||||||
|
$mcrypt_cipher = MCRYPT_RIJNDAEL_128;
|
||||||
|
//$mcrypt_cipher = MCRYPT_RIJNDAEL_256;
|
||||||
|
//$mcrypt_cipher = MCRYPT_BLOWFISH;
|
||||||
|
$mcrypt_mode = MCRYPT_MODE_CBC;
|
||||||
|
$mcrypt_rand_src = MCRYPT_DEV_RANDOM;
|
||||||
|
$pbkdf2_hash = 'sha512';
|
||||||
|
$pbkdf2_iterations = 65536;
|
||||||
|
$salt_length = 32;
|
||||||
|
|
||||||
|
// todo: include config file
|
||||||
|
|
||||||
|
function encrypt($decrypted, $password) {
|
||||||
|
global $mcrypt_cipher, $mcrypt_mode, $mcrypt_rand_src, $salt_length;
|
||||||
|
//echo "decrypted: '$decrypted'<br/>\n";
|
||||||
|
srand();
|
||||||
|
// first generate a random $salt_length character salt
|
||||||
|
$salt_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ’"\'~!@#$%^&*(){}[],./?';
|
||||||
|
$salt_chars_len = strlen($salt_chars);
|
||||||
|
$salt = '';
|
||||||
|
for ($i = 0; $i < $salt_length; $i++)
|
||||||
|
$salt .= $salt_chars[rand(0, $salt_chars_len)];
|
||||||
|
//echo "salt: '$salt'<br/>\n";
|
||||||
|
// stretch the key
|
||||||
|
$key = stretch_key($password, $salt, mcrypt_get_key_size($mcrypt_cipher, $mcrypt_mode));
|
||||||
|
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
|
||||||
|
$iv = mcrypt_create_iv(mcrypt_get_iv_size($mcrypt_cipher, $mcrypt_mode), $mcrypt_rand_src);
|
||||||
|
$iv_base64 = base64_encode($iv);
|
||||||
|
//echo "iv_base64: '$iv_base64'<br/>\n";
|
||||||
|
// Encrypt $decrypted plus a random character to be removed so we know the last character isn't \0
|
||||||
|
$encrypted = base64_encode(mcrypt_encrypt($mcrypt_cipher, $key, $decrypted . $salt_chars[rand(0, $salt_chars_len)], $mcrypt_mode, $iv));
|
||||||
|
//echo "encrypted: '$encrypted'<br/>\n";
|
||||||
|
// We're done!
|
||||||
|
return "$salt:$mcrypt_cipher:$mcrypt_mode:$iv_base64:$encrypted";
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrypt($encrypted, $password, $check_integrity = true) {
|
||||||
|
$encrypted = explode(':', $encrypted); // salt, mcrypt_cipher, mcrypt_mode, iv_base64, encrypted_base64
|
||||||
|
// retrieve $salt from $encrypted
|
||||||
|
$salt = $encrypted[0];
|
||||||
|
$mcrypt_cipher = $encrypted[1];
|
||||||
|
$mcrypt_mode = $encrypted[2];
|
||||||
|
// stretch the key
|
||||||
|
$key = stretch_key($password, $salt, mcrypt_get_key_size($mcrypt_cipher, $mcrypt_mode));
|
||||||
|
// Retrieve $iv which is base64_decoded.
|
||||||
|
$iv = base64_decode($encrypted[3]);
|
||||||
|
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any \0 character has to be padding.
|
||||||
|
$decrypted = rtrim(mcrypt_decrypt($mcrypt_cipher, $key, base64_decode($encrypted[4]), $mcrypt_mode, $iv), "\0\4");
|
||||||
|
// Remove the last character from $decrypted.
|
||||||
|
$decrypted = substr($decrypted, 0, -1);
|
||||||
|
// Yay!
|
||||||
|
return $decrypted;
|
||||||
|
}
|
||||||
|
function stretch_key($key, $salt, $max_key_size){
|
||||||
|
global $pbkdf2_hash, $pbkdf2_iterations;
|
||||||
|
//echo "max_key_size: '$max_key_size'<br/>\n";
|
||||||
|
//return hash('SHA256', $salt . $key, true);
|
||||||
|
return pbkdf2($pbkdf2_hash, $key, $salt, $pbkdf2_iterations, $max_key_size, true);
|
||||||
|
}
|
||||||
|
function secure_delete_file($filename){
|
||||||
|
$size = filesize($filename);
|
||||||
|
|
||||||
|
$src = fopen('/dev/zero', 'rb');
|
||||||
|
$dest = fopen($filename, 'wb');
|
||||||
|
|
||||||
|
stream_copy_to_stream($src, $dest, $size);
|
||||||
|
|
||||||
|
fclose($src);
|
||||||
|
fclose($dest);
|
||||||
|
unlink($filename);
|
||||||
|
}
|
||||||
|
function secure_delete_folder($path) {
|
||||||
|
$it = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($path),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
foreach ($it as $file) {
|
||||||
|
if (in_array($file->getBasename(), array('.', '..'))) {
|
||||||
|
continue;
|
||||||
|
} elseif ($file->isDir()) {
|
||||||
|
rmdir($file->getPathname());
|
||||||
|
} elseif ($file->isFile() || $file->isLink()) {
|
||||||
|
secure_delete_file($file->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rmdir($path);
|
||||||
|
}
|
||||||
|
function write_to_file($fname, $contents, $mode = 'w') {
|
||||||
|
$fh = fopen($fname, $mode);
|
||||||
|
fwrite($fh, $contents);
|
||||||
|
fclose($fh);
|
||||||
|
}
|
||||||
|
function encrypt_write($folder, $key, $decrypted = NULL){
|
||||||
|
if($decrypted === NULL)
|
||||||
|
$decrypted = create_blob();
|
||||||
|
if(!file_exists($folder)) {
|
||||||
|
// create directory
|
||||||
|
mkdir($folder, 0700, true);
|
||||||
|
// write attributes
|
||||||
|
$time_to_live = setDefaultLimits('time-to-live', 1, 1, 24);
|
||||||
|
write_to_file($folder.'time-to-live', $time_to_live);
|
||||||
|
}
|
||||||
|
write_to_file($folder.'blob', encrypt($decrypted, $key), 'wb');
|
||||||
|
return $decrypted;
|
||||||
|
}
|
||||||
|
// creates a blob from $new_blob_source
|
||||||
|
function create_blob(){
|
||||||
|
global $new_blob_source, $max_size, $min_size;
|
||||||
|
srand();
|
||||||
|
return file_get_contents($new_blob_source, false, NULL, 0, rand( (($max_size-$min_size)/2), $max_size)); // size is some random amount between halfway between max_size and min_size and max_size
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
|
||||||
|
* $algorithm - The hash algorithm to use. Recommended: SHA256
|
||||||
|
* $password - The password.
|
||||||
|
* $salt - A salt that is unique to the password.
|
||||||
|
* $count - Iteration count. Higher is better, but slower. Recommended: At least 1000.
|
||||||
|
* $key_length - The length of the derived key in bytes.
|
||||||
|
* $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise.
|
||||||
|
* Returns: A $key_length-byte key derived from the password and salt.
|
||||||
|
*
|
||||||
|
* Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
|
||||||
|
*
|
||||||
|
* This implementation of PBKDF2 was originally created by https://defuse.ca
|
||||||
|
* With improvements by http://www.variations-of-shadow.com
|
||||||
|
*/
|
||||||
|
function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
|
||||||
|
{
|
||||||
|
$algorithm = strtolower($algorithm);
|
||||||
|
if(!in_array($algorithm, hash_algos(), true))
|
||||||
|
trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
|
||||||
|
if($count <= 0 || $key_length <= 0)
|
||||||
|
trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
|
||||||
|
|
||||||
|
if (function_exists("hash_pbkdf2")) {
|
||||||
|
// The output length is in NIBBLES (4-bits) if $raw_output is false!
|
||||||
|
if (!$raw_output) {
|
||||||
|
$key_length = $key_length * 2;
|
||||||
|
}
|
||||||
|
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash_length = strlen(hash($algorithm, "", true));
|
||||||
|
$block_count = ceil($key_length / $hash_length);
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
for($i = 1; $i <= $block_count; $i++) {
|
||||||
|
// $i encoded as 4 bytes, big endian.
|
||||||
|
$last = $salt . pack("N", $i);
|
||||||
|
// first iteration
|
||||||
|
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
|
||||||
|
// perform the other $count - 1 iterations
|
||||||
|
for ($j = 1; $j < $count; $j++) {
|
||||||
|
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
|
||||||
|
}
|
||||||
|
$output .= $xorsum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($raw_output)
|
||||||
|
return substr($output, 0, $key_length);
|
||||||
|
else
|
||||||
|
return bin2hex(substr($output, 0, $key_length));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRequired($name){
|
||||||
|
if(!isset($_REQUEST[$name]))
|
||||||
|
die("All parameters must be set."); // intentionally vague
|
||||||
|
return $_REQUEST[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDefault($name, $default){
|
||||||
|
return isset($_REQUEST[$name]) ? $_REQUEST[$name] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function does no bounds checking on lower/upper, so don't send in user input
|
||||||
|
function setDefaultLimits($name, $default, $lower, $upper){
|
||||||
|
$ret = setDefault($name, $default);
|
||||||
|
if($ret < $lower)
|
||||||
|
return $lower;
|
||||||
|
if($ret > $upper)
|
||||||
|
return $upper;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = setRequired('id');
|
||||||
|
$key = setRequired('key');
|
||||||
|
|
||||||
|
$tmp = setDefault('tmp', true) !== 'false'; // default is true
|
||||||
|
|
||||||
|
$folder = ($tmp ? $tmp_blob_path : $blob_path).hash('sha512', $id . stretch_key($key, 'hau98grch348rcueoahic34hce.i', 128)).'/';
|
||||||
|
|
||||||
|
// I'd like the file to never get to the filesystem at all, any good solutions for this?
|
||||||
|
// http://stackoverflow.com/questions/5701508/storing-php-php-fpm-apaches-temporary-from-upload-files-in-ram-rather-than-th
|
||||||
|
$file = $_FILES['file'];
|
||||||
|
|
||||||
|
$decrypted = '';
|
||||||
|
if(isset($file)){
|
||||||
|
// then we want to SET a new file that was sent in
|
||||||
|
if($file['size'] > $max_size)
|
||||||
|
die('Max file size exceeded');
|
||||||
|
if($file['size'] < $min_size)
|
||||||
|
die('Min file size not reached');
|
||||||
|
|
||||||
|
$decrypted = file_get_contents($file['tmp_name']);
|
||||||
|
|
||||||
|
// delete unencrypted file and previous folder if it exists
|
||||||
|
secure_delete_file($file['tmp_name']);
|
||||||
|
secure_delete_folder($folder);
|
||||||
|
|
||||||
|
encrypt_write($folder, $key, $decrypted);
|
||||||
|
|
||||||
|
//echo "serving new real just sent in<br />\n";
|
||||||
|
} else if(!file_exists($folder)) {
|
||||||
|
// then we want to SET a new file we create from $new_blob_source
|
||||||
|
$decrypted = encrypt_write($folder, $key);
|
||||||
|
|
||||||
|
//echo "serving new real just generated<br />\n";
|
||||||
|
} else {
|
||||||
|
// otherwise we want to serve an existing file
|
||||||
|
$fname = $folder.'blob';
|
||||||
|
$decrypted = decrypt(file_get_contents($fname), $key);
|
||||||
|
touch($fname);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: '.setDefault('content-type', $default_content_type));
|
||||||
|
echo $decrypted;
|
||||||
|
//echo "decrypted: '$decrypted'<br/>\n";
|
||||||
|
?>
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# secureblob.php https://github.com/moparisthebest/secureblob
|
||||||
|
# Copyright (C) 2014 moparisthebest (Travis Burtrum)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
file_age_hours(){
|
||||||
|
file="$1"
|
||||||
|
from_date="$2"
|
||||||
|
[ -z "$from_date" ] && from_date="$(date +%s)"
|
||||||
|
seconds_diff="$(($from_date - $(stat -c '%Y' "$file")))"
|
||||||
|
echo "$(($seconds_diff/60/60))"
|
||||||
|
}
|
||||||
|
|
||||||
|
from_date="$(date +%s)"
|
||||||
|
find /tmp/secureblob /run/shm/secureblob -type f -name blob -mmin +60 | while read file
|
||||||
|
do
|
||||||
|
dir="$(dirname "$file")"
|
||||||
|
[ "$(file_age_hours "$file" "$from_date")" -lt "$(cat "$dir/time-to-live")" ] 2>/dev/null || {
|
||||||
|
# done this way so if time-to-live isn't a proper number we delete everything
|
||||||
|
find "$dir" -type f -exec shred --force --remove '{}' \;
|
||||||
|
rm -rf "$dir"
|
||||||
|
}
|
||||||
|
done
|
||||||
|
exit
|
||||||
|
|
||||||
|
# set up tests
|
||||||
|
rm -rf /run/shm/secureblob /tmp/secureblob
|
||||||
|
mkdir -p /run/shm/secureblob /tmp/secureblob /tmp/secureblob/bob /tmp/secureblob/tom
|
||||||
|
touch /tmp/secureblob/tom/blob
|
||||||
|
echo 1 > /tmp/secureblob/tom/time-to-live
|
||||||
|
touch --date='3 hours ago' /tmp/secureblob/bob/blob
|
||||||
|
echo 2 > /tmp/secureblob/bob/time-to-live
|
||||||
|
# end tests
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
url="$1"
|
||||||
|
[ -z "$url" ] && echo "Must specify URL" && exit 1
|
||||||
|
file="$2"
|
||||||
|
[ -z "$file" ] && file="${BASH_SOURCE[0]}"
|
||||||
|
echo "uploading '$file' to '$url'"
|
||||||
|
sha1sum < "$file"
|
||||||
|
curl -s -F "file=@$file" -F 'id=bob' -F 'key=bob' "$url" | sha1sum
|
||||||
|
curl -s -F 'id=bob' -F 'key=bob' "$url" | sha1sum
|
Loading…
Reference in New Issue