Using iter-time doesn't give the desired timeout and security

Bug #1703691 reported by Patrik Nilsson
256
This bug affects 1 person
Affects Status Importance Assigned to Milestone
cryptsetup
New
Undecided
Unassigned
cryptsetup (Ubuntu)
Confirmed
Undecided
Unassigned

Bug Description

I have formatted using cryptsetup with option iter-time for use longer timeout than the default 2000ms. When I write 60000 I get 18 seconds and 10000 is 2 seconds. Something is terrible wrong.

To get a timeout closer to what I want I wrote this script, which is an except:

  benchmarkiterations=$(cryptsetup benchmark | grep -e '^PBKDF2-sha256' | awk '{print $2}')

  cryptsetup -q --key-file ${keyfileluks} luksFormat -i $(($cryptsetuptimeout)) -c aes -s 256 -h sha256 --uuid=${uuidluks} --use-random $loopluks
  timerstart=$(date +%s.%N)
  cryptsetup -q --key-file ${keyfileluks} luksOpen $loopluks ${uuidluks}_${mapper}crypt
  timerend=$(date +%s.%N)
  cryptsetup -q luksClose ${uuidluks}_${mapper}crypt

  timerdiff=$(bc -l <<< "($timerend-$timerstart)*1000")
  timerfactor=$(bc -l <<< "$cryptsetuptimeout/$timerdiff")
  timeoutnew=$(bc -l <<< "scale=0; ($cryptsetuptimeout*$timerfactor*1.2)/1")

  cryptsetup -q --key-file ${keyfileluks} luksFormat -i $timeoutnew -c aes -s 256 -h sha256 --uuid=${uuidluks} --use-random $loopluks
  iterations=$(cryptsetup luksDump $loopluks | grep -e '[[:space:]]Iterations:' | awk '{print $2}')
  iterationspermsec=$(($iterations/$cryptsetuptimeout))
  if [ "$iterationspermsec" -lt "500" ]; then
   echo "Error too few iterations: $iterationspermsec"
   exit 1
  fi

First I benchmark the computer with rounds per second and then test the desired timeout. Then I compare the benchmark with the rounds per seconds got while testing and then calculates a new approximate value. Then I finally format the device. This gives better values.

At lines around 700 in keymanage.c master key digest is set to the 1/8 of the expected value:

 /* Compute master key digest */
 iteration_time_ms /= 8;
 header->mkDigestIterations = at_least((uint32_t)(*PBKDF2_per_sec/1024) * iteration_time_ms,
           LUKS_MKD_ITERATIONS_MIN);

At lines around 800 in keymanage.c about half of the timeout goes away:

 /*
  * Avoid floating point operation
  * Final iteration count is at least LUKS_SLOT_ITERATIONS_MIN
  */
 PBKDF2_temp = (*PBKDF2_per_sec / 2) * (uint64_t)iteration_time_ms;
 PBKDF2_temp /= 1024;
 if (PBKDF2_temp > UINT32_MAX)
  PBKDF2_temp = UINT32_MAX;
 hdr->keyblock[keyIndex].passwordIterations = at_least((uint32_t)PBKDF2_temp,
             LUKS_SLOT_ITERATIONS_MIN);

Moreover one second are 1000 ms, not 1024.

Benchmarking of PBKDF always gives to low speed: (from pbkdf_check.c from line 54)

int crypt_pbkdf_check(const char *kdf, const char *hash,
        const char *password, size_t password_size,
        const char *salt, size_t salt_size,
        uint64_t *iter_secs)
{
 struct rusage rstart, rend;
 int r = 0, step = 0;
 long ms = 0;
 char buf;
 unsigned int iterations;

 if (!kdf || !hash)
  return -EINVAL;

 iterations = 1 << 15;
 while (ms < 500) {
  if (getrusage(RUSAGE_SELF, &rstart) < 0)
   return -EINVAL;

  r = crypt_pbkdf(kdf, hash, password, password_size, salt,
    salt_size, &buf, 1, iterations);
  if (r < 0)
   return r;

  if (getrusage(RUSAGE_SELF, &rend) < 0)
   return -EINVAL;

  ms = time_ms(&rstart, &rend);
  if (ms > 500)
   break;

  if (ms <= 62)
   iterations <<= 4;
  else if (ms <= 125)
   iterations <<= 3;
  else if (ms <= 250)
   iterations <<= 2;
  else
   iterations <<= 1;

  if (++step > 10 || !iterations)
   return -EINVAL;
 }

 if (iter_secs)
  *iter_secs = (iterations * 1000) / ms;
 return r;
}

It is not as secure as you expect. You can decrypt the device with a master key.

https://unix.stackexchange.com/questions/119803/how-to-decrypt-luks-with-the-known-master-key

description: updated
description: updated
information type: Private Security → Public Security
Revision history for this message
Seth Arnold (seth-arnold) wrote :

> 60000 I get 18 seconds and 10000 is 2 seconds.

60000 / 10000 == 6.

2.1 * 6 == 12.6
2.2 * 6 == 13.2
..
2.9 * 6 == 17.4

Depending upon where exactly your 2.x seconds measurement for 10000 iterations landed, this makes perfect sense.

> Moreover one second are 1000 ms, not 1024.

This is an old trick -- dividing an integer by 1000 is a lot more work than dividing by 1024. If this is only done once it's a bit silly but if it is done in a tight loop it can have surprising impact on the performance.

Thanks

Changed in cryptsetup (Ubuntu):
status: New → Confirmed
Revision history for this message
Jean-Louis Dupond (dupondje) wrote :

What version of cryptsetup is this?
Also notice the official repository is on gitlab.

There the issue has some newer updates:
https://gitlab.com/cryptsetup/cryptsetup/issues/185

Some changes were done in 1.7.x to the benchmark. So it might already be fixed!

Revision history for this message
asi (gmazyland) wrote :

Obviously it is wrong repository, upstream is https://gitlab.com/cryptsetup/cryptsetup/ and mirror is https://github.com/mbroz/cryptsetup

Please do not link random repos that some people create without any communication with upstream...

Anyway, there were several changes to benchmarking, please read release notes to v 1.7.0
https://www.kernel.org/pub/linux/utils/cryptsetup/v1.7/v1.7.0-ReleaseNotes
and mainly the referenced paper there that covers these issues.

It should be fixed.

Revision history for this message
Marc Deslauriers (mdeslaur) wrote :

Whoops, sorry for pasting the wrong URL. As mentioned, the actual bug URL is here:

https://gitlab.com/cryptsetup/cryptsetup/issues/185

Revision history for this message
asi (gmazyland) wrote :

Volume (master) key digest is there only to verify validity of the key.
The digest iteration count is not relevant for the security of LUKS in normal situation.

This iteration (slowdown) for digest will only help if the volume key was generated by a flawed RNG, where brute-force is possible. (For proper RNG it is impossible to brute force key even without iterations.)

Moreover, if you know some plaintext on device, attacker will use different trick (I think it is described in linked paper): You try to decrypt device and check that plaintext (for example filesytem magic string). This bypasses digest completely and cost is onle one cipher decryption step per try (much cheaper than digest calculation).

IOW: the digest iteration count is not important, only the iteration count in keyslot is, this one slows down password dictionary and brute-force attacks.

To post a comment you must log in.
This report contains Public Security information  
Everyone can see this security related information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.