最終回となる今回は、
Megauploadの閉鎖と復活
2012年1月19日、
Megauploadのようなオンラインストレージサービスを、
- 大容量のファイルをアップロードすることが可能である
- 同時ダウンロード数や速度が制限されていて、
有料会員登録することでダウンロード速度が速くなる - ファイルをアップロードすることで報酬を得られるプログラムがある
実態として、
知名度が高く、
1年後の2013年1月19日、
サービス運営者にも中身がわからない
サービス運営者にも中身がわからない、
Megaは、
Megaに学ぶクライアントサイド技術
というわけでMegaのようなクライアントサイドでの暗号化/
このサンプルには、
- ファイルはブラウザ上で暗号化/
復号化を行う - 復号にはパスワードが必要だが、
パスワードをlocation. hash [3] を使って受け渡すことで負担なくダウンロードできる
それではアップロードとダウンロードに分けて、
暗号化してアップロード
ファイルを暗号化してアップロードするには
- ローカルファイルを読み取る
- 暗号化する
- アップロードする
という3つの処理が必要です。最近のPCとブラウザであれば、
ファイルを受け取る
ブラウザからのファイルアップロードと言えば、input type = file
を使うことで行えます。
今までは、
HTML5にてFile APIが整備されたことで、input type = file
によって選択されたファイル、
分割して読み取る
File APIにより、
reader = new FileReader;
reader.onload = function(){
reader.result;
};
reader.readAsBinaryString(file);
Fileオブジェクトが作られた段階では、
単純にFileオブジェクトをFileReaderを使って読み込む場合、
クラス名 | 役割 |
---|---|
Blob | サイズ固定、 |
File | Blobにファイル用のプロパティが備わったもの |
FileReader | Blob、 |
分割して暗号化する
それでは、
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });
var ciphertextPart1 = aesEncryptor.process("Message Part 1"); ┓
var ciphertextPart2 = aesEncryptor.process("Message Part 2"); |
var ciphertextPart3 = aesEncryptor.process("Message Part 3"); ┛(1)
var ciphertextPart4 = aesEncryptor.finalize(); (2)
var aesDecryptor = CryptoJS.algo.AES.createDecryptor(key, { iv: iv });
var plaintextPart1 = aesDecryptor.process(ciphertextPart1); ┓
var plaintextPart2 = aesDecryptor.process(ciphertextPart2); |
var plaintextPart3 = aesDecryptor.process(ciphertextPart3); |
var plaintextPart4 = aesDecryptor.process(ciphertextPart4); |
var plaintextPart5 = aesDecryptor.finalize(); ┛(3)
</script>
通常の暗号化が文字列とパスワードを一度に渡し、process
finalize
暗号化したそばからアップロード
大容量のファイルをサーバに直接アップロードする場合、
# app.psgi
# plackup --port=5000 app.psgi
use strict;
use Plack::Builder;
use Plack::Request;
use Plack::App::Directory;
use Path::Class;
use Digest::MD5 qw(md5_hex);
my $upload_dir = "./files/";
builder {
mount "/files" => Plack::App::File->new(
root => $upload_dir);
mount "/upload" => sub {
my $env = shift;
my $req = Plack::Request->new($env);
if ($req->method eq "POST") {
my $file = file(
$upload_dir . md5_hex($req->param("key")));
my $appender = $file->open('a') or die $!;
$appender->print($req->raw_body);
$appender->close;
}
[200, [], ["OK"]];
};
mount "/delete" => sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $success;
if ($req->method eq "POST") {
my $file = file(
$upload_dir . md5_hex($req->param("key")));
$success = $file->remove;
}
[200, [], [ $success ? "OK" : "NG" ]];
};
mount "/" => sub {
my $env = shift;
if ($env->{PATH_INFO} eq "/") {
return Plack::App::File->new(
file => './static/index.html')->call($env);
}
Plack::App::File->new(root => './static/')->call($env);
};
};
サーバ側での実装に合わせて形式は自由に決めてもよいのですが、
以上が暗号化してアップロードするまでの流れです。次は、
function StreamUploader(api){
this.uniq_key = random_str(20);
this.num = 0;
this.api = api;
this.busy = false;
this.queue = [];
}
StreamUploader.prototype = {
// TODO: retry
upload_binary: function(binary){ // typed array
var self = this;
var task = function() {
self.busy = true;
var xhr = new XMLHttpRequest;
console.log("upload: " + binary.length + " bytes");
var param = "num=" + self.num + "&" + "key=" + self.uniq_key;
self.num++;
xhr.onload = function(){
setTimeout(function(){ self.busy = false }, 1000) };
xhr.open("POST", self.api + "?" + param, true);
xhr.send(binary.buffer);
};
this.queue.push(task);
this.dequeue();
},
dequeue: function(){
var self = this;
if (!this.queue.length) return;
if (this.busy) {
setTimeout(function(){
self.dequeue();
}, 1000);
} else {
var task = this.queue.shift();
task();
}
}
};