Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile.dep
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,10 @@ ifneq (,$(filter random,$(USEMODULE)))
USEMODULE += luid
endif

ifneq (,$(filter hashes,$(USEMODULE)))
USEMODULE += crypto
endif

ifneq (,$(filter asymcute,$(USEMODULE)))
USEMODULE += sock_udp
USEMODULE += sock_util
Expand Down
114 changes: 114 additions & 0 deletions sys/hashes/pbkdf2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup examples
* @{
*
* @file
* @brief PBKDF2 key derivation implementation- only sha256 is supported
* at the moment, and the key size is fixed.
*
* @author Juan I Carrano <j.carrano@fu-berlin.de>
*
* @}
*/

#include <string.h>

#include "hashes/sha256.h"
#include "hashes/pbkdf2.h"
#include "crypto/helper.h"

static void inplace_xor_scalar(uint8_t *bytes, size_t len, uint8_t c)
{
while (len--) {
*bytes ^= c;
bytes++;
}
}

static void inplace_xor_digests(uint8_t *d1, const uint8_t *d2)
{
int len = SHA256_DIGEST_LENGTH;

while (len--) {
*d1 ^= *d2;
d1++;
d2++;
}
}

#define _WIPE(a) crypto_secure_wipe(&a, sizeof(a))

void pbkdf2_sha256(const uint8_t *password, size_t password_len,
const uint8_t *salt, size_t salt_len,
int iterations,
uint8_t *output)
{
sha256_context_t inner;
sha256_context_t outer;
uint8_t tmp_digest[SHA256_DIGEST_LENGTH];
int first_iter = 1;

{
uint8_t processed_pass[SHA256_INTERNAL_BLOCK_SIZE] = {0};

if (password_len > sizeof(processed_pass)) {
sha256_init(&inner);
sha256_update(&inner, password, password_len);
sha256_final(&inner, processed_pass);
} else {
memcpy(processed_pass, password, password_len);
}

sha256_init(&inner);
sha256_init(&outer);

/* Trick: doing inner.update(processed_pass XOR 0x36) followed by
* inner.update(processed_pass XOR 0x5C) requires remembering
* processed_pass. Instead undo the first XOR while doing the second.
*/
inplace_xor_scalar(processed_pass, sizeof(processed_pass), 0x36);
sha256_update(&inner, processed_pass, sizeof(processed_pass));

inplace_xor_scalar(processed_pass, sizeof(processed_pass), 0x36 ^ 0x5C);
sha256_update(&outer, processed_pass, sizeof(processed_pass));

_WIPE(processed_pass);
}

memset(output, 0, SHA256_DIGEST_LENGTH);

while (iterations--) {
sha256_context_t inner_copy = inner, outer_copy = outer;

if (first_iter) {
sha256_update(&inner_copy, salt, salt_len);
sha256_update(&inner_copy, "\x00\x00\x00\x01", 4);
first_iter = 0;
} else {
sha256_update(&inner_copy, tmp_digest, sizeof(tmp_digest));
}

sha256_final(&inner_copy, tmp_digest);

sha256_update(&outer_copy, tmp_digest, sizeof(tmp_digest));
sha256_final(&outer_copy, tmp_digest);

inplace_xor_digests(output, tmp_digest);

if (iterations == 0) {
_WIPE(inner_copy);
_WIPE(outer_copy);
}
}

_WIPE(inner);
_WIPE(outer);
_WIPE(tmp_digest);
}
48 changes: 48 additions & 0 deletions sys/include/hashes/pbkdf2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_hashes_pbkdf2 PBKDF2
* @ingroup sys_hashes
* @brief PBKDF2 key derivation implementation.
* @{
*
* @file
* @brief PBKDF2 key derivation implementation.
*
* @author Juan I Carrano <j.carrano@fu-berlin.de>
*
* @}
*/

#ifndef HASHES_PBKDF2_H
#define HASHES_PBKDF2_H

#include "hashes/sha256.h"

#ifdef __cplusplus
extern "C" {
#endif

#define PBKDF2_KEY_SIZE SHA256_DIGEST_LENGTH

/**
* Create a key from a password and hash using PBKDF2.
*
* @param iterations Number of rounds. Must be >1.
* @param[out] output Array of size PBKDF2_KEY_SIZE
*/
void pbkdf2_sha256(const uint8_t *password, size_t password_len,
const uint8_t *salt, size_t salt_len,
int iterations,
uint8_t *output);

#ifdef __cplusplus
}
#endif

#endif /* HASHES_PBKDF2_H */
17 changes: 17 additions & 0 deletions tests/pbkdf2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
include ../Makefile.tests_common

# This application uses getchar and thus expects input from stdio
USEMODULE += stdin
USEMODULE += hashes
USEMODULE += base64

ifeq (,$(filter native,$(BOARD)))
RIOT_TERMINAL = other
TERMPROG = miniterm.py
TERMFLAGS = --eol LF /dev/ttyACM0 115200
endif

# boards fail due to UART weirdness and line buffering done by the terminal
BOARD_WHITELIST += native

include $(RIOTBASE)/Makefile.include
11 changes: 11 additions & 0 deletions tests/pbkdf2/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Test PBKDF2 implementation
==========================

This test evaluates the RIOT implementation against a reference. The objective
is flexibility and clarity, and for this reason there are no hard coded vectors,
but instead the test is interactive, with the DUT processing vectors given
through the serial interface.

This means that the test is slower, but more complete and trustworthy.

The test is completely automated.
137 changes: 137 additions & 0 deletions tests/pbkdf2/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2019 Freie Universität Berlin.
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

/**
* @{
*
* @file
* @brief Test PBKDF2-sha256 implementation.
*
* @author Juan Carrano <j.carrano@fu-berlin.de>
*
* This application reads (password, salt, iterations) tuples from the
* standard input and outputs the derived key.
*
* The salt must be base64 encoded. The key is printed as base64.
* @}
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "base64.h"
#include "hashes/pbkdf2.h"

const char error_message[] = "{error}";
const char input_message[] = "{ready}";

#define LINEBUF_SZ (128)

enum TEST_STATE {
TEST_READ_PASS,
TEST_READ_SALT,
TEST_READ_ITERS,
TEST_COMPUTE,
TEST_ERROR
};

int main(void)
{
static char linebuf[LINEBUF_SZ];

/* There will be a few bytes wasted here */
static char password[LINEBUF_SZ];
static uint8_t salt[LINEBUF_SZ];
static uint8_t key[PBKDF2_KEY_SIZE];

size_t passwd_len = 0, salt_len = 0;
int iterations = 0;

enum TEST_STATE state = TEST_READ_PASS;

while ((puts(input_message), fgets(linebuf, LINEBUF_SZ, stdin) != NULL)) {
char *s_end;
int conversion_status, line_len = strlen(linebuf)-1;
size_t b64_buff_size;

linebuf[line_len] = '\0';

switch (state) {
case TEST_READ_PASS:
strcpy(password, linebuf);
passwd_len = line_len;

state++;

break;
case TEST_READ_SALT:
/* work around bug in base64_decode */
if (line_len == 0) {
salt_len = 0;
conversion_status = BASE64_SUCCESS;
} else {
salt_len = sizeof(salt);
conversion_status = base64_decode((uint8_t*)linebuf,
line_len+1,
salt, &salt_len);
}

if(conversion_status == BASE64_SUCCESS) {
state++;
} else {
state = TEST_ERROR;
}

break;
case TEST_READ_ITERS:
iterations = strtol(linebuf, &s_end, 10);

if (*s_end != '\0') {
state = TEST_ERROR;
} else {
state++;
}

break;
default:
assert(1);
break;
}

switch (state) {
case TEST_COMPUTE:
pbkdf2_sha256((uint8_t*)password, passwd_len, salt, salt_len,
iterations, key);

b64_buff_size = sizeof(linebuf);
conversion_status = base64_encode(key, sizeof(key),
(uint8_t*)linebuf,
&b64_buff_size);

if(conversion_status == BASE64_SUCCESS) {
linebuf[b64_buff_size] = 0;
puts(linebuf);
} else {
puts(error_message);
}

state = TEST_READ_PASS;
break;
case TEST_ERROR:
puts(error_message);
state = TEST_READ_PASS;
break;
default:
break;
}
}

return 0;
}
50 changes: 50 additions & 0 deletions tests/pbkdf2/tests/01-random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
# Copyright (C) 2019 Freie Universität Berlin
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
# Author: Juan Carrano <j.carrano@fu-berlin.de>
"""Random test vectors"""

import hashlib
import random

import test_base

randgen = random.Random(42)

_pass_chars = [c for c in (chr(x) for x in range(128))
if c.isprintable()]


def randompass(length):
return "".join(randgen.choices(_pass_chars, k=length))


def randomsalt(bytes_):
return (randgen.getrandbits(bytes_*8).to_bytes(bytes_, 'big')
if bytes_ else b'')


def randomvector(pass_len, salt_len, iters):
pass_ = randompass(pass_len)
salt = randomsalt(salt_len)
key = hashlib.pbkdf2_hmac('sha256', pass_.encode('ascii'), salt, iters)

return pass_, salt, iters, key


VECTORS = [
randomvector(0, 16, 10),
randomvector(8, 0, 10),
randomvector(9, 64, 1),
randomvector(65, 38, 20),
randomvector(32, 15, 12),
randomvector(48, 32, 15),
]


if __name__ == "__main__":
test_base.main(VECTORS)
Loading