A Secure File Transfer

Lately I have been interested in encryption, and I was curious if it would be possible to encrypt in javascript, send to server, then download it, while the decryption/encryption is handled on the client end.

I was more or less able to do this in a few hours: https://quickyfile.com While the site itself doesn’t have looks the power behind it is real.

It harnesses HTML5 and cryptojs to do the lifting. Uploading ONLY has been tested and working in Firefox and Chrome, and Chrome chokes on large downloads.

Design was easy:

<html>
<head>
<title>Quickyfile, upload encryted files, quickly</title>
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/rollups/aes.js"></script>
<script>
function encryptFile() {
	var fileInput = document.getElementById('file');
	var file = fileInput.files[0]
	var reader = new FileReader();
	var password = Math.random().toString(36).slice(-8);
	reader.onload = function(e) {
		document.getElementById('wait').innerHTML="Please Wait Encrypting";
		var encrypted = CryptoJS.AES.encrypt(e.target.result, password);
		document.getElementById('wait').innerHTML="File Encryption Finished";
		console.log([encrypted]);
		var data = new FormData($("#fileinfo")[0]);
		var encryptedFile = new File([encrypted], file.name + '.encrypted', {type: "text/plain", lastModified: new Date()});
		data.append('file[0]', encryptedFile);
		document.getElementById('wait').innerHTML="File Upload Starting";
		$.ajax({
			url: 'upload.php',
			data: data,
			cache: false,
			contentType: false,
			processData: false,
			type: 'POST',
			success: function (data) {
				document.getElementById('wait').innerHTML="File Upload Done";
				document.getElementById('status').innerHTML="Send this in your email: " + data +"&password="+password;
     			}
 		});
	};
	reader.readAsDataURL(file);
}
</script>
</head>
<body>
 
<input type="file" name="file" id="file"><br />
<input type="button" id="upload" value="Upload File" onClick="encryptFile()"><br />
<span id="wait"></span><br /><br />
<span id="status"></span>
</body>
</html>

So what happens… When the upload button is pushed, encryptFile() is executed. We generate a random password (maybe not so random) in the browser. We read the base64 or the file and then launch into readFile, all while updating our span. We pass into cryptojs and encrypt the file into AES. After encryption we make a fake form to submit (using jQuery) and do an AJAX call to upload.php

<?php
$connect = mysql_connect("localhost","xxx","xxx") or die (mysql_error());
$db = mysql_select_db("quickyfile",$connect);
 
if (!isset($_FILES["file"])) {
	exit;
}
 
$tmpName = $_FILES["file"]['tmp_name'][0];
 
if ($tmpName == "") {
	exit;
}
 
$fp      = fopen($tmpName, 'r');
$content = fread($fp, filesize($tmpName));
$content = addslashes($content);
fclose($fp);
 
$ok = false;
$uuid="";
 
while (!$ok) {
	$uuid = gen_uuid();
	$sql = "SELECT count(*) from files where uuid=\"$uuid\"";
	$result = mysql_query($sql,$connect);
	$row = mysql_fetch_row($result);
	if ($row[0] == 0) {
		$ok = true;
	}
}
 
$sql = "INSERT into files (uuid,file) values (\"$uuid\",\"$content\")";
$result = mysql_query($sql,$connect) or die (mysql_error());
 
 
echo "https://quickyfile.com/download.php?uuid=$uuid";
 
function gen_uuid() {
	return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),mt_rand( 0, 0xffff ),mt_rand( 0, 0x0fff ) | 0x4000,mt_rand( 0, 0x3fff ) | 0x8000,mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ));
}
?>

I made probably not the best choice to store the file in a database, but I was too lazy to protect a directory. So the file is uploaded to the server, at this point the server ONLY sees encrypted data. The server generates a UUID and passes it back, this is how we know which file to download.

Now the system will spit out a download like like:

Screen Shot 2015-03-29 at 10.07.46 PM

You can also click here in a compatible browser to see it.

So what happens on the download?

<html>
<head>
<title>Quickyfile, Download Encrypted files quick.</title>
<script src="js/jquery-1.11.2.min.js"></script>
<script src="js/rollups/aes.js"></script>
<script>
$( document ).ready(function() {
	document.getElementById('status').innerHTML = "Starting Download";
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function(){
    if (this.readyState == 4 && this.status == 200){
	//var output = this.response;
	var decrypted = CryptoJS.AES.decrypt(this.response, "<?php echo $_GET['password']?>");
	//document.getElementById('status').innerHTML = decrypted.toString(CryptoJS.enc.Utf8);
	var d = decrypted.toString(CryptoJS.enc.Utf8);
	var a = document.getElementById('download'); //or grab it by tagname etc
	a.href = d;
 
 
 
    }
}
document.getElementById
xhr.open('GET', 'file.php?uuid=<?php echo $_GET['uuid'] ?>');
xhr.send();    
 
});
 
</script>
 
</head>
<body>
<span id="status"></span>
<br />
<a id="download" download>Download</a>
</body>
</html>

On the download we wait for the document to be ready, once it’s ready, we literally use ajax to download the file in the background and save it to html5 storage.

<?php
$connect = mysql_connect("localhost","xxx","xxx") or die (mysql_error());
$db = mysql_select_db("quickyfile",$connect);
$uuid = $_GET['uuid'];
$sql = "SELECT file from files where uuid=\"$uuid\"";
$result = mysql_query($sql,$connect);
$row = mysql_fetch_row($result);
$row[0] = stripslashes($row[0]);
echo $row[0];
?>

file.php sends the base64 data to download.php

back to download.php we used the passed password to decrypt the file.

The buggiest part of the thing is the way we have to download the file. We actually pass the base64 to the href of the download link, I have seen this make chrome choke.

Here is the MySQL database:

CREATE TABLE `files` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `uuid` VARCHAR(255) DEFAULT NULL,
  `file` longblob,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uuid` (`uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

Video for good measure:

Thanks for reading!

–John

Leave a Reply

Your email address will not be published. Required fields are marked *