diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php
index 0aafcad559a2773e2649ea07bdbd3b9e39da5836..74f0e337a1294d1d38a7263ec3a956fe7da29a9c 100644
--- a/apps/files_external/appinfo/app.php
+++ b/apps/files_external/appinfo/app.php
@@ -18,6 +18,7 @@ OC::$CLASSPATH['OC\Files\Storage\SMB_OC'] = 'files_external/lib/smb_oc.php';
 OC::$CLASSPATH['OC\Files\Storage\AmazonS3'] = 'files_external/lib/amazons3.php';
 OC::$CLASSPATH['OC\Files\Storage\Dropbox'] = 'files_external/lib/dropbox.php';
 OC::$CLASSPATH['OC\Files\Storage\SFTP'] = 'files_external/lib/sftp.php';
+OC::$CLASSPATH['OC\Files\Storage\SFTP_Key'] = 'files_external/lib/sftp_key.php';
 OC::$CLASSPATH['OC_Mount_Config'] = 'files_external/lib/config.php';
 OC::$CLASSPATH['OCA\Files\External\Api'] = 'files_external/lib/api.php';
 
@@ -177,5 +178,17 @@ OC_Mount_Config::registerBackend('\OC\Files\Storage\SFTP', array(
 		'password' => '*'.$l->t('Password'),
 		'root' => '&'.$l->t('Remote subfolder'))));
 
+OC_Mount_Config::registerBackend('\OC\Files\Storage\SFTP_Key', [
+	'backend' => 'SFTP with secret key login',
+	'priority' => 100,
+	'configuration' => array(
+		'host' => (string)$l->t('Host'),
+		'user' => (string)$l->t('Username'),
+		'public_key' => (string)$l->t('Public key'),
+		'private_key' => '#private_key',
+		'root' => '&'.$l->t('Remote subfolder')),
+	'custom' => 'sftp_key',
+	]
+);
 $mountProvider = new \OCA\Files_External\Config\ConfigAdapter();
 \OC::$server->getMountProviderCollection()->registerProvider($mountProvider);
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1605bb98a87adaf691d9afbb98c0f02178afa7c
--- /dev/null
+++ b/apps/files_external/appinfo/application.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) 2015 University of Edinburgh <Ross.Nicoll@ed.ac.uk>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_External\Appinfo;
+
+use \OCA\Files_External\Controller\AjaxController;
+use \OCP\AppFramework\App;
+use \OCP\IContainer;
+
+ /**
+  * @package OCA\Files_External\Appinfo
+  */
+class Application extends App {
+	public function __construct(array $urlParams=array()) {
+		parent::__construct('files_external', $urlParams);
+		$container = $this->getContainer();
+
+		/**
+		 * Controllers
+		 */
+		$container->registerService('AjaxController', function (IContainer $c) {
+			return new AjaxController(
+				$c->query('AppName'),
+				$c->query('Request')
+			);
+		});
+	}
+}
diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php
index b852b34c5d32574819bc0b93df930d36c90c6580..5c7c4eca9099b49ea76cec713f410fa5d63e40e9 100644
--- a/apps/files_external/appinfo/routes.php
+++ b/apps/files_external/appinfo/routes.php
@@ -20,6 +20,23 @@
  *
  */
 
+namespace OCA\Files_External\Appinfo;
+
+$application = new Application();
+$application->registerRoutes(
+        $this,
+        array(
+                'routes' => array(
+                        array(
+                                'name' => 'Ajax#getSshKeys',
+                                'url' => '/ajax/sftp_key.php',
+                                'verb' => 'POST',
+                                'requirements' => array()
+                        )
+                )
+        )
+);
+
 /** @var $this OC\Route\Router */
 
 $this->create('files_external_add_mountpoint', 'ajax/addMountPoint.php')
@@ -37,10 +54,11 @@ $this->create('files_external_dropbox', 'ajax/dropbox.php')
 $this->create('files_external_google', 'ajax/google.php')
 	->actionInclude('files_external/ajax/google.php');
 
+
 $this->create('files_external_list_applicable', '/applicable')
 	->actionInclude('files_external/ajax/applicable.php');
 
-OC_API::register('get',
+\OC_API::register('get',
 		'/apps/files_external/api/v1/mounts',
 		array('\OCA\Files\External\Api', 'getUserMounts'),
 		'files_external');
diff --git a/apps/files_external/controller/ajaxcontroller.php b/apps/files_external/controller/ajaxcontroller.php
new file mode 100644
index 0000000000000000000000000000000000000000..141fc7817d29bca72e2a00e937e5e75f813442b6
--- /dev/null
+++ b/apps/files_external/controller/ajaxcontroller.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright (c) 2015 University of Edinburgh <Ross.Nicoll@ed.ac.uk>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_External\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\IRequest;
+use OCP\AppFramework\Http\JSONResponse;
+
+class AjaxController extends Controller {
+	public function __construct($appName, IRequest $request) {
+		parent::__construct($appName, $request);
+	}
+
+	private function generateSshKeys() {
+		$rsa = new \Crypt_RSA();
+		$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_OPENSSH);
+		$rsa->setPassword(\OC::$server->getConfig()->getSystemValue('secret', ''));
+
+		$key = $rsa->createKey();
+		// Replace the placeholder label with a more meaningful one
+		$key['publicKey'] = str_replace('phpseclib-generated-key', gethostname(), $key['publickey']);
+
+		return $key;
+	}
+
+	/**
+	 * Generates an SSH public/private key pair.
+	 *
+	 * @NoAdminRequired
+	 */
+	public function getSshKeys() {
+		$key = $this->generateSshKeys();
+		return new JSONResponse(
+			array('data' => array(
+				'private_key' => $key['privatekey'],
+				'public_key' => $key['publickey']
+			),
+			'status' => 'success'
+		));
+	}
+
+}
diff --git a/apps/files_external/js/sftp_key.js b/apps/files_external/js/sftp_key.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b39628247c207c54e3e95d99a9583a7c66909a0
--- /dev/null
+++ b/apps/files_external/js/sftp_key.js
@@ -0,0 +1,53 @@
+$(document).ready(function() {
+
+	$('#externalStorage tbody tr.\\\\OC\\\\Files\\\\Storage\\\\SFTP_Key').each(function() {
+		var tr = $(this);
+		var config = $(tr).find('.configuration');
+		if ($(config).find('.sftp_key').length === 0) {
+			setupTableRow(tr, config);
+		}
+	});
+
+	// We can't catch the DOM elements being added, but we can pick up when
+	// they receive focus
+	$('#externalStorage').on('focus', 'tbody tr.\\\\OC\\\\Files\\\\Storage\\\\SFTP_Key', function() {
+		var tr = $(this);
+		var config = $(tr).find('.configuration');
+
+		if ($(config).find('.sftp_key').length === 0) {
+			setupTableRow(tr, config);
+		}
+	});
+
+	$('#externalStorage').on('click', '.sftp_key', function(event) {
+		event.preventDefault();
+		var tr = $(this).parent().parent();
+		generateKeys(tr);
+	});
+
+	function setupTableRow(tr, config) {
+		$(config).append($(document.createElement('input')).addClass('button sftp_key')
+			.attr('type', 'button')
+			.attr('value', t('files_external', 'Generate keys')));
+		// If there's no private key, build one
+		if (0 === $(config).find('[data-parameter="private_key"]').val().length) {
+			generateKeys(tr);
+		}
+	}
+
+	function generateKeys(tr) {
+		var config = $(tr).find('.configuration');
+
+		$.post(OC.filePath('files_external', 'ajax', 'sftp_key.php'), {}, function(result) {
+			if (result && result.status === 'success') {
+				$(config).find('[data-parameter="public_key"]').val(result.data.public_key);
+				$(config).find('[data-parameter="private_key"]').val(result.data.private_key);
+				OC.MountConfig.saveStorage(tr, function() {
+					// Nothing to do
+				});
+			} else {
+				OC.dialogs.alert(result.data.message, t('files_external', 'Error generating key pair') );
+			}
+		});
+	}
+});
diff --git a/apps/files_external/l10n/en_GB.json b/apps/files_external/l10n/en_GB.json
index 21a5881c94e4197ed91ea841e0403c4ae06bee70..4b4da2f2cb51f6fbdabb87949ddd0cc4c67e02e2 100644
--- a/apps/files_external/l10n/en_GB.json
+++ b/apps/files_external/l10n/en_GB.json
@@ -69,4 +69,4 @@
     "Enable User External Storage" : "Enable User External Storage",
     "Allow users to mount the following external storage" : "Allow users to mount the following external storage"
 },"pluralForm" :"nplurals=2; plural=(n != 1);"
-}
\ No newline at end of file
+}
diff --git a/apps/files_external/lib/sftp.php b/apps/files_external/lib/sftp.php
index f6c56669734e1e4defe1a3644e0173694d83077d..2a762ad068fb342832c661baa775461e1b9e54c3 100644
--- a/apps/files_external/lib/sftp.php
+++ b/apps/files_external/lib/sftp.php
@@ -20,7 +20,7 @@ class SFTP extends \OC\Files\Storage\Common {
 	/**
 	* @var \Net_SFTP
 	*/
-	private $client;
+	protected $client;
 
 	private static $tempFiles = array();
 
@@ -42,7 +42,8 @@ class SFTP extends \OC\Files\Storage\Common {
 			$this->host = substr($this->host, $proto+3);
 		}
 		$this->user = $params['user'];
-		$this->password = $params['password'];
+		$this->password
+			= isset($params['password']) ? $params['password'] : '';
 		$this->root
 			= isset($params['root']) ? $this->cleanPath($params['root']) : '/';
 
@@ -101,6 +102,18 @@ class SFTP extends \OC\Files\Storage\Common {
 		return 'sftp::' . $this->user . '@' . $this->host . '/' . $this->root;
 	}
 
+	public function getHost() {
+		return $this->host;
+	}
+
+	public function getRoot() {
+		return $this->root;
+	}
+
+	public function getUser() {
+		return $this->user;
+	}
+
 	/**
 	 * @param string $path
 	 */
@@ -121,7 +134,7 @@ class SFTP extends \OC\Files\Storage\Common {
 		return false;
 	}
 
-	private function writeHostKeys($keys) {
+	protected function writeHostKeys($keys) {
 		try {
 			$keyPath = $this->hostKeysPath();
 			if ($keyPath && file_exists($keyPath)) {
@@ -137,7 +150,7 @@ class SFTP extends \OC\Files\Storage\Common {
 		return false;
 	}
 
-	private function readHostKeys() {
+	protected function readHostKeys() {
 		try {
 			$keyPath = $this->hostKeysPath();
 			if (file_exists($keyPath)) {
diff --git a/apps/files_external/lib/sftp_key.php b/apps/files_external/lib/sftp_key.php
new file mode 100644
index 0000000000000000000000000000000000000000..6113f88a8ff3183a3b7a2adbd9d009582c8cb030
--- /dev/null
+++ b/apps/files_external/lib/sftp_key.php
@@ -0,0 +1,194 @@
+<?php
+/**
+ * Copyright (c) 2014, 2015 University of Edinburgh <Ross.Nicoll@ed.ac.uk>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+namespace OC\Files\Storage;
+
+/**
+* Uses phpseclib's Net_SFTP class and the Net_SFTP_Stream stream wrapper to
+* provide access to SFTP servers.
+*/
+class SFTP_Key extends \OC\Files\Storage\SFTP {
+	private $publicKey;
+	private $privateKey;
+
+	public function __construct($params) {
+		parent::__construct($params);
+		$this->publicKey = $params['public_key'];
+		$this->privateKey = $params['private_key'];
+	}
+
+	/**
+	 * Returns the connection.
+	 *
+	 * @return \Net_SFTP connected client instance
+	 * @throws \Exception when the connection failed
+	 */
+	public function getConnection() {
+		if (!is_null($this->client)) {
+			return $this->client;
+		}
+
+		$hostKeys = $this->readHostKeys();
+		$this->client = new \Net_SFTP($this->getHost());
+
+		// The SSH Host Key MUST be verified before login().
+		$currentHostKey = $this->client->getServerPublicHostKey();
+		if (array_key_exists($this->getHost(), $hostKeys)) {
+			if ($hostKeys[$this->getHost()] !== $currentHostKey) {
+				throw new \Exception('Host public key does not match known key');
+			}
+		} else {
+			$hostKeys[$this->getHost()] = $currentHostKey;
+			$this->writeHostKeys($hostKeys);
+		}
+
+		$key = $this->getPrivateKey();
+		if (is_null($key)) {
+			throw new \Exception('Secret key could not be loaded');
+		}
+		if (!$this->client->login($this->getUser(), $key)) {
+			throw new \Exception('Login failed');
+		}
+		return $this->client;
+	}
+
+	/**
+	 * Returns the private key to be used for authentication to the remote server.
+	 *
+	 * @return \Crypt_RSA instance or null in case of a failure to load the key.
+	 */
+	private function getPrivateKey() {
+		$key = new \Crypt_RSA();
+		$key->setPassword(\OC::$server->getConfig()->getSystemValue('secret', ''));
+		if (!$key->loadKey($this->privateKey)) {
+			// Should this exception rather than return null?
+			return null;
+		}
+		return $key;
+	}
+
+	/**
+	 * Throws an exception if the provided host name/address is invalid (cannot be resolved
+	 * and is not an IPv4 address).
+	 *
+	 * @return true; never returns in case of a problem, this return value is used just to
+	 * make unit tests happy.
+	 */
+	public function assertHostAddressValid($hostname) {
+		// TODO: Should handle IPv6 addresses too
+		if (!preg_match('/^\d+\.\d+\.\d+\.\d+$/', $hostname) && gethostbyname($hostname) === $hostname) {
+			// Hostname is not an IPv4 address and cannot be resolved via DNS
+			throw new \InvalidArgumentException('Cannot resolve hostname.');
+		}
+		return true;
+	}
+
+	/**
+	 * Throws an exception if the provided port number is invalid (cannot be resolved
+	 * and is not an IPv4 address).
+	 *
+	 * @return true; never returns in case of a problem, this return value is used just to
+	 * make unit tests happy.
+	 */
+	public function assertPortNumberValid($port) {
+		if (!preg_match('/^\d+$/', $port)) {
+			throw new \InvalidArgumentException('Port number must be a number.');
+		}
+		if ($port < 0 || $port > 65535) {
+			throw new \InvalidArgumentException('Port number must be between 0 and 65535 inclusive.');
+		}
+		return true;
+	}
+
+	/**
+	 * Replaces anything that's not an alphanumeric character or "." in a hostname
+	 * with "_", to make it safe for use as part of a file name.
+	 */
+	protected function sanitizeHostName($name) {
+		return preg_replace('/[^\d\w\._]/', '_', $name);
+	}
+
+	/**
+	 * Replaces anything that's not an alphanumeric character or "_" in a username
+	 * with "_", to make it safe for use as part of a file name.
+	 */
+	protected function sanitizeUserName($name) {
+		return preg_replace('/[^\d\w_]/', '_', $name);
+	}
+
+	public function test() {
+		if (empty($this->getHost())) {
+			\OC::$server->getLogger()->warning('Hostname has not been specified');
+			return false;
+		}
+		if (empty($this->getUser())) {
+			\OC::$server->getLogger()->warning('Username has not been specified');
+			return false;
+		}
+		if (!isset($this->privateKey)) {
+			\OC::$server->getLogger()->warning('Private key was missing from the request');
+			return false;
+		}
+
+		// Sanity check the host
+		$hostParts = explode(':', $this->getHost());
+		try {
+			if (count($hostParts) == 1) {
+				$hostname = $hostParts[0];
+				$this->assertHostAddressValid($hostname);
+			} else if (count($hostParts) == 2) {
+				$hostname = $hostParts[0];
+				$this->assertHostAddressValid($hostname);
+				$this->assertPortNumberValid($hostParts[1]);
+			} else {
+				throw new \Exception('Host connection string is invalid.');
+			}
+		} catch(\Exception $e) {
+			\OC::$server->getLogger()->warning($e->getMessage());
+			return false;
+		}
+
+		// Validate the key
+		$key = $this->getPrivateKey();
+		if (is_null($key)) {
+			\OC::$server->getLogger()->warning('Secret key could not be loaded');
+			return false;
+		}
+
+		try {
+			if ($this->getConnection()->nlist() === false) {
+				return false;
+			}
+		} catch(\Exception $e) {
+			// We should be throwing a more specific error, so we're not just catching
+			// Exception here
+			\OC::$server->getLogger()->warning($e->getMessage());
+			return false;
+		}
+
+		// Save the key somewhere it can easily be extracted later
+		if (\OC::$server->getUserSession()->getUser()) {
+			$view = new \OC\Files\View('/'.\OC::$server->getUserSession()->getUser()->getUId().'/files_external/sftp_keys');
+			if (!$view->is_dir('')) {
+				if (!$view->mkdir('')) {
+					\OC::$server->getLogger()->warning('Could not create secret key directory.');
+					return false;
+				}
+			}
+			$key_filename = $this->sanitizeUserName($this->getUser()).'@'.$this->sanitizeHostName($hostname).'.pub';
+			$key_file = $view->fopen($key_filename, "w");
+			if ($key_file) {
+				fwrite($key_file, $this->publicKey);
+				fclose($key_file);
+			} else {
+				\OC::$server->getLogger()->warning('Could not write secret key file.');
+			}
+		}
+
+		return true;
+	}
+}
diff --git a/apps/files_external/tests/backends/sftp_key.php b/apps/files_external/tests/backends/sftp_key.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e55cc37ca3186897566fafb2ae4de23bcd1a1be
--- /dev/null
+++ b/apps/files_external/tests/backends/sftp_key.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * ownCloud
+ *
+ * @author Henrik Kjölhede
+ * @copyright 2013 Henrik Kjölhede hkjolhede@gmail.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Test\Files\Storage;
+
+class SFTP_Key extends Storage {
+	private $config;
+
+	protected function setUp() {
+		parent::setUp();
+
+		$id = $this->getUniqueID();
+		$this->config = include('files_external/tests/config.php');
+		if ( ! is_array($this->config) or ! isset($this->config['sftp_key']) or ! $this->config['sftp_key']['run']) {
+			$this->markTestSkipped('SFTP with key backend not configured');
+		}
+		$this->config['sftp_key']['root'] .= '/' . $id; //make sure we have an new empty folder to work in
+		$this->instance = new \OC\Files\Storage\SFTP_Key($this->config['sftp_key']);
+		$this->instance->mkdir('/');
+	}
+
+	protected function tearDown() {
+		if ($this->instance) {
+			$this->instance->rmdir('/');
+		}
+
+		parent::tearDown();
+	}
+
+	/**
+         * @expectedException InvalidArgumentException
+         */
+        public function testInvalidAddressShouldThrowException() {
+		# I'd use example.com for this, but someone decided to break the spec and make it resolve
+                $this->instance->assertHostAddressValid('notarealaddress...');
+        }
+
+	public function testValidAddressShouldPass() {
+                $this->assertTrue($this->instance->assertHostAddressValid('localhost'));
+        }
+
+	/**
+	 * @expectedException InvalidArgumentException
+	 */
+	public function testNegativePortNumberShouldThrowException() {
+		$this->instance->assertPortNumberValid('-1');
+	}
+
+	/**
+         * @expectedException InvalidArgumentException
+         */
+        public function testNonNumericalPortNumberShouldThrowException() {
+                $this->instance->assertPortNumberValid('a');
+        }
+
+	/**
+         * @expectedException InvalidArgumentException
+         */
+        public function testHighPortNumberShouldThrowException() { 
+                $this->instance->assertPortNumberValid('65536');
+        }
+
+	public function testValidPortNumberShouldPass() {
+                $this->assertTrue($this->instance->assertPortNumberValid('22222'));
+        }
+}
diff --git a/apps/files_external/tests/config.php b/apps/files_external/tests/config.php
index 62aff4d1bc1eed6f0d805d9e7f0d5a03dc8a3271..cf9cdfeead86518f4ef074bd11324058abec8327 100644
--- a/apps/files_external/tests/config.php
+++ b/apps/files_external/tests/config.php
@@ -88,5 +88,13 @@ return array(
 		'user'=>'test',
 		'password'=>'test',
 		'root'=>'/test'
-	)
+	),
+	'sftp_key' => array (
+                'run'=>false,
+                'host'=>'localhost',
+                'user'=>'test',
+                'public_key'=>'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDJPTvz3OLonF2KSGEKP/nd4CPmRYvemG2T4rIiNYjDj0U5y+2sKEWbjiUlQl2bsqYuVoJ+/UNJlGQbbZ08kQirFeo1GoWBzqioaTjUJfbLN6TzVVKXxR9YIVmH7Ajg2iEeGCndGgbmnPfj+kF9TR9IH8vMVvtubQwf7uEwB0ALhw== phpseclib-generated-key',
+		'private_key'=>'test',
+                'root'=>'/test'
+        ),
 );