【问题标题】:What is the time complexity of crypt function in Linux?Linux中crypt函数的时间复杂度是多少?
【发布时间】:2015-10-02 14:37:34
【问题描述】:

unix中用于认证的crypt函数如下所述:

char *crypt(const char *key, const char *salt);

假设我有key(长度n)和salt(长度m),时间复杂度是多少(顺序为算法)调用这个函数?

【问题讨论】:

标签: c linux algorithm crypt


【解决方案1】:

来自crypt的手册页:

salt 是从集合[a-zA-Z0-9./] 中选择的两个字符的字符串。该字符串用于以 4096 种不同方式之一扰乱算法。

通过取key的前8个字符的每一个的最低7位,得到一个56位的key。

然后使用如此获得的密钥来加密一个常量字符串(使用经过调整的DES 算法),这需要固定的时间。因此,该函数对于任何有效参数都有恒定的运行时间。请注意,这种截断会导致密码非常弱。

正如melpomene 所评论的那样,一些实现提供了对crypt 函数的扩展,允许选择更安全的模式。对于以下内容,我假设您使用的是 GNU C 库中的 crypt 函数。 The manual 说:

对于基于 MD5 的算法,salt 应包含字符串 $1$,后跟最多 8 个字符,以另一个 $ 或字符串结尾结尾。 crypt 的结果将是盐,如果盐不是以 1 结尾,则后跟 $,然后是字母表中的 22 个字符 ./0-9A-Za-z,总共最多 34 个字符。 key 中的每个字符都很重要。

由于 salt 的长度由一个常数固定,并且加密哈希函数的时间复杂度与输入的长度呈线性关系,因此crypt 函数的整体时间复杂度将是线性的在中。

我的 glibc 版本除了支持MD5 之外,还支持更安全的SHA-256(通过$5$ 选择)和SHA-512(通过$6$ 选择)加密哈希函数。它们的输入长度也具有线性时间复杂度。

由于我无法理解我现在实际上应该执行的任务,因此我对各种 crypt 方法进行了计时以支持上述分析。以下是结果。

绘制的是在crypt 函数中花费的执行时间与key 字符串的长度之比。每个数据系列都覆盖有线性回归,但 DES 除外,其中绘制了平均值。我很惊讶 SHA-512 实际上比 SHA-256 快

用于基准测试的代码在这里 (benchmark.c)。

#define _GNU_SOURCE  /* crypt */

#include <errno.h>   /* errno, strerror */
#include <stdio.h>   /* FILE, fopen, fclose, fprintf */
#include <stdlib.h>  /* EXIT_{SUCCESS,FAILURE}, malloc, free, [s]rand */
#include <string.h>  /* size_t, strlen */
#include <assert.h>  /* assert */
#include <time.h>    /* CLOCKS_PER_SEC, clock_t, clock */
#include <unistd.h>  /* crypt */

/* Barrier to stop the compiler from re-ordering instructions. */
#define COMPILER_BARRIER asm volatile("" ::: "memory")

/* First character in the printable ASCII range. */
static const char ascii_first = ' ';

/* Last character in the printable ASCII range. */
static const char ascii_last = '~';

/*
  Benchmark the time it takes to crypt(3) a key of length *keylen* with salt
  *salt*.  The result is written to the stream *ostr* so its computation cannot
  be optimized away.
*/
static clock_t
measure_crypt(const size_t keylen, const char *const salt, FILE *const ostr)
{
  char * key;
  const char * passwd;
  clock_t t1;
  clock_t t2;
  size_t i;
  key = malloc(keylen + 1);
  if (key == NULL)
    return ((clock_t) -1);
  /*
    Generate a random key.  The randomness is extremely poor; never do this in
    cryptographic applications!
  */
  for (i = 0; i < keylen; ++i)
    key[i] = ascii_first + rand() % (ascii_last - ascii_first);
  key[keylen] = '\0';
  assert(strlen(key) == keylen);
  COMPILER_BARRIER;
  t1 = clock();
  COMPILER_BARRIER;
  passwd = crypt(key, salt);
  COMPILER_BARRIER;
  t2 = clock();
  COMPILER_BARRIER;
  fprintf(ostr, "%s\n", passwd);
  free(key);
  return t2 - t1;
}

/*
  The program can be called with zero or one arguments.  The argument, if
  given, will be used as salt.
*/
int
main(const int argc, const char *const *const argv)
{
  const size_t keymax = 2000;
  const size_t keystep = 100;
  const char * salt = "..";  /* default salt */
  FILE * devnull = NULL;  /* redirect noise to black hole */
  int status = EXIT_SUCCESS;
  size_t keylen;
  if (argc > 1)
    salt = argv[1];
  devnull = fopen("/dev/null", "r");
  if (devnull == NULL)
    goto label_catch;
  srand((unsigned) clock());
  for (keylen = 0; keylen <= keymax; keylen += keystep)
    {
      clock_t ticks;
      double millis;
      ticks= measure_crypt(keylen, salt, devnull);
      if (ticks < 0)
        goto label_catch;
      millis = 1.0E3 * ticks / CLOCKS_PER_SEC;
      fprintf(stdout, "%16zu %e\n", keylen, millis);
    }
  goto label_finally;
 label_catch:
  status = EXIT_FAILURE;
  fprintf(stderr, "error: %s\n", strerror(errno));
 label_finally:
  if (devnull != NULL)
    fclose(devnull);
  return status;
}

用于回归和绘图的 Gnuplot 脚本在这里 (plot.gplt)。

set terminal 'svg'
set output 'timings.svg'

set xrange [0 : *]
set yrange [0 : *]

set key top left

set title 'crypt(3) benchmarks'
set xlabel 'key length / bytes'
set ylabel 'computation time / milliseconds'

des(x) = a_des
md5(x) = a_md5 + b_md5 * x
sha256(x) = a_sha256 + b_sha256 * x
sha512(x) = a_sha512 + b_sha512 * x

fit des(x) 'timings.des' via a_des
fit md5(x) 'timings.md5' via a_md5, b_md5
fit sha256(x) 'timings.sha256' via a_sha256, b_sha256
fit sha512(x) 'timings.sha512' via a_sha512, b_sha512

plot des(x)           w l notitle     lc '#75507b' lt 1 lw 2.5,     \
     'timings.des'    w p t 'DES'     lc '#5c3566' pt 7 ps 0.8,     \
     md5(x)           w l notitle     lc '#cc0000' lt 1 lw 2.5,     \
     'timings.md5'    w p t 'MD5'     lc '#a40000' pt 7 ps 0.8,     \
     sha256(x)        w l notitle     lc '#73d216' lt 1 lw 2.5,     \
     'timings.sha256' w p t 'SHA-256' lc '#4e9a06' pt 7 ps 0.8,     \
     sha512(x)        w l notitle     lc '#3465a4' lt 1 lw 2.5,     \
     'timings.sha512' w p t 'SHA-512' lc '#204a87' pt 7 ps 0.8

最后,Makefile 用于将所有内容连接在一起 (GNUmakefile)。

CC := gcc
CPPFLAGS :=
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lcrypt

all: benchmark timings.svg timings.png

benchmark: benchmark.o
    ${CC} -o $@ ${CFLAGS} $^ ${LDFLAGS} ${LIBS}

benchmark.o: benchmark.c
    ${CC} -c ${CPPFLAGS} ${CFLAGS} $<

timings.svg: plot.gplt timings.des timings.md5 timings.sha256 timings.sha512
    gnuplot $<

timings.png: timings.svg
    convert $< $@

timings.des: benchmark
    ./$< '$(shell pwgen -ncs 2)' > $@

timings.md5: benchmark
    ./$< '$$1$$$(shell pwgen -ncs 8)' > $@

timings.sha256: benchmark
    ./$< '$$5$$$(shell pwgen -ncs 16)' > $@

timings.sha512: benchmark
    ./$< '$$6$$$(shell pwgen -ncs 16)' > $@

clean:
    rm -f benchmark benchmark.o fit.log $(wildcard *.o timings.*)

.PHONY: all clean

【讨论】:

  • 请注意,一些crypts 支持额外的哈希算法,如MD5 或SHA-1,由$X$Y$ 形式的盐选择(其中X 标识算法,Y 是实际的盐)。那些通常使用所有输入,所以我猜复杂度将是 O(n+m)(密码长度 + 盐长度)。
  • @melpomene 你是对的。您想发布自己的答案还是我可以将此信息包含在我的答案中?
猜你喜欢
  • 1970-01-01
  • 2018-11-02
  • 2016-12-12
  • 2020-12-03
  • 2017-09-11
  • 2020-03-13
  • 1970-01-01
相关资源
最近更新 更多