Burnmsg Application in Laravel (Tutorial)
While brain storming one day, I decided to go ahead and build a self-destructing, encrypted messaging application in Laravel, using PHP's built-in mcrypt library to see how hard it would be. Turns out it isn't very hard at all.
First we need to download Laravel and setup the base of our project. We'll call it 'Burnmsg'.
$ composer create-project laravel/laravel burnmsg
Now let's setup our composer.json file to fetch some dependencies. We'll use Twitter's bootstrap for styling and Jeffrey Way's time-saving artisan generators.
{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"require": {
"laravel/framework": "4.0.*",
"twitter/bootstrap": "3.0.*"
"way/generators": "dev-master"
},
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
]
},
"scripts": {
"post-install-cmd": [
"php artisan optimize"
],
"post-update-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-create-project-cmd": [
"php artisan key:generate"
]
},
"config": {
"preferred-install": "dist"
},
"minimum-stability": "dev"
}
Make sure you add the generators service provider to 'app/config/app.php' in the 'providers' array.
<?php
'providers' = array(
'...',
'Way\Generators\GeneratorsServiceProvider'
),
Change your database configuration in 'app/config/database.php'. Once that's done we can generate our model, controller and views all in one command using the useful generators tool.
$ php artisan generate:resource --fields="body:binary,url:string,iv:binary,destroyed:boolean" messages
Now we can run our migration to create the messages table.
$ php artisan migrate
Next let's set up our routes. We only need a few. One for the message form. One to post the data and one to view our message.
<?php
Route::get('/', ['as' => 'messages.create', 'uses' => 'MessagesController@create']);
Route::post('/', ['as' => 'messages.store', 'uses' => 'MessagesController@store']);
Route::get('/{key}/{value}', ['as' => 'messages.show', 'uses' => 'MessagesController@show']);
In our controller we will only need a create, store and show method. The create method will return the view for displaying our form.
<?php
public function create()
{
return View::make('messages.create');
}
The store method will take the input and validate it. In this case we only care that the message isn't left blank. Next we will generate a random key. Then we will generate an IV or initialization vector. This is used to improve the randomization of our encryption. In this case we will use the Blowfish algorithm. The last steps are to encrypt the message and then save the encrypted message and the IV for decryption later. We will also create a random identifier. The key will not be saved though. Instead it will be part of the URL for retrieving the message later. Because the key is part of the URL the message creator needs to keep this as safe as possible. This also prevents the person running the server from being able to read any of the messages. Plausible-deniability. Finally we return the view which displays the URL for our message.
<?php
public function store()
{
// Validate input
$v = Message::validate(Input::all());
if ($v !== true) {
return Redirect::route('messages.create')
->withErrors($v->errors());
}
$msg = new Message;
// Generate a key
$key = sha1(microtime(true).mt_rand(10000,90000));
// Generate an IV
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);
// Encrypt the message
$body = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, Input::get('body'), MCRYPT_MODE_CFB, $iv);
// Save the message to the database
$msg->body = $body;
$msg->url = Message::get_unique_url();
$msg->iv = $iv;
$msg->save();
// Return the view
return View::make('messages.store', ['url' => $msg->url, 'key' => $key]);
}
The show method will take the random identifier and the key and decrypt the message. Once decrypted it will overwrite the message with 'null' in the database and return the view displaying the one-time-viewable message.
<?php
public function show($url, $key)
{
// Fetch our message
$msg = Message::where('url', '=', $url)->first();
if ($msg->destroyed) {
$body = "This message has been destroyed";
} else {
// Decrypt it
$iv = $msg->iv;
$body = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $msg->body, MCRYPT_MODE_CFB, $iv);
// Destroy the message
$msg->body = null;
$msg->destroyed = true;
$msg->save();
}
return View::make('messages.show', ['body' => $body]);
}
Now let's take care of our model. The model will handle the validation as well as generating the random url identifier saved in the store method above.
<?php
class Message extends Eloquent {
protected $guarded = ['id', 'created_at', 'updated_at'];
public static $rules = ['body' => 'required'];
public static function validate($input)
{
$v = Validator::make($input, static::$rules);
return $v->fails() ? $v : true;
}
public static function get_unique_url() {
// set a random number
$number = rand(10000, 9999999);
// character list for generating a random string
$charlist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$decimal = intval($number);
//get the list of valid characters
$charlist = substr($charlist, 0, 62);
$converted = '';
while($number > 0) {
$converted = $charlist{($number % 62)} . $converted;
$number = floor($number / 62);
}
if( static::whereUrl($converted)->first() ) {
static::get_unique_url();
}
return $converted;
}
}
Now we have most of the hard part out of the way. All that is left is to create and style the various views using Twitter Bootstrap which will be covered Below.
The first thing we'll need to do is create a default layout. This is the layout that the rest of our views will extend. Let's save it as 'app/views/layouts/default.blade.php'.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<title>Burnmsg - Self-desctructing, encrypted messages</title>
<!-- Bootstrap core CSS -->
<link href="" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="">Burnmsg</a>
</div>
</div>
</div>
<div class="container">
@yield('content')
</div><!-- /.container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="></script>
</body>
</html>
Now we need to make sure that Twitter Bootstrap is accessible. Bootstrap is installed into the 'vendor' folder of our project by composer. First let's copy the Javascript libraries.
$ cp vendor/twitter/bootstrap/dist/js/bootstrap.min.js public/js
We also need the CSS stylesheet but we are going to need to tweak some of the styles. We are going to do this using LESS.
$ touch public/css/styles.less
In our newly created LESS file we will import the original stylesheet and make our tweaks as needed.
@import "../../vendor/twitter/bootstrap/less/bootstrap";
body {
padding-top: 65px; /* bring body to bottom of navbar */
}
article {
white-space: pre-wrap;
}
LESS makes it easy to work with CSS but it needs to be "compiled" in order for the browser to understand it.
$ lessc public/css/styles.less > public/css/styles.css
I use 'lessc' which is a Ruby gem but you can use any LESS compiler. Google should bring up a handful of options.
Now all that is left is to create our various views which correspond to the different actions involved in our application. Let's save these views in 'app/views/messages'.
The first view 'create.blade.php' will represent our message creation form. Notice that it '@extends' our default layout and renders the form within the 'content' section which is referenced in the default layout.
@extends('layouts.default')
@section('content')
{{ Form::open(['method' => 'post', 'route' => 'messages.store', 'class' => 'form']) }}
<div class="form-group">
@if($errors->first())
<div class="alert alert-danger">
{{ $errors->first() }}
</div>
@endif
{{ Form::label('body', 'Message') }}
{{ Form::textarea('body', null, ['class' => 'form-control']) }}
</div>
<div class="form-group">
{{ Form::submit('Submit', ['class' => 'btn btn-primary']) }}
{{ Form::close() }}
</div>
@stop
Once the message is submitted our 'store.blade.php' view will display a link for the user to copy and give to whomever they wish for later reading.
@extends('layouts.default')
@section('content')
<div class="alert alert-success">
Your message has been saved.
Here is the URL <a href="{{ route('messages.show', [$url, $key]) }}">{{ route('messages.show', [$url, $key]) }}</a>
</div>
@stop
Lastly the 'show.blade.php' view will display the message for the user once they decide to read it.
@extends('layouts.default')
@section('content')
<article>
{{{ $body }}}
</article>
@stop
Now we should have a pretty sweet messaging app that destroys your message upon reading. Keep in mind that I am no cryptography or digital forensics expert so rely on this application at your own risk. Go ahead and modify it to make it more secure if you like. Leave any suggestions you may have in the comments.
0 comments:
Post a Comment