2014-08-26 17:02:40 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
|
|
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
|
|
* later.
|
|
|
|
* See the COPYING-README file.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace OC\Security;
|
|
|
|
|
|
|
|
use Crypt_AES;
|
|
|
|
use Crypt_Hash;
|
|
|
|
use OCP\Security\ICrypto;
|
2014-08-26 22:49:53 +00:00
|
|
|
use OCP\Security\ISecureRandom;
|
2014-08-26 17:02:40 +00:00
|
|
|
use OCP\Security\StringUtils;
|
2014-08-26 22:49:53 +00:00
|
|
|
use OCP\IConfig;
|
2014-08-26 17:02:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
|
|
|
|
* it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
|
|
|
|
*
|
|
|
|
* Usage:
|
|
|
|
* $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
|
|
|
|
* $encryptWithCustompassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
|
|
|
|
*
|
|
|
|
* @package OC\Security
|
|
|
|
*/
|
|
|
|
class Crypto implements ICrypto {
|
|
|
|
/** @var Crypt_AES $cipher */
|
|
|
|
private $cipher;
|
|
|
|
/** @var int */
|
|
|
|
private $ivLength = 16;
|
2014-08-26 22:49:53 +00:00
|
|
|
/** @var IConfig */
|
|
|
|
private $config;
|
|
|
|
/** @var ISecureRandom */
|
|
|
|
private $random;
|
2014-08-26 17:02:40 +00:00
|
|
|
|
2014-08-26 22:49:53 +00:00
|
|
|
function __construct(IConfig $config, ISecureRandom $random) {
|
2014-08-26 17:02:40 +00:00
|
|
|
$this->cipher = new Crypt_AES();
|
2014-08-26 22:49:53 +00:00
|
|
|
$this->config = $config;
|
|
|
|
$this->random = $random;
|
2014-08-26 17:02:40 +00:00
|
|
|
}
|
|
|
|
|
2014-09-11 07:51:45 +00:00
|
|
|
/**
|
|
|
|
* Custom implementation of hex2bin since the function is only available starting
|
|
|
|
* with PHP 5.4
|
|
|
|
*
|
|
|
|
* @TODO Remove this once 5.3 support for ownCloud is dropped
|
|
|
|
* @param $message
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected static function hexToBin($message) {
|
|
|
|
if (function_exists('hex2bin')) {
|
|
|
|
return hex2bin($message);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pack("H*", $message);
|
|
|
|
}
|
|
|
|
|
2014-08-26 17:02:40 +00:00
|
|
|
/**
|
|
|
|
* @param string $message The message to authenticate
|
|
|
|
* @param string $password Password to use (defaults to `secret` in config.php)
|
|
|
|
* @return string Calculated HMAC
|
|
|
|
*/
|
|
|
|
public function calculateHMAC($message, $password = '') {
|
|
|
|
if($password === '') {
|
2014-08-26 22:49:53 +00:00
|
|
|
$password = $this->config->getSystemValue('secret');
|
2014-08-26 17:02:40 +00:00
|
|
|
}
|
|
|
|
|
2014-09-03 09:03:27 +00:00
|
|
|
// Append an "a" behind the password and hash it to prevent reusing the same password as for encryption
|
|
|
|
$password = hash('sha512', $password . 'a');
|
|
|
|
|
2014-08-26 17:02:40 +00:00
|
|
|
$hash = new Crypt_Hash('sha512');
|
|
|
|
$hash->setKey($password);
|
|
|
|
return $hash->hash($message);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
|
|
|
|
* @param string $plaintext
|
|
|
|
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
|
|
|
|
* @return string Authenticated ciphertext
|
|
|
|
*/
|
|
|
|
public function encrypt($plaintext, $password = '') {
|
|
|
|
if($password === '') {
|
2014-08-26 22:49:53 +00:00
|
|
|
$password = $this->config->getSystemValue('secret');
|
2014-08-26 17:02:40 +00:00
|
|
|
}
|
|
|
|
$this->cipher->setPassword($password);
|
|
|
|
|
2014-08-26 22:49:53 +00:00
|
|
|
$iv = $this->random->getLowStrengthGenerator()->generate($this->ivLength);
|
2014-08-26 17:02:40 +00:00
|
|
|
$this->cipher->setIV($iv);
|
|
|
|
|
|
|
|
$ciphertext = bin2hex($this->cipher->encrypt($plaintext));
|
|
|
|
$hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password));
|
|
|
|
|
|
|
|
return $ciphertext.'|'.$iv.'|'.$hmac;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
|
|
|
|
* @param string $authenticatedCiphertext
|
|
|
|
* @param string $password Password to encrypt, if not specified the secret from config.php will be taken
|
|
|
|
* @return string plaintext
|
|
|
|
* @throws \Exception If the HMAC does not match
|
|
|
|
*/
|
|
|
|
public function decrypt($authenticatedCiphertext, $password = '') {
|
|
|
|
if($password === '') {
|
2014-08-26 22:49:53 +00:00
|
|
|
$password = $this->config->getSystemValue('secret');
|
2014-08-26 17:02:40 +00:00
|
|
|
}
|
|
|
|
$this->cipher->setPassword($password);
|
|
|
|
|
|
|
|
$parts = explode('|', $authenticatedCiphertext);
|
|
|
|
if(sizeof($parts) !== 3) {
|
|
|
|
throw new \Exception('Authenticated ciphertext could not be decoded.');
|
|
|
|
}
|
|
|
|
|
2014-09-11 07:51:45 +00:00
|
|
|
$ciphertext = self::hexToBin($parts[0]);
|
2014-08-26 17:02:40 +00:00
|
|
|
$iv = $parts[1];
|
2014-09-11 07:51:45 +00:00
|
|
|
$hmac = self::hexToBin($parts[2]);
|
2014-08-26 17:02:40 +00:00
|
|
|
|
|
|
|
$this->cipher->setIV($iv);
|
|
|
|
|
|
|
|
if(!StringUtils::equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) {
|
|
|
|
throw new \Exception('HMAC does not match.');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->cipher->decrypt($ciphertext);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|