Automatic upgrade to PBKDF2 when changing password of a V6 filesystem.

Increase desired KDF duration to 3 seconds in paranoia mode.
Bump to version 1.5.


git-svn-id: http://encfs.googlecode.com/svn/trunk@37 db9cf616-1c43-0410-9cb8-a902689de0d6
This commit is contained in:
Valient Gough 2008-08-17 07:26:52 +00:00
parent bcfe2aff09
commit ab90e2d91f
10 changed files with 208 additions and 139 deletions

View File

@ -1,7 +1,7 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(encfs/encfs.h) dnl a source file from your sub dir
AM_INIT_AUTOMAKE(encfs, 1.4.3) dnl searches for some needed programs
AM_INIT_AUTOMAKE(encfs, 1.5) dnl searches for some needed programs
dnl without this order in this file, automake will be confused!
dnl

View File

@ -86,8 +86,13 @@ public:
// create a new key based on a password
// if iterationCount == 0, then iteration count will be determined
// by newKey function and filled in.
// If iterationCount == 0, then desiredFunctionDuration is how many
// milliseconds the password derivation function should take to run.
virtual CipherKey newKey(const char *password, int passwdLength,
int &iterationCount, const unsigned char *salt, int saltLen) =0;
int &iterationCount, long desiredFunctionDuration,
const unsigned char *salt, int saltLen) =0;
// deprecated - for backward compatibility
virtual CipherKey newKey(const char *password, int passwdLength ) =0;
// create a new random key
virtual CipherKey newRandomKey() =0;

View File

@ -75,6 +75,9 @@ static const int DefaultBlockSize = 1024;
// use the extpass option, as extpass can return arbitrary length binary data.
static const int MaxPassBuf = 512;
static const int NormalKDFDuration = 500; // 1/2 a second
static const int ParanoiaKDFDuration = 3000; // 3 seconds
// environment variable names for values encfs stores in the environment when
// calling an external password program.
static const char ENCFS_ENV_ROOTDIR[] = "encfs_root";
@ -173,6 +176,7 @@ namespace boost
ar << make_nvp("saltData",
serial::make_binary_object(cfg.saltData, cfg.saltSize));
ar << make_nvp("kdfIterations", cfg.kdfIterations);
ar << make_nvp("desiredKDFDuration", cfg.desiredKDFDuration);
}
template<class Archive>
@ -200,11 +204,13 @@ namespace boost
ar >> make_nvp("saltData",
serial::make_binary_object(cfg.saltData, cfg.saltSize));
ar >> make_nvp("kdfIterations", cfg.kdfIterations);
ar >> make_nvp("desiredKDFDuration", cfg.desiredKDFDuration);
} else
{
cfg.saltSize = 0;
cfg.saltData = NULL;
cfg.kdfIterations = 0;
cfg.kdfIterations = 16;
cfg.desiredKDFDuration = NormalKDFDuration;
}
}
@ -318,7 +324,10 @@ ConfigType readConfig_load( ConfigInfo *nm, const char *path,
try
{
if( (*nm->loadFunc)( path, config, nm ))
{
config->cfgType = nm->type;
return nm->type;
}
} catch( rlog::Error & err )
{
err.log( _RLWarningChannel );
@ -329,6 +338,7 @@ ConfigType readConfig_load( ConfigInfo *nm, const char *path,
} else
{
// No load function - must be an unsupported type..
config->cfgType = nm->type;
return nm->type;
}
}
@ -901,7 +911,7 @@ static
bool selectZeroBlockPassThrough()
{
// xgroup(setup)
return boolDefaultNo(
return boolDefaultYes(
_("Enable file-hole pass-through?\n"
"This avoids writing encrypted blocks when file holes are created."));
}
@ -938,7 +948,8 @@ RootPtr createV6Config( EncFS_Context *ctx, const std::string &rootDir,
bool uniqueIV = false;
bool chainedIV = false;
bool externalIV = false;
bool allowHoles = false;
bool allowHoles = true;
long desiredKDFDuration = NormalKDFDuration;
if (reverseEncryption)
{
@ -973,6 +984,7 @@ RootPtr createV6Config( EncFS_Context *ctx, const std::string &rootDir,
uniqueIV = true;
chainedIV = true;
externalIV = true;
desiredKDFDuration = ParanoiaKDFDuration;
} else
if(answer[0] != 'x')
{
@ -1054,6 +1066,7 @@ RootPtr createV6Config( EncFS_Context *ctx, const std::string &rootDir,
EncFSConfig config;
config.cfgType = Config_V6;
config.cipherIface = cipher->interface();
config.keySize = keySize;
config.blockSize = blockSize;
@ -1067,16 +1080,10 @@ RootPtr createV6Config( EncFS_Context *ctx, const std::string &rootDir,
config.externalIVChaining = externalIV;
config.allowHoles = allowHoles;
config.saltSize = 20;
config.saltData = new unsigned char[config.saltSize];
config.saltSize = 0;
config.saltData = NULL;
config.kdfIterations = 0; // filled in by keying function
if(!cipher->randomize(config.saltData, config.saltSize, true))
{
cout << _("Unable to generate random data for key derivation\n");
return rootInfo;
}
config.desiredKDFDuration = desiredKDFDuration;
cout << "\n";
// xgroup(setup)
@ -1114,17 +1121,11 @@ RootPtr createV6Config( EncFS_Context *ctx, const std::string &rootDir,
CipherKey userKey;
rDebug( "useStdin: %i", useStdin );
if(useStdin)
userKey = getUserKey( cipher, useStdin,
config.saltData, config.saltSize,
config.kdfIterations);
else if(passwordProgram.empty())
userKey = getNewUserKey( cipher,
config.saltData, config.saltSize,
config.kdfIterations );
userKey = config.getUserKey( useStdin );
else if(!passwordProgram.empty())
userKey = config.getUserKey( passwordProgram, rootDir );
else
userKey = getUserKey( passwordProgram, cipher, rootDir,
config.saltData, config.saltSize,
config.kdfIterations );
userKey = config.getNewUserKey();
cipher->writeKey( volumeKey, encodedKey, userKey );
userKey.reset();
@ -1232,7 +1233,7 @@ void showFSInfo( const EncFSConfig &config )
}
{
cout << autosprintf(_("Key Size: %i bits"), config.keySize);
cipher = Cipher::New( config.cipherIface, config.keySize );
cipher = config.getCipher();
if(!cipher)
{
// xgroup(diag)
@ -1240,6 +1241,13 @@ void showFSInfo( const EncFSConfig &config )
} else
cout << "\n";
}
if(config.kdfIterations > 0 && config.saltSize > 0)
{
cout << autosprintf(_("Using PBKDF2, with %i iterations"),
config.kdfIterations) << "\n";
cout << autosprintf(_("Salt Size: %i bits"), 8*config.saltSize)
<< "\n";
}
if(config.blockMACBytes)
{
if(config.subVersion < 20040813)
@ -1287,9 +1295,48 @@ void showFSInfo( const EncFSConfig &config )
}
cout << "\n";
}
CipherKey getUserKey( const shared_ptr<Cipher> &cipher, bool useStdin,
const unsigned char *salt, int saltSize, int &iterations)
shared_ptr<Cipher> EncFSConfig::getCipher()
{
return Cipher::New( cipherIface, keySize );
}
CipherKey EncFSConfig::makeKey(const char *password, int passwdLen)
{
CipherKey userKey;
shared_ptr<Cipher> cipher = getCipher();
// if no salt is set and we're creating a new password for a new
// FS type, then initialize salt..
if(saltSize == 0 && kdfIterations == 0 && cfgType >= Config_V6)
{
// upgrade to using salt
saltSize = 20;
saltData = new unsigned char[saltSize];
}
if(saltSize > 0)
{
// if iterations isn't known, then we're creating a new key, so
// randomize the salt..
if(kdfIterations == 0 && !cipher->randomize( saltData, saltSize, true))
{
cout << _("Error creating salt\n");
return userKey;
}
userKey = cipher->newKey( password, passwdLen,
kdfIterations, desiredKDFDuration,
saltData, saltSize);
} else
{
userKey = cipher->newKey( password, passwdLen );
}
return userKey;
}
CipherKey EncFSConfig::getUserKey(bool useStdin)
{
char passBuf[MaxPassBuf];
char *res;
@ -1311,8 +1358,7 @@ CipherKey getUserKey( const shared_ptr<Cipher> &cipher, bool useStdin,
if(!res)
cerr << _("Zero length password not allowed\n");
else
userKey = cipher->newKey( passBuf, strlen(passBuf),
iterations, salt, saltSize);
userKey = makeKey(passBuf, strlen(passBuf));
memset( passBuf, 0, sizeof(passBuf) );
@ -1345,9 +1391,8 @@ std::string readPassword( int FD )
return result;
}
CipherKey getUserKey( const std::string &passProg,
const shared_ptr<Cipher> &cipher, const std::string &rootDir,
const unsigned char *salt, int saltSize, int &iterations)
CipherKey EncFSConfig::getUserKey( const std::string &passProg,
const std::string &rootDir )
{
// have a child process run the command and get the result back to us.
int fds[2], pid;
@ -1418,8 +1463,7 @@ CipherKey getUserKey( const std::string &passProg,
waitpid(pid, NULL, 0);
// convert to key..
result = cipher->newKey( password.c_str(), password.length(),
iterations, salt, saltSize );
result = makeKey(password.c_str(), password.length());
// clear buffer..
password.assign( password.length(), '\0' );
@ -1427,8 +1471,7 @@ CipherKey getUserKey( const std::string &passProg,
return result;
}
CipherKey getNewUserKey( const shared_ptr<Cipher> &cipher,
const unsigned char *salt, int saltSize, int &iterations )
CipherKey EncFSConfig::getNewUserKey()
{
CipherKey userKey;
char passBuf[MaxPassBuf];
@ -1444,9 +1487,9 @@ CipherKey getNewUserKey( const shared_ptr<Cipher> &cipher,
sizeof(passBuf2)-1, RPP_ECHO_OFF);
if(res1 && res2 && !strcmp(passBuf, passBuf2))
userKey = cipher->newKey( passBuf, strlen(passBuf),
iterations, salt, saltSize );
else
{
userKey = makeKey(passBuf, strlen(passBuf));
} else
{
// xgroup(common) -- probably not common, but group with the others
cerr << _("Passwords did not match, please try again\n");
@ -1478,8 +1521,7 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
}
// first, instanciate the cipher.
shared_ptr<Cipher> cipher =
Cipher::New( config.cipherIface, config.keySize );
shared_ptr<Cipher> cipher = config.getCipher();
if(!cipher)
{
rError(_("Unable to find cipher %s, version %i:%i:%i"),
@ -1494,14 +1536,13 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
// get user key
CipherKey userKey;
rDebug( "useStdin: %i", opts->useStdin );
if(opts->passwordProgram.empty())
userKey = getUserKey( cipher, opts->useStdin,
config.saltData, config.saltSize, config.kdfIterations);
else
userKey = getUserKey( opts->passwordProgram,
cipher, opts->rootDir,
config.saltData, config.saltSize, config.kdfIterations);
if(opts->passwordProgram.empty())
{
rDebug( "useStdin: %i", opts->useStdin );
userKey = config.getUserKey( opts->useStdin );
} else
userKey = config.getUserKey( opts->passwordProgram, opts->rootDir );
if(!userKey)
return rootInfo;

View File

@ -38,8 +38,20 @@ std::string parentDirectory( const std::string &path );
// do it and return true.
bool userAllowMkdir( const char *dirPath, mode_t mode );
enum ConfigType
{
Config_None = 0,
Config_Prehistoric,
Config_V3,
Config_V4,
Config_V5,
Config_V6
};
struct EncFSConfig
{
ConfigType cfgType;
std::string creator;
int subVersion;
@ -54,6 +66,7 @@ struct EncFSConfig
int saltSize; // in bytes
unsigned char *saltData;
int kdfIterations;
long desiredKDFDuration;
int blockMACBytes; // MAC headers on blocks..
int blockMACRandBytes; // number of random bytes in the block header
@ -66,6 +79,7 @@ struct EncFSConfig
EncFSConfig()
{
cfgType = Config_None;
subVersion = 0;
blockMACBytes = 0;
blockMACRandBytes = 0;
@ -77,6 +91,7 @@ struct EncFSConfig
saltSize = 0;
saltData = NULL;
kdfIterations = 0;
desiredKDFDuration = 500;
}
~EncFSConfig()
@ -84,16 +99,15 @@ struct EncFSConfig
if(saltData != NULL)
delete[] saltData;
}
};
enum ConfigType
{
Config_None = 0,
Config_Prehistoric,
Config_V3,
Config_V4,
Config_V5,
Config_V6
CipherKey getUserKey(bool useStdin);
CipherKey getUserKey(const std::string &passwordProgram,
const std::string &rootDir);
CipherKey getNewUserKey();
shared_ptr<Cipher> getCipher();
private:
CipherKey makeKey(const char *password, int passwdLen);
};
class Cipher;
@ -178,14 +192,4 @@ bool readV6Config( const char *configFile, EncFSConfig *config,
bool writeV6Config( const char *configFile, EncFSConfig *config);
CipherKey getUserKey(const boost::shared_ptr<Cipher> &cipher, bool useStdin,
const unsigned char *salt, int saltLen, int &iterations);
CipherKey getUserKey(const std::string &passwordProgram,
const boost::shared_ptr<Cipher> &cipher,
const std::string &rootDir,
const unsigned char *salt, int saltLen, int &iterations);
CipherKey getNewUserKey(const boost::shared_ptr<Cipher> &cipher,
const unsigned char *salt, int saltLen, int &iterations);
#endif

View File

@ -84,7 +84,12 @@ Interface NullCipher::interface() const
}
CipherKey NullCipher::newKey(const char *, int,
int &, const unsigned char *, int )
int &, long, const unsigned char *, int )
{
return gNullKey;
}
CipherKey NullCipher::newKey(const char *, int)
{
return gNullKey;
}

View File

@ -38,7 +38,9 @@ public:
// create a new key based on a password
virtual CipherKey newKey(const char *password, int passwdLength,
int &iterationCount, const unsigned char *salt, int saltLen);
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen);
virtual CipherKey newKey(const char *password, int passwdLength);
// create a new random key
virtual CipherKey newRandomKey();

View File

@ -53,9 +53,6 @@ const int MAX_KEYLENGTH = 32; // in bytes (256 bit)
const int MAX_IVLENGTH = 16;
const int KEY_CHECKSUM_BYTES = 4;
// how long we'd like the PDF function to take, in micro seconds
const long DesiredPDFTime = 500 * 1000; // 1/2 second
#ifndef MIN
inline int MIN(int a, int b)
{
@ -139,7 +136,7 @@ long time_diff(const timeval &end, const timeval &start)
int TimedPBKDF2(const char *pass, int passlen,
const unsigned char *salt, int saltlen,
int keylen, unsigned char *out,
int &numIterations)
long desiredPDFTime)
{
int iter = 1000;
timeval start, end;
@ -150,25 +147,21 @@ int TimedPBKDF2(const char *pass, int passlen,
int res = PKCS5_PBKDF2_HMAC_SHA1(pass, passlen, salt, saltlen,
iter, keylen, out);
if(res != 1)
return res;
return -1;
gettimeofday( &end, 0 );
long delta = time_diff(end, start);
if(delta < DesiredPDFTime / 8)
if(delta < desiredPDFTime / 8)
{
iter *= 4;
} else if(delta < (5 * DesiredPDFTime / 6))
} else if(delta < (5 * desiredPDFTime / 6))
{
// estimate number of iterations to get close to desired time
iter = (int)((double)iter * (double)DesiredPDFTime
iter = (int)((double)iter * (double)desiredPDFTime
/ (double)delta);
} else
{
// done..
numIterations = iter;
return 1;
}
return iter;
}
}
@ -178,9 +171,9 @@ int TimedPBKDF2(const char *pass, int passlen,
// - Version 2:0 uses BytesToKey.
// We support both 2:0 and 1:0, hence current:revision:age = 2:0:1
// - Version 2:1 adds support for Message Digest function interface
// - Version 3:0 uses PBKDF2 for password derivation
static Interface BlowfishInterface( "ssl/blowfish", 3, 0, 2 );
static Interface AESInterface( "ssl/aes", 3, 0, 2 );
// - Version 2:2 adds PBKDF2 for password derivation
static Interface BlowfishInterface( "ssl/blowfish", 2, 2, 1 );
static Interface AESInterface( "ssl/aes", 2, 2, 1 );
#if defined(HAVE_EVP_BF)
@ -402,7 +395,8 @@ Interface SSL_Cipher::interface() const
is used to encipher/decipher the master key.
*/
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
int &iterationCount, const unsigned char *salt, int saltLen)
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen)
{
const EVP_MD *md = EVP_sha1();
if(!md)
@ -414,35 +408,49 @@ CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
int bytes = 0;
if( iface.current() > 2 )
if(iterationCount == 0)
{
if(iterationCount == 0)
// timed run, fills in iteration count
int res = TimedPBKDF2(password, passwdLength,
salt, saltLen,
_keySize+_ivLength, KeyData(key),
1000 * desiredDuration);
if(res <= 0)
{
// timed run, fills in iteration count
if(TimedPBKDF2(password, passwdLength,
salt, saltLen,
_keySize+_ivLength, KeyData(key),
iterationCount) != 1)
{
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
} else if(iterationCount == 0)
{
rWarning("TimedPBKDF2 failed to fill in iteration count");
return CipherKey();
}
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
} else
iterationCount = res;
} else
{
// known iteration length
if(PKCS5_PBKDF2_HMAC_SHA1(password, passwdLength, salt, saltLen,
iterationCount, _keySize + _ivLength,
KeyData(key)) != 1)
{
// known iteration length
if(PKCS5_PBKDF2_HMAC_SHA1(password, passwdLength, salt, saltLen,
iterationCount, _keySize + _ivLength,
KeyData(key)) != 1)
{
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
}
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
}
} else if( iface.current() > 1 )
}
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength)
{
const EVP_MD *md = EVP_sha1();
if(!md)
{
rError("Unknown digest SHA1");
return CipherKey();
}
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
int bytes = 0;
if( iface.current() > 1 )
{
// now we use BytesToKey, which can deal with Blowfish keys larger then
// 128 bits.

View File

@ -88,7 +88,10 @@ public:
// create a new key based on a password
virtual CipherKey newKey(const char *password, int passwdLength,
int &iterationCount, const unsigned char *salt, int saltLen);
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen);
// deprecated - for backward compatibility
virtual CipherKey newKey(const char *password, int passwdLength);
// create a new random key
virtual CipherKey newRandomKey();

View File

@ -295,16 +295,18 @@ A choice is provided for two pre-configured settings ('standard' and
'paranoia'), along with an expert configuration mode.
I<Standard> mode uses the following settings:
Cipher: Blowfish
Key Size: 160 bits
Filesystem Block Size: 512 bytes
Cipher: AES
Key Size: 192 bits
PBKDF2 with 1/2 second runtime, 160 bit salt
Filesystem Block Size: 1024 bytes
Filename Encoding: Block encoding with IV chaining
Unique initialization vector file headers
I<Paranoia> mode uses the following settings:
Cipher: AES
Key Size: 256 bits
Filesystem Block Size: 512 bytes
PBKDF2 with 3 second runtime, 160 bit salt
Filesystem Block Size: 1024 bytes
Filename Encoding: Block encoding with IV chaining
Unique initialization vector file headers
Message Authentication Code block headers
@ -314,6 +316,23 @@ In the expert / manual configuration mode, each of the above options is
configurable. Here is a list of current options with some notes about what
they mean:
=head1 Key Derivation Function
As of version 1.5, B<EncFS> now uses PBKDF2 as the default key derivation
function. The number of iterations in the keying function is selected based on
wall clock time to generate the key. In standard mode, a target time of 0.5
seconds is used, and in paranoia mode a target of 3.0 seconds is used.
On a 1.6Ghz AMD 64 system, it rougly 64k iterations of the key derivation
function can be handled in half a second. The exact number of iterations to
use is stored in the configuration file, as it is needed to remount the
filesystem.
If an B<EncFS> filesystem configuration from 1.4.x is modified with version 1.5
(such as when using encfsctl to change the password), then the new PBKDF2
function will be used and the filesystem will no longer be readable by older
versions.
=over 4
=item I<Cipher>
@ -382,7 +401,7 @@ vector. So "a/foo" and "b/foo" will have completely different encoded names
for "foo". This features has almost no performance impact (for most
operations), and so is the default in all modes.
B<Note:> One significant exception is directory renames. Since the
B<Note:> One significant performance exception is directory renames. Since the
initialization vector for filename encoding depends on the directory path, any
rename requires re-encoding every filename in the tree of the directory being
changed. If there are thousands of files, then EncFS will have to do thousands

View File

@ -669,9 +669,7 @@ static int do_chpasswd( bool useStdin, int argc, char **argv )
// ask for existing password
cout << _("Enter current Encfs password\n");
CipherKey userKey = getUserKey( cipher, useStdin,
config.saltData, config.saltSize,
config.kdfIterations );
CipherKey userKey = config.getUserKey( useStdin );
if(!userKey)
return EXIT_FAILURE;
@ -694,27 +692,11 @@ static int do_chpasswd( bool useStdin, int argc, char **argv )
cout << _("Enter new Encfs password\n");
// reinitialize salt and iteration count
config.kdfIterations = 0; // generate new
if(config.saltSize != 20)
{
if(config.saltData != NULL)
delete[] config.saltData;
config.saltSize = 20;
config.saltData = new unsigned char[config.saltSize];
}
if(!cipher->randomize(config.saltData, config.saltSize, 20))
{
cout << _("Error creating salt\n");
return EXIT_FAILURE;
}
if( useStdin )
userKey = getUserKey( cipher, true,
config.saltData, config.saltSize,
config.kdfIterations );
userKey = config.getUserKey( true );
else
userKey = getNewUserKey( cipher,
config.saltData, config.saltSize,
config.kdfIterations );
userKey = config.getNewUserKey();
// re-encode the volume key using the new user key and write it out..
int result = EXIT_FAILURE;