improve PBKDF2 performance

The PBKDF2 in sys/geom/eli/pkcs5v2.c is around half the speed it could be

GELI's PBKDF2 uses a simple benchmark to determine a number of iterations
that will takes approximately 2 seconds. The security provided is actually
half what is expected, because an attacker could use the optimized
algorithm to brute force the key in half the expected time.

With this change, all newly generated GELI keys will be approximately 2x
as strong. Previously generated keys will talk half as long to calculate,
resulting in faster mounting of encrypted volumes. Users may choose to
rekey, to generate a new key with the larger default number of iterations
using the geli(8) setkey command.

Security of existing data is not compromised, as ~1 second per brute force
attempt is still a very high threshold.

PR:		202365
Original Research:	https://jbp.io/2015/08/11/pbkdf2-performance-matters/
Submitted by:	Joe Pixton <jpixton@gmail.com> (Original Version), jmg (Later Version)
Reviewed by:	ed, pjd, delphij
Approved by:	secteam, pjd (maintainer)
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D8236
This commit is contained in:
Allan Jude 2017-02-19 19:30:31 +00:00
parent d7aee58a78
commit 85c15ab853
11 changed files with 1322 additions and 28 deletions

View File

@ -425,6 +425,10 @@
.. ..
.. ..
.. ..
eli
pbkdf2
..
..
.. ..
kern kern
acct acct

View File

@ -24,6 +24,10 @@ WARNS?= 0
.PATH: ${.CURDIR}/../../../lib/libc/string .PATH: ${.CURDIR}/../../../lib/libc/string
SRCS+= bcmp.c bcopy.c bzero.c SRCS+= bcmp.c bcopy.c bzero.c
# need explicit_bzero for crypto
.PATH: ${.CURDIR}/../../../sys/libkern
SRCS+= explicit_bzero.c
# Our password input method # Our password input method
SRCS+= pwgets.c SRCS+= pwgets.c

View File

@ -692,8 +692,8 @@ int g_eli_crypto_decrypt(u_int algo, u_char *data, size_t datasize,
const u_char *key, size_t keysize); const u_char *key, size_t keysize);
struct hmac_ctx { struct hmac_ctx {
SHA512_CTX shactx; SHA512_CTX innerctx;
u_char k_opad[128]; SHA512_CTX outerctx;
}; };
void g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey, void g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey,

View File

@ -47,7 +47,7 @@ void
g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey, g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey,
size_t hkeylen) size_t hkeylen)
{ {
u_char k_ipad[128], key[128]; u_char k_ipad[128], k_opad[128], key[128];
SHA512_CTX lctx; SHA512_CTX lctx;
u_int i; u_int i;
@ -66,13 +66,17 @@ g_eli_crypto_hmac_init(struct hmac_ctx *ctx, const uint8_t *hkey,
/* XOR key with ipad and opad values. */ /* XOR key with ipad and opad values. */
for (i = 0; i < sizeof(key); i++) { for (i = 0; i < sizeof(key); i++) {
k_ipad[i] = key[i] ^ 0x36; k_ipad[i] = key[i] ^ 0x36;
ctx->k_opad[i] = key[i] ^ 0x5c; k_opad[i] = key[i] ^ 0x5c;
} }
bzero(key, sizeof(key)); explicit_bzero(key, sizeof(key));
/* Perform inner SHA512. */ /* Start inner SHA512. */
SHA512_Init(&ctx->shactx); SHA512_Init(&ctx->innerctx);
SHA512_Update(&ctx->shactx, k_ipad, sizeof(k_ipad)); SHA512_Update(&ctx->innerctx, k_ipad, sizeof(k_ipad));
bzero(k_ipad, sizeof(k_ipad)); explicit_bzero(k_ipad, sizeof(k_ipad));
/* Start outer SHA512. */
SHA512_Init(&ctx->outerctx);
SHA512_Update(&ctx->outerctx, k_opad, sizeof(k_opad));
explicit_bzero(k_opad, sizeof(k_opad));
} }
void void
@ -80,28 +84,27 @@ g_eli_crypto_hmac_update(struct hmac_ctx *ctx, const uint8_t *data,
size_t datasize) size_t datasize)
{ {
SHA512_Update(&ctx->shactx, data, datasize); SHA512_Update(&ctx->innerctx, data, datasize);
} }
void void
g_eli_crypto_hmac_final(struct hmac_ctx *ctx, uint8_t *md, size_t mdsize) g_eli_crypto_hmac_final(struct hmac_ctx *ctx, uint8_t *md, size_t mdsize)
{ {
u_char digest[SHA512_MDLEN]; u_char digest[SHA512_MDLEN];
SHA512_CTX lctx;
SHA512_Final(digest, &ctx->shactx); /* Complete inner hash */
/* Perform outer SHA512. */ SHA512_Final(digest, &ctx->innerctx);
SHA512_Init(&lctx);
SHA512_Update(&lctx, ctx->k_opad, sizeof(ctx->k_opad)); /* Complete outer hash */
bzero(ctx, sizeof(*ctx)); SHA512_Update(&ctx->outerctx, digest, sizeof(digest));
SHA512_Update(&lctx, digest, sizeof(digest)); SHA512_Final(digest, &ctx->outerctx);
SHA512_Final(digest, &lctx);
bzero(&lctx, sizeof(lctx)); explicit_bzero(ctx, sizeof(*ctx));
/* mdsize == 0 means "Give me the whole hash!" */ /* mdsize == 0 means "Give me the whole hash!" */
if (mdsize == 0) if (mdsize == 0)
mdsize = SHA512_MDLEN; mdsize = SHA512_MDLEN;
bcopy(digest, md, mdsize); bcopy(digest, md, mdsize);
bzero(digest, sizeof(digest)); explicit_bzero(digest, sizeof(digest));
} }
void void

View File

@ -56,6 +56,7 @@ pkcs5v2_genkey(uint8_t *key, unsigned keylen, const uint8_t *salt,
uint8_t *counter, *keyp; uint8_t *counter, *keyp;
u_int i, bsize, passlen; u_int i, bsize, passlen;
uint32_t count; uint32_t count;
struct hmac_ctx startpoint, ctx;
passlen = strlen(passphrase); passlen = strlen(passphrase);
bzero(key, keylen); bzero(key, keylen);
@ -66,20 +67,23 @@ pkcs5v2_genkey(uint8_t *key, unsigned keylen, const uint8_t *salt,
for (count = 1; keylen > 0; count++, keylen -= bsize, keyp += bsize) { for (count = 1; keylen > 0; count++, keylen -= bsize, keyp += bsize) {
bsize = MIN(keylen, sizeof(md)); bsize = MIN(keylen, sizeof(md));
counter[0] = (count >> 24) & 0xff; be32enc(counter, count);
counter[1] = (count >> 16) & 0xff;
counter[2] = (count >> 8) & 0xff; g_eli_crypto_hmac_init(&startpoint, passphrase, passlen);
counter[3] = count & 0xff; ctx = startpoint;
g_eli_crypto_hmac(passphrase, passlen, saltcount, g_eli_crypto_hmac_update(&ctx, saltcount, sizeof(saltcount));
sizeof(saltcount), md, 0); g_eli_crypto_hmac_final(&ctx, md, sizeof(md));
xor(keyp, md, bsize); xor(keyp, md, bsize);
for(i = 1; i < iterations; i++) { for(i = 1; i < iterations; i++) {
g_eli_crypto_hmac(passphrase, passlen, md, sizeof(md), ctx = startpoint;
md, 0); g_eli_crypto_hmac_update(&ctx, md, sizeof(md));
g_eli_crypto_hmac_final(&ctx, md, sizeof(md));
xor(keyp, md, bsize); xor(keyp, md, bsize);
} }
} }
explicit_bzero(&startpoint, sizeof(startpoint));
explicit_bzero(&ctx, sizeof(ctx));
} }
#ifndef _KERNEL #ifndef _KERNEL

View File

@ -3,5 +3,6 @@
TESTSDIR= ${TESTSBASE}/sys/geom TESTSDIR= ${TESTSBASE}/sys/geom
TESTS_SUBDIRS+= class TESTS_SUBDIRS+= class
TESTS_SUBDIRS+= eli
.include <bsd.test.mk> .include <bsd.test.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.include <bsd.own.mk>
TESTSDIR= ${TESTSBASE}/sys/geom/eli
ATF_TESTS_SUBDIRS+= pbkdf2
.include <bsd.test.mk>

View File

@ -0,0 +1,28 @@
# $FreeBSD$
.include <bsd.own.mk>
SYSDIR = ../../../../../sys
.PATH: ${SYSDIR}/geom/eli
.PATH: ${SYSDIR}/crypto/sha2
TESTSDIR= ${TESTBASE}/sys/geom/eli/pbkdf2
ATF_TESTS_C= pbkdf2
CFLAGS.pbkdf2= -I${SYSDIR}
SRCS.pbkdf2= \
hmactest.c \
g_eli_crypto.c \
g_eli_hmac.c \
pkcs5v2.c \
sha512c.c \
sha256c.c
LIBADD.pbkdf2= crypto
testvect.h: gentestvect.py
python gentestvect.py > $@
.include <bsd.test.mk>

View File

@ -0,0 +1,63 @@
#!/usr/bin/env python
# $FreeBSD$
from hashlib import pbkdf2_hmac
import hashlib
import itertools
import string
#From: https://stackoverflow.com/questions/14945095/how-to-escape-string-for-generated-c
def cstring(s, encoding='ascii'):
if isinstance(s, unicode):
s = s.encode(encoding)
result = ''
for c in s:
if not (32 <= ord(c) < 127) or c in ('\\', '"'):
result += '\\%03o' % ord(c)
else:
result += c
return '"' + result + '"'
intarr = lambda y: ', '.join(map(lambda x: str(ord(x)), y))
_randfd = open('/dev/urandom', 'rb')
_maketrans = string.maketrans('', '')
def randgen(l, delchrs=None):
if delchrs is None:
return _randfd.read(l)
s = ''
while len(s) < l:
s += string.translate(_randfd.read(l - len(s)), _maketrans,
delchrs)
return s
def printhmacres(salt, passwd, itr, hmacout):
print '\t{ %s, %d, %s, %d, %s, %d },' % (cstring(salt), len(salt),
cstring(passwd), itr, cstring(hmacout), len(hmacout))
if __name__ == '__main__':
import sys
if len(sys.argv) == 1:
hashfun = 'sha512'
else:
hashfun = sys.argv[1]
if hashfun not in hashlib.algorithms:
print 'Invalid hash function: %s' % `hashfun`
sys.exit(1)
print '/* Test Vectors for PBKDF2-%s */' % hashfun.upper()
print '\t/* salt, saltlen, passwd, itr, hmacout, hmacoutlen */'
for saltl in xrange(8, 64, 8):
for itr in itertools.chain(xrange(100, 1000, 100), xrange(1000,
10000, 1000)):
for passlen in xrange(8, 80, 8):
salt = randgen(saltl)
passwd = randgen(passlen, '\x00')
hmacout = pbkdf2_hmac(hashfun, passwd, salt,
itr)
printhmacres(salt, passwd, itr, hmacout)

View File

@ -0,0 +1,41 @@
/*
* $FreeBSD$
*/
#include <sys/param.h>
#include <atf-c.h>
#include <geom/eli/pkcs5v2.h>
const struct {
char *salt;
size_t saltlen;
char *passwd;
int iterations;
char *hmacout;
size_t hmaclen;
} testdata[] = {
#include "testvect.h"
};
ATF_TC_WITHOUT_HEAD(hmactest);
ATF_TC_BODY(hmactest, tc)
{
size_t i;
uint8_t hmacout[64];
for (i = 0; i < nitems(testdata); i++) {
pkcs5v2_genkey(hmacout, testdata[i].hmaclen,
(uint8_t *)testdata[i].salt, testdata[i].saltlen,
testdata[i].passwd, testdata[i].iterations);
ATF_REQUIRE(bcmp(hmacout, testdata[i].hmacout,
testdata[i].hmaclen) == 0);
}
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, hmactest);
return (atf_no_error());
}

File diff suppressed because it is too large Load Diff