2012-08-01 13:11:41 +00:00
< ? php
/**
* ownCloud
*
* @ author Sam Tuke , Frank Karlitschek , Robin Appelman
* @ copyright 2012 Sam Tuke samtuke @ owncloud . com ,
* Robin Appelman icewind @ owncloud . com , Frank Karlitschek
* frank @ owncloud . org
*
* 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 />.
*
*/
2012-10-17 15:35:19 +00:00
namespace OCA\Encryption ;
2012-11-28 18:39:19 +00:00
require_once 'Crypt_Blowfish/Blowfish.php' ;
2012-10-17 15:35:19 +00:00
// Todo:
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
// - Add a setting "Don´ t encrypt files larger than xx because of performance reasons"
// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension)
// - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster
// - IMPORTANT! Check if the block lenght of the encrypted data stays the same
2012-08-01 13:11:41 +00:00
/**
* Class for common cryptography functionality
*/
class Crypt {
2012-07-26 11:47:43 +00:00
/**
2012-08-01 13:11:41 +00:00
* @ brief return encryption mode client or server side encryption
2012-07-31 14:52:21 +00:00
* @ param string user name ( use system wide setting if name = null )
2012-07-26 11:47:43 +00:00
* @ return string 'client' or 'server'
*/
2012-07-31 18:35:36 +00:00
public static function mode ( $user = null ) {
2012-07-31 18:28:11 +00:00
2012-11-16 18:31:37 +00:00
// $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' );
//
// if ( $mode == 'user') {
// if ( !$user ) {
// $user = \OCP\User::getUser();
// }
// $mode = 'none';
// if ( $user ) {
// $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" );
// $result = $query->execute(array($user));
// if ($row = $result->fetchRow()){
// $mode = $row['mode'];
// }
// }
// }
//
// return $mode;
2012-08-14 18:06:56 +00:00
2012-11-16 18:31:37 +00:00
return 'server' ;
2012-10-17 15:35:19 +00:00
2012-08-01 13:11:41 +00:00
}
/**
* @ brief Create a new encryption keypair
* @ return array publicKey , privatekey
*/
public static function createKeypair () {
2012-10-17 15:35:19 +00:00
2012-08-01 13:11:41 +00:00
$res = openssl_pkey_new ();
// Get private key
openssl_pkey_export ( $res , $privateKey );
// Get public key
$publicKey = openssl_pkey_get_details ( $res );
$publicKey = $publicKey [ 'key' ];
return ( array ( 'publicKey' => $publicKey , 'privateKey' => $privateKey ) );
}
2012-10-17 15:35:19 +00:00
/**
* @ brief Add arbitrary padding to encrypted data
* @ param string $data data to be padded
* @ return padded data
2012-11-14 15:00:40 +00:00
* @ note In order to end up with data exactly 8192 bytes long we must add two letters . It is impossible to achieve exactly 8192 length blocks with encryption alone , hence padding is added to achieve the required length .
2012-10-17 15:35:19 +00:00
*/
public static function addPadding ( $data ) {
$padded = $data . 'xx' ;
return $padded ;
}
/**
* @ brief Remove arbitrary padding to encrypted data
* @ param string $padded padded data to remove padding from
2012-11-16 18:31:37 +00:00
* @ return unpadded data on success , false on error
2012-10-17 15:35:19 +00:00
*/
public static function removePadding ( $padded ) {
if ( substr ( $padded , - 2 ) == 'xx' ) {
$data = substr ( $padded , 0 , - 2 );
return $data ;
} else {
# TODO: log the fact that unpadded data was submitted for removal of padding
return false ;
}
}
2012-08-01 13:11:41 +00:00
/**
* @ brief Check if a file ' s contents contains an IV and is symmetrically encrypted
* @ return true / false
2012-10-17 15:35:19 +00:00
* @ note see also OCA\Encryption\Util -> isEncryptedPath ()
2012-08-01 13:11:41 +00:00
*/
public static function isEncryptedContent ( $content ) {
if ( ! $content ) {
return false ;
}
2012-10-17 15:35:19 +00:00
$noPadding = self :: removePadding ( $content );
2012-08-01 13:11:41 +00:00
// Fetch encryption metadata from end of file
2012-10-17 15:35:19 +00:00
$meta = substr ( $noPadding , - 22 );
2012-08-01 13:11:41 +00:00
// Fetch IV from end of file
$iv = substr ( $meta , - 16 );
// Fetch identifier from start of metadata
$identifier = substr ( $meta , 0 , 6 );
if ( $identifier == '00iv00' ) {
return true ;
} else {
return false ;
}
}
2012-11-22 14:08:19 +00:00
/**
* Check if a file is encrypted according to database file cache
* @ param string $path
* @ return bool
*/
2012-11-28 18:39:19 +00:00
public static function isEncryptedMeta ( $path ) {
2012-11-22 14:08:19 +00:00
# TODO: Use DI to get OC_FileCache_Cached out of here
// Fetch all file metadata from DB
$metadata = \OC_FileCache_Cached :: get ( $path , '' );
// Return encryption status
return isset ( $metadata [ 'encrypted' ] ) and ( bool ) $metadata [ 'encrypted' ];
}
2012-08-01 13:11:41 +00:00
/**
* @ brief Check if a file is encrypted via legacy system
* @ return true / false
*/
2012-11-28 18:39:19 +00:00
public static function isLegacyEncryptedContent ( $content ) {
2012-08-01 13:11:41 +00:00
// Fetch all file metadata from DB
$metadata = \OC_FileCache_Cached :: get ( $content , '' );
// If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system
if (
$content
and isset ( $metadata [ 'encrypted' ] )
and $metadata [ 'encrypted' ] === true
and ! self :: isEncryptedContent ( $content )
) {
return true ;
} else {
return false ;
}
}
/**
* @ brief Symmetrically encrypt a string
* @ returns encrypted file
*/
public static function encrypt ( $plainContent , $iv , $passphrase = '' ) {
if ( $encryptedContent = openssl_encrypt ( $plainContent , 'AES-128-CFB' , $passphrase , false , $iv ) ) {
return $encryptedContent ;
} else {
\OC_Log :: write ( 'Encryption library' , 'Encryption (symmetric) of content failed' , \OC_Log :: ERROR );
return false ;
}
}
/**
* @ brief Symmetrically decrypt a string
* @ returns decrypted file
*/
public static function decrypt ( $encryptedContent , $iv , $passphrase ) {
2012-09-11 12:40:45 +00:00
2012-08-01 13:11:41 +00:00
if ( $plainContent = openssl_decrypt ( $encryptedContent , 'AES-128-CFB' , $passphrase , false , $iv ) ) {
return $plainContent ;
} else {
2012-11-16 18:31:37 +00:00
throw new \Exception ( 'Encryption library: Decryption (symmetric) of content failed' );
2012-08-01 13:11:41 +00:00
return false ;
}
}
2012-11-14 15:00:40 +00:00
/**
* @ brief Concatenate encrypted data with its IV and padding
* @ param string $content content to be concatenated
* @ param string $iv IV to be concatenated
* @ returns string concatenated content
*/
2012-08-23 15:43:10 +00:00
public static function concatIv ( $content , $iv ) {
$combined = $content . '00iv00' . $iv ;
return $combined ;
}
2012-11-14 15:00:40 +00:00
/**
* @ brief Split concatenated data and IV into respective parts
* @ param string $catFile concatenated data to be split
* @ returns array keys : encrypted , iv
*/
public static function splitIv ( $catFile ) {
// Fetch encryption metadata from end of file
$meta = substr ( $catFile , - 22 );
// Fetch IV from end of file
$iv = substr ( $meta , - 16 );
// Remove IV and IV identifier text to expose encrypted content
$encrypted = substr ( $catFile , 0 , - 22 );
$split = array (
'encrypted' => $encrypted
, 'iv' => $iv
);
2012-11-15 11:50:05 +00:00
return $split ;
2012-11-14 15:00:40 +00:00
}
2012-08-01 13:11:41 +00:00
/**
* @ brief Symmetrically encrypts a string and returns keyfile content
* @ param $plainContent content to be encrypted in keyfile
* @ returns encrypted content combined with IV
* @ note IV need not be specified , as it will be stored in the returned keyfile
* and remain accessible therein .
*/
public static function symmetricEncryptFileContent ( $plainContent , $passphrase = '' ) {
if ( ! $plainContent ) {
return false ;
}
$iv = self :: generateIv ();
if ( $encryptedContent = self :: encrypt ( $plainContent , $iv , $passphrase ) ) {
// Combine content to encrypt with IV identifier and actual IV
2012-12-11 15:10:56 +00:00
$catfile = self :: concatIv ( $encryptedContent , $iv );
2012-08-01 13:11:41 +00:00
2012-12-11 15:10:56 +00:00
$padded = self :: addPadding ( $catfile );
2012-10-17 15:35:19 +00:00
return $padded ;
2012-08-01 13:11:41 +00:00
} else {
\OC_Log :: write ( 'Encryption library' , 'Encryption (symmetric) of keyfile content failed' , \OC_Log :: ERROR );
return false ;
}
}
/**
* @ brief Symmetrically decrypts keyfile content
* @ param string $source
* @ param string $target
* @ param string $key the decryption key
2012-08-14 18:06:56 +00:00
* @ returns decrypted content
2012-08-01 13:11:41 +00:00
*
* This function decrypts a file
*/
public static function symmetricDecryptFileContent ( $keyfileContent , $passphrase = '' ) {
if ( ! $keyfileContent ) {
2012-11-16 18:31:37 +00:00
throw new \Exception ( 'Encryption library: no data provided for decryption' );
2012-08-01 13:11:41 +00:00
}
2012-10-17 15:35:19 +00:00
// Remove padding
$noPadding = self :: removePadding ( $keyfileContent );
2012-12-11 17:12:46 +00:00
// Split into enc data and catfile
$catfile = self :: splitIv ( $noPadding );
2012-08-01 13:11:41 +00:00
2012-12-11 17:12:46 +00:00
if ( $plainContent = self :: decrypt ( $catfile [ 'encrypted' ], $catfile [ 'iv' ], $passphrase ) ) {
2012-08-01 13:11:41 +00:00
return $plainContent ;
}
}
/**
* @ brief Creates symmetric keyfile content using a generated key
* @ param string $plainContent content to be encrypted
* @ returns array keys : key , encrypted
* @ note symmetricDecryptFileContent () can be used to decrypt files created using this method
*
* This function decrypts a file
*/
public static function symmetricEncryptFileContentKeyfile ( $plainContent ) {
$key = self :: generateKey ();
if ( $encryptedContent = self :: symmetricEncryptFileContent ( $plainContent , $key ) ) {
return array (
'key' => $key
, 'encrypted' => $encryptedContent
);
} else {
return false ;
}
}
/**
* @ brief Create asymmetrically encrypted keyfile content using a generated key
* @ param string $plainContent content to be encrypted
* @ returns array keys : key , encrypted
* @ note symmetricDecryptFileContent () can be used to decrypt files created using this method
*
* This function decrypts a file
*/
public static function multiKeyEncrypt ( $plainContent , array $publicKeys ) {
$envKeys = array ();
if ( openssl_seal ( $plainContent , $sealed , $envKeys , $publicKeys ) ) {
return array (
'keys' => $envKeys
, 'encrypted' => $sealed
);
} else {
return false ;
}
}
/**
* @ brief Asymmetrically encrypt a file using multiple public keys
* @ param string $plainContent content to be encrypted
2012-08-14 18:06:56 +00:00
* @ returns string $plainContent decrypted string
2012-08-01 13:11:41 +00:00
* @ note symmetricDecryptFileContent () can be used to decrypt files created using this method
*
* This function decrypts a file
*/
public static function multiKeyDecrypt ( $encryptedContent , $envKey , $privateKey ) {
if ( ! $encryptedContent ) {
return false ;
}
if ( openssl_open ( $encryptedContent , $plainContent , $envKey , $privateKey ) ) {
return $plainContent ;
} else {
\OC_Log :: write ( 'Encryption library' , 'Decryption (asymmetric) of sealed content failed' , \OC_Log :: ERROR );
return false ;
}
}
/**
* @ brief Asymetrically encrypt a string using a public key
* @ returns encrypted file
*/
public static function keyEncrypt ( $plainContent , $publicKey ) {
openssl_public_encrypt ( $plainContent , $encryptedContent , $publicKey );
return $encryptedContent ;
}
/**
* @ brief Asymetrically decrypt a file using a private key
* @ returns decrypted file
*/
public static function keyDecrypt ( $encryptedContent , $privatekey ) {
openssl_private_decrypt ( $encryptedContent , $plainContent , $privatekey );
return $plainContent ;
}
2012-08-14 18:06:56 +00:00
/**
2012-11-20 19:10:10 +00:00
* @ brief Encrypts content symmetrically and generates keyfile asymmetrically
2012-12-11 15:10:56 +00:00
* @ returns array containing catfile and new keyfile .
* keys : data , key
2012-08-14 18:06:56 +00:00
* @ note this method is a wrapper for combining other crypt class methods
*/
public static function keyEncryptKeyfile ( $plainContent , $publicKey ) {
// Encrypt plain data, generate keyfile & encrypted file
$cryptedData = self :: symmetricEncryptFileContentKeyfile ( $plainContent );
// Encrypt keyfile
$cryptedKey = self :: keyEncrypt ( $cryptedData [ 'key' ], $publicKey );
return array ( 'data' => $cryptedData [ 'encrypted' ], 'key' => $cryptedKey );
}
/**
2012-12-11 15:10:56 +00:00
* @ brief Takes catfile , keyfile , and private key , and
2012-11-20 19:10:10 +00:00
* performs decryption
2012-08-14 18:06:56 +00:00
* @ returns decrypted content
* @ note this method is a wrapper for combining other crypt class methods
*/
2012-12-11 15:10:56 +00:00
public static function keyDecryptKeyfile ( $catfile , $keyfile , $privateKey ) {
2012-08-14 18:06:56 +00:00
2012-12-11 15:10:56 +00:00
// Decrypt the keyfile with the user's private key
2012-12-11 17:12:46 +00:00
$decryptedKeyfile = self :: keyDecrypt ( $keyfile , $privateKey );
2012-08-14 18:06:56 +00:00
2012-12-11 15:10:56 +00:00
// trigger_error( "\$keyfile = ".var_export($keyfile, 1));
// Decrypt the catfile symmetrically using the decrypted keyfile
2012-12-11 17:12:46 +00:00
$decryptedData = self :: symmetricDecryptFileContent ( $catfile , $decryptedKeyfile );
2012-08-14 18:06:56 +00:00
return $decryptedData ;
}
2012-08-01 13:11:41 +00:00
2012-08-16 18:18:18 +00:00
/**
* @ brief Symmetrically encrypt a file by combining encrypted component data blocks
*/
public static function symmetricBlockEncryptFileContent ( $plainContent , $key ) {
$crypted = '' ;
2012-08-23 15:43:10 +00:00
$remaining = $plainContent ;
2012-08-23 18:19:39 +00:00
$testarray = array ();
2012-08-23 15:43:10 +00:00
while ( strlen ( $remaining ) ) {
2012-08-16 18:18:18 +00:00
2012-10-17 15:35:19 +00:00
//echo "\n\n\$block = ".substr( $remaining, 0, 6126 );
2012-08-23 18:19:39 +00:00
2012-08-16 18:18:18 +00:00
// Encrypt a chunk of unencrypted data and add it to the rest
2012-10-17 15:35:19 +00:00
$block = self :: symmetricEncryptFileContent ( substr ( $remaining , 0 , 6126 ), $key );
$padded = self :: addPadding ( $block );
2012-08-23 15:43:10 +00:00
$crypted .= $block ;
2012-08-16 18:18:18 +00:00
2012-08-23 18:19:39 +00:00
$testarray [] = $block ;
2012-08-16 18:18:18 +00:00
// Remove the data already encrypted from remaining unencrypted data
2012-10-17 15:35:19 +00:00
$remaining = substr ( $remaining , 6126 );
2012-08-16 18:18:18 +00:00
}
2012-08-23 18:19:39 +00:00
//echo "hags ";
//echo "\n\n\n\$crypted = $crypted\n\n\n";
//print_r($testarray);
2012-08-16 18:18:18 +00:00
return $crypted ;
}
/**
* @ brief Symmetrically decrypt a file by combining encrypted component data blocks
*/
public static function symmetricBlockDecryptFileContent ( $crypted , $key ) {
2012-08-23 15:43:10 +00:00
2012-08-16 18:18:18 +00:00
$decrypted = '' ;
2012-08-23 15:43:10 +00:00
$remaining = $crypted ;
2012-08-23 18:19:39 +00:00
$testarray = array ();
2012-08-23 15:43:10 +00:00
while ( strlen ( $remaining ) ) {
2012-08-23 18:19:39 +00:00
2012-10-17 15:35:19 +00:00
$testarray [] = substr ( $remaining , 0 , 8192 );
2012-08-16 18:18:18 +00:00
2012-10-17 15:35:19 +00:00
// Decrypt a chunk of unencrypted data and add it to the rest
$decrypted .= self :: symmetricDecryptFileContent ( $remaining , $key );
2012-08-16 18:18:18 +00:00
2012-08-23 15:43:10 +00:00
// Remove the data already encrypted from remaining unencrypted data
2012-10-17 15:35:19 +00:00
$remaining = substr ( $remaining , 8192 );
2012-08-16 18:18:18 +00:00
}
2012-10-17 15:35:19 +00:00
//echo "\n\n\$testarray = "; print_r($testarray);
2012-08-23 18:19:39 +00:00
2012-08-23 15:43:10 +00:00
return $decrypted ;
2012-08-16 18:18:18 +00:00
}
2012-08-01 13:11:41 +00:00
/**
2012-11-14 15:00:40 +00:00
* @ brief Generates a pseudo random initialisation vector
* @ return String $iv generated IV
2012-08-01 13:11:41 +00:00
*/
public static function generateIv () {
2012-11-14 15:00:40 +00:00
if ( $random = openssl_random_pseudo_bytes ( 12 , $strong ) ) {
2012-08-01 13:11:41 +00:00
if ( ! $strong ) {
// If OpenSSL indicates randomness is insecure, log error
\OC_Log :: write ( 'Encryption library' , 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log :: WARN );
}
2012-11-14 15:00:40 +00:00
// We encode the iv purely for string manipulation
// purposes - it gets decoded before use
$iv = base64_encode ( $random );
2012-08-01 13:11:41 +00:00
return $iv ;
} else {
2012-11-14 15:00:40 +00:00
throw new Exception ( 'Generating IV failed' );
2012-08-01 13:11:41 +00:00
}
}
/**
* @ brief Generate a pseudo random 1024 kb ASCII key
* @ returns $key Generated key
*/
public static function generateKey () {
// $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 );
// Generate key
2012-08-14 18:06:56 +00:00
if ( $key = base64_encode ( openssl_random_pseudo_bytes ( 183 , $strong ) ) ) {
2012-08-01 13:11:41 +00:00
if ( ! $strong ) {
// If OpenSSL indicates randomness is insecure, log error
2012-11-14 15:00:40 +00:00
throw new Exception ( 'Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()' );
2012-08-01 13:11:41 +00:00
}
return $key ;
} else {
return false ;
}
}
public static function changekeypasscode ( $oldPassword , $newPassword ) {
2012-08-15 07:54:21 +00:00
if ( \OCP\User :: isLoggedIn ()){
2012-08-09 11:47:27 +00:00
$key = Keymanager :: getPrivateKey ();
2012-08-15 07:54:21 +00:00
if ( ( $key = Crypt :: symmetricDecryptFileContent ( $key , $oldpasswd )) ) {
if ( ( $key = Crypt :: symmetricEncryptFileContent ( $key , $newpasswd )) ) {
Keymanager :: setPrivateKey ( $key );
return true ;
}
2012-08-09 11:47:27 +00:00
}
2012-08-01 13:11:41 +00:00
}
2012-08-15 07:54:21 +00:00
return false ;
2012-08-01 13:11:41 +00:00
}
2012-11-22 14:08:19 +00:00
2012-11-28 18:39:19 +00:00
/**
* @ brief Get the blowfish encryption handeler for a key
* @ param $key string ( optional )
* @ return Crypt_Blowfish blowfish object
*
* if the key is left out , the default handeler will be used
*/
public static function getBlowfish ( $key = '' ) {
if ( $key ) {
return new \Crypt_Blowfish ( $key );
} else {
return false ;
}
}
public static function legacyCreateKey ( $passphrase ) {
// Generate a random integer
$key = mt_rand ( 10000 , 99999 ) . mt_rand ( 10000 , 99999 ) . mt_rand ( 10000 , 99999 ) . mt_rand ( 10000 , 99999 );
// Encrypt the key with the passphrase
$legacyEncKey = self :: legacyEncrypt ( $key , $passphrase );
return $legacyEncKey ;
}
/**
* @ brief encrypts content using legacy blowfish system
* @ param $content the cleartext message you want to encrypt
* @ param $key the encryption key ( optional )
* @ returns encrypted content
*
* This function encrypts an content
*/
public static function legacyEncrypt ( $content , $passphrase = '' ) {
2012-12-11 15:10:56 +00:00
//trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) );
2012-11-28 18:39:19 +00:00
$bf = self :: getBlowfish ( $passphrase );
return $bf -> encrypt ( $content );
}
/**
* @ brief decrypts content using legacy blowfish system
* @ param $content the cleartext message you want to decrypt
* @ param $key the encryption key ( optional )
* @ returns cleartext content
*
* This function decrypts an content
*/
public static function legacyDecrypt ( $content , $passphrase = '' ) {
//trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) );
2012-12-11 17:12:46 +00:00
$bf = self :: getBlowfish ( $passphrase );
2012-11-28 18:39:19 +00:00
2012-12-11 15:10:56 +00:00
// trigger_error(var_export($bf, 1) );
2012-11-28 18:39:19 +00:00
$decrypted = $bf -> decrypt ( $content );
$trimmed = rtrim ( $decrypted , " \0 " );
return $trimmed ;
}
public static function legacyKeyRecryptKeyfile ( $legacyEncryptedContent , $legacyPassphrase , $publicKey , $newPassphrase ) {
$decrypted = self :: legacyDecrypt ( $legacyEncryptedContent , $legacyPassphrase );
$recrypted = self :: keyEncryptKeyfile ( $decrypted , $publicKey );
return $recrypted ;
}
/**
* @ brief Re - encryptes a legacy blowfish encrypted file using AES with integrated IV
* @ param $legacyContent the legacy encrypted content to re - encrypt
* @ returns cleartext content
*
* This function decrypts an content
*/
public static function legacyRecrypt ( $legacyContent , $legacyPassphrase , $newPassphrase ) {
# TODO: write me
}
2012-08-01 13:11:41 +00:00
}
2012-07-11 16:51:27 +00:00
?>