diff --git a/README.md b/README.md index 5095515..9fbb093 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ If you have a 32-bit system and a lot of cores, consider lowering the number of * While you can pipe any data into the program, the data should be uncompressed and unencrypted -- otherwise no deduplication could be performed on it. `zbackup` would compress and encrypt the data itself, so there's no need to do that yourself. So just run `tar c` and pipe it into `zbackup` directly. If backing up disk images employing encryption, pipe the unencrypted version (the one you normally mount). If you create `.zip` or `.rar` files, use no compression (`-0` or `-m0`) and no encryption. * Parallel LZMA compression uses a lot of RAM (several hundreds of megabytes, depending on the number of threads used), and ten times more virtual address space. The latter is only relevant on 32-bit architectures where it's limited to 2 or 3 GB. If you hit the ceiling, lower the number of threads with `--threads`. * Since the data is deduplicated, there's naturally no redundancy in it. A loss of a single file can lead to a loss of virtually all data. Make sure you store it on a redundant storage (RAID1, a cloud provider etc). - * The encryption key, if used, is stored in the `info` file in the root of the repo. It is encrypted with your password. Technically thus you can change your password without re-encrypting any data, and as long as no one possesses the old `info` file and knows your old password, you would be safe (even though the actual option to change password is not implemented yet -- someone who needs this is welcome to create a pull request -- the possibility is all there). Also note that it is crucial you don't lose your `info` file, as otherwise the whole backup would be lost. + * The encryption key, if used, is stored in the `info` file in the root of the repo. It is encrypted with your password. Technically thus you can change your password without re-encrypting any data, and as long as no one possesses the old `info` file and knows your old password, you would be safe (note that ability to change repo type between encrypted and non-encrypted is not implemented yet -- someone who needs this is welcome to create a pull request -- the possibility is all there). Also note that it is crucial you don't lose your `info` file, as otherwise the whole backup would be lost. # Limitations @@ -166,7 +166,7 @@ won't be able to read those bundles. There's a lot to be improved in the program. It was released with the minimum amount of functionality to be useful. It is also stable. This should hopefully stimulate people to join the development and add all those other fancy features. Here's a list of ideas: * Additional options, such as configurable chunk and bundle sizes etc. - * A command to change password. + * Ability to change bundle type (between encrypted and non-encrypted). * Improved garbage collection. The program should support ability to specify maximum index file size / maximum index file count (for better compatibility with cloud storages as well) or something like retention policy. * A command to fsck the repo by doing something close to what garbage collection does, but also checking all hashes and so on. * Parallel decompression. Right now decompression is single-threaded, but it is possible to look ahead in the stream and perform prefetching. diff --git a/zcollector.cc b/backup_collector.cc similarity index 99% rename from zcollector.cc rename to backup_collector.cc index b7fe74a..bc935db 100644 --- a/zcollector.cc +++ b/backup_collector.cc @@ -1,7 +1,7 @@ // Copyright (c) 2012-2014 Konstantin Isakov and ZBackup contributors, see CONTRIBUTORS // Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE -#include "zcollector.hh" +#include "backup_collector.hh" #include #include diff --git a/zcollector.hh b/backup_collector.hh similarity index 86% rename from zcollector.hh rename to backup_collector.hh index 1f38297..8ca060c 100644 --- a/zcollector.hh +++ b/backup_collector.hh @@ -1,8 +1,8 @@ // Copyright (c) 2012-2014 Konstantin Isakov and ZBackup contributors, see CONTRIBUTORS // Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE -#ifndef Z_COLLECTOR_HH_INCLUDED__ -#define Z_COLLECTOR_HH_INCLUDED__ +#ifndef BACKUP_COLLECTOR_HH_INCLUDED__ +#define BACKUP_COLLECTOR_HH_INCLUDED__ #include "zbackup_base.hh" #include "chunk_storage.hh" diff --git a/chunk_storage.cc b/chunk_storage.cc index 38941b0..73ce40b 100644 --- a/chunk_storage.cc +++ b/chunk_storage.cc @@ -81,7 +81,7 @@ void Writer::commit() // Generate a random filename unsigned char buf[ 24 ]; // Same comments as for Bundle::IdSize - Random::genaratePseudo( buf, sizeof( buf ) ); + Random::generatePseudo( buf, sizeof( buf ) ); indexTempFile->moveOverTo( Dir::addPath( indexDir, toHex( buf, sizeof( buf ) ) ) ); @@ -152,7 +152,7 @@ Bundle::Id const & Writer::getCurrentBundleId() if ( !hasCurrentBundleId ) { // Generate a new one - Random::genaratePseudo( ¤tBundleId, sizeof( currentBundleId ) ); + Random::generatePseudo( ¤tBundleId, sizeof( currentBundleId ) ); hasCurrentBundleId = true; } diff --git a/encrypted_file.cc b/encrypted_file.cc index cfa4192..a283fe3 100644 --- a/encrypted_file.cc +++ b/encrypted_file.cc @@ -341,7 +341,7 @@ void OutputStream::writeRandomIv() if ( key.hasKey() ) { char iv[ Encryption::IvSize ]; - Random::genaratePseudo( iv, sizeof( iv ) ); + Random::generatePseudo( iv, sizeof( iv ) ); write( iv, sizeof( iv ) ); } } diff --git a/encryption_key.cc b/encryption_key.cc index 59d0b2d..e15d079 100644 --- a/encryption_key.cc +++ b/encryption_key.cc @@ -66,12 +66,13 @@ EncryptionKey::~EncryptionKey() } void EncryptionKey::generate( string const & password, - EncryptionKeyInfo & info ) + EncryptionKeyInfo & info, + EncryptionKey & encryptionkey ) { // Use this buf for salts - char buf[ 16 ]; + char buf[ KeySize ]; - Random::genaratePseudo( buf, sizeof( buf ) ); + Random::generatePseudo( buf, sizeof( buf ) ); info.set_salt( buf, sizeof( buf ) ); info.set_rounds( 10000 ); // TODO: make this configurable @@ -79,11 +80,13 @@ void EncryptionKey::generate( string const & password, deriveKey( password, info, derivedKey, sizeof( derivedKey ) ); char key[ KeySize ]; - - Random::genarateTrue( key, sizeof( key ) ); + if ( encryptionkey.hasKey() ) + memcpy( key, encryptionkey.getKey(), KeySize ); + else + Random::generateTrue( key, sizeof( key ) ); // Fill in the HMAC verification part - Random::genaratePseudo( buf, sizeof( buf ) ); + Random::generatePseudo( buf, sizeof( buf ) ); info.set_key_check_input( buf, sizeof( buf ) ); info.set_key_check_hmac( calculateKeyHmac( key, sizeof( key ), info.key_check_input() ) ); diff --git a/encryption_key.hh b/encryption_key.hh index ed8fa6d..f90982d 100644 --- a/encryption_key.hh +++ b/encryption_key.hh @@ -41,7 +41,8 @@ public: { return sizeof( key ); } /// Generates new key info using the given password - static void generate( string const & password, EncryptionKeyInfo & ); + static void generate( string const & password, EncryptionKeyInfo &, + EncryptionKey & encryptionkey ); /// Returns a static instance without any key set static EncryptionKey const & noKey(); diff --git a/random.cc b/random.cc index c6d8ebd..c1bba7d 100644 --- a/random.cc +++ b/random.cc @@ -5,13 +5,13 @@ #include namespace Random { -void genarateTrue( void * buf, unsigned size ) +void generateTrue( void * buf, unsigned size ) { if ( RAND_bytes( (unsigned char *) buf, size ) != 1 ) throw exCantGenerate(); } -void genaratePseudo( void * buf, unsigned size ) +void generatePseudo( void * buf, unsigned size ) { if ( RAND_pseudo_bytes( (unsigned char *) buf, size ) < 0 ) throw exCantGenerate(); diff --git a/random.hh b/random.hh index 3040cb4..0243f9e 100644 --- a/random.hh +++ b/random.hh @@ -12,10 +12,10 @@ namespace Random { DEF_EX( exCantGenerate, "Error generating random sequence, try later", std::exception ) /// This one fills the buffer with true randomness, suitable for a key -void genarateTrue( void * buf, unsigned size ); +void generateTrue( void * buf, unsigned size ); /// This one fills the buffer with pseudo randomness, suitable for salts but not /// keys -void genaratePseudo( void * buf, unsigned size ); +void generatePseudo( void * buf, unsigned size ); } #endif diff --git a/zbackup.cc b/zbackup.cc index 3e93681..d35988e 100644 --- a/zbackup.cc +++ b/zbackup.cc @@ -27,7 +27,7 @@ #include "zbackup.hh" #include "index_file.hh" #include "bundle.hh" -#include "zcollector.hh" +#include "backup_collector.hh" using std::vector; using std::bitset; @@ -321,8 +321,8 @@ void ZExchange::exchange( string const & srcPath, string const & dstPath, } } -DEF_EX( exExchangeWithLessThanTwoKeys, "Specify password flag (--non-encrypted or --password-file)" - " for import/export operation twice (first for source and second for destination)", std::exception ) +DEF_EX( exSpecifyTwoKeys, "Specify password flag (--non-encrypted or --password-file)" + " for import/export/passwd operation twice (first for source and second for destination)", std::exception ) DEF_EX( exNonEncryptedWithKey, "--non-encrypted and --password-file are incompatible", std::exception ) DEF_EX( exSpecifyEncryptionOptions, "Specify either --password-file or --non-encrypted", std::exception ) DEF_EX_STR( exInvalidThreadsValue, "Invalid threads value specified:", std::exception ) @@ -491,7 +491,7 @@ int main( int argc, char *argv[] ) "Usage: %s [flags] [command args]\n" " Flags: --non-encrypted|--password-file \n" -" password flag should be specified twice if import/export\n" +" password flag should be specified twice if import/export/passwd\n" " command specified\n" " --silent (default is verbose)\n" " --threads (default is %zu on your system)\n" @@ -508,7 +508,8 @@ int main( int argc, char *argv[] ) " performs export from source to destination storage;\n" " import -\n" " performs import from source to destination storage;\n" -" gc - performs chunk garbage collection.\n" +" gc - performs chunk garbage collection;\n" +" passwd - changes repository info file passphrase.\n" " For export/import storage path must be valid (initialized) storage.\n" "", *argv, defaultThreads, defaultCacheSizeMb ); @@ -518,12 +519,16 @@ int main( int argc, char *argv[] ) if ( passwords.size() > 1 && ( ( passwords[ 0 ].empty() && !passwords[ 1 ].empty() ) || ( !passwords[ 0 ].empty() && passwords[ 1 ].empty() ) ) && - ( strcmp( args[ 0 ], "export" ) != 0 && strcmp( args[ 0 ], "import" ) != 0 ) ) + ( strcmp( args[ 0 ], "export" ) != 0 && + strcmp( args[ 0 ], "import" ) != 0 && + strcmp( args[ 0 ], "passwd" ) ) ) throw exNonEncryptedWithKey(); else - if ( passwords.size() < 2 && - ( strcmp( args[ 0 ], "export" ) == 0 || strcmp( args[ 0 ], "import" ) == 0 ) ) - throw exExchangeWithLessThanTwoKeys(); + if ( passwords.size() != 2 && + ( strcmp( args[ 0 ], "export" ) == 0 || + strcmp( args[ 0 ], "import" ) == 0 || + strcmp( args[ 0 ], "passwd" ) == 0 ) ) + throw exSpecifyTwoKeys(); else if ( passwords.size() < 1 ) throw exSpecifyEncryptionOptions(); @@ -614,7 +619,7 @@ int main( int argc, char *argv[] ) // Perform the restore if ( args.size() != 2 ) { - fprintf( stderr, "Usage: %s gc \n", + fprintf( stderr, "Usage: %s gc \n", *argv ); return EXIT_FAILURE; } @@ -622,6 +627,28 @@ int main( int argc, char *argv[] ) zr.gc(); } else + if ( strcmp( args[ 0 ], "passwd" ) == 0 ) + { + // Perform the password change + if ( args.size() != 2 ) + { + fprintf( stderr, "Usage: %s passwd \n", + *argv ); + return EXIT_FAILURE; + } + + ZBackupBase zbb( args[ 1 ], passwords[ 0 ], true ); + if ( passwords[ 0 ].empty() != passwords[ 1 ].empty() ) + { + fprintf( stderr, +"Changing repo encryption type (non-encrypted to encrypted and vice versa) " +"is not supported yet.\n" +"Current repo type: %s\n", zbb.encryptionkey.hasKey() ? "encrypted" : "non-encrypted" ); + return EXIT_FAILURE; + } + zbb.setPassword( passwords[ 1 ] ); + } + else { fprintf( stderr, "Error: unknown command line option: %s\n", args[ 0 ] ); return EXIT_FAILURE; diff --git a/zbackup_base.cc b/zbackup_base.cc index 7d965d6..b6ddfa3 100644 --- a/zbackup_base.cc +++ b/zbackup_base.cc @@ -73,12 +73,15 @@ void ZBackupBase::initStorage( string const & storageDir, // TODO: make the following configurable storageInfo.set_chunk_max_size( 65536 ); storageInfo.set_bundle_max_payload_size( 0x200000 ); + EncryptionKey encryptionkey = EncryptionKey::noKey(); if ( isEncrypted ) EncryptionKey::generate( password, - *storageInfo.mutable_encryption_key() ); + *storageInfo.mutable_encryption_key(), + encryptionkey ); - storageInfo.set_default_compression_method( Compression::CompressionMethod::defaultCompression->getName() ); + storageInfo.set_default_compression_method( + Compression::CompressionMethod::defaultCompression->getName() ); Paths paths( storageDir ); @@ -131,3 +134,14 @@ void ZBackupBase::useDefaultCompressionMethod() Compression::CompressionMethod::defaultCompression = compression; } +void ZBackupBase::setPassword( string const & password ) +{ + EncryptionKey::generate( password, + *storageInfo.mutable_encryption_key(), encryptionkey ); + + StorageInfoFile::save( getStorageInfoPath(), storageInfo ); + + EncryptionKey encryptionkey( password, storageInfo.has_encryption_key() ? + &storageInfo.encryption_key() : 0 ); +} + diff --git a/zbackup_base.hh b/zbackup_base.hh index 65be004..0411571 100644 --- a/zbackup_base.hh +++ b/zbackup_base.hh @@ -53,6 +53,8 @@ public: void useDefaultCompressionMethod(); + void setPassword( std::string const & password ); + StorageInfo storageInfo; EncryptionKey encryptionkey; TmpMgr tmpMgr;