【发布时间】:2015-10-02 14:37:34
【问题描述】:
unix中用于认证的crypt函数如下所述:
char *crypt(const char *key, const char *salt);
假设我有key(长度n)和salt(长度m),时间复杂度是多少(顺序为算法)调用这个函数?
【问题讨论】:
unix中用于认证的crypt函数如下所述:
char *crypt(const char *key, const char *salt);
假设我有key(长度n)和salt(长度m),时间复杂度是多少(顺序为算法)调用这个函数?
【问题讨论】:
来自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)(密码长度 + 盐长度)。