diff --git a/Makefile.dep b/Makefile.dep
index 9b9dcd53abeae1c24864dcbbbd5ca46273b5310c..cb63f4a1a00cf9317e6478615f994b6fbda1b71c 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -5,7 +5,8 @@ OLD_USEPKG := $(sort $(USEPKG))
 # include board dependencies
 -include $(RIOTBOARD)/$(BOARD)/Makefile.dep
 
-# pull dependencies from drivers
+# pull dependencies from sys and drivers
+include $(RIOTBASE)/sys/Makefile.dep
 include $(RIOTBASE)/drivers/Makefile.dep
 
 ifneq (,$(filter cbor_ctime,$(USEMODULE)))
@@ -615,6 +616,13 @@ ifneq (,$(filter random,$(USEMODULE)))
     USEMODULE += prng_tinymt32
   endif
 
+  ifneq (,$(filter prng_fortuna,$(USEMODULE)))
+    USEMODULE += fortuna
+    USEMODULE += hashes
+    USEMODULE += crypto
+    USEMODULE += xtimer
+  endif
+
   ifneq (,$(filter prng_tinymt32,$(USEMODULE)))
     USEMODULE += tinymt32
   endif
diff --git a/sys/Makefile.dep b/sys/Makefile.dep
new file mode 100644
index 0000000000000000000000000000000000000000..b2e06357c4afda528ad3da368e97b3018557cf3c
--- /dev/null
+++ b/sys/Makefile.dep
@@ -0,0 +1,3 @@
+ifneq (,$(filter prng_fortuna,$(USEMODULE)))
+  CFLAGS += -DCRYPTO_AES
+endif
diff --git a/sys/include/random.h b/sys/include/random.h
index b33bb8c45d20b6e4fe0b8ac09338acf68bd916d0..48b978782fedf1d5dd97b31b1f2e2ef90b71c175 100644
--- a/sys/include/random.h
+++ b/sys/include/random.h
@@ -21,6 +21,7 @@
  *  - Mersenne Twister
  *  - Simple Park-Miller PRNG
  *  - Musl C PRNG
+ *  - Fortuna (CS)PRNG
  */
 
 #ifndef RANDOM_H
diff --git a/sys/random/Makefile b/sys/random/Makefile
index 4e66ead04224b2454d770d49b7628e2d2255ed3f..4436add645206419a333d43bd66b65ba932a4e0b 100644
--- a/sys/random/Makefile
+++ b/sys/random/Makefile
@@ -3,6 +3,10 @@ SRC := random.c
 BASE_MODULE := prng
 SUBMODULES := 1
 
+ifneq (,$(filter prng_fortuna,$(USEMODULE)))
+  DIRS += fortuna
+endif
+
 ifneq (,$(filter prng_tinymt32,$(USEMODULE)))
   DIRS += tinymt32
 endif
diff --git a/sys/random/fortuna.c b/sys/random/fortuna.c
new file mode 100644
index 0000000000000000000000000000000000000000..01ac4fa5db91bee154906175c0bb3355e8094ff2
--- /dev/null
+++ b/sys/random/fortuna.c
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2016-2018 Bas Stottelaar <basstottelaar@gmail.com>
+ *
+ * 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 sys_random
+ * @{
+ * @file
+ *
+ * @brief   Glue-code for Fortuna PRNG.
+ *
+ * @author  Bas Stottelaar <basstottelaar@gmail.com>
+ * @}
+ */
+
+#include "log.h"
+#include "mutex.h"
+
+#include "fortuna/fortuna.h"
+
+/**
+ * @brief This holds the PRNG state.
+ */
+static fortuna_state_t fortuna_state;
+
+/**
+ * @brief Initialize the PRNG with a given number of bytes.
+ */
+static void _init(uint8_t *in, size_t bytes)
+{
+    fortuna_seed_t seed;
+
+    /* ensure a seed of proper length is available, which may not be the case
+       with a small input */
+    memset(seed, 0, sizeof(seed));
+
+    for (int i = 0; i < (int) bytes; i++) {
+        seed[i % sizeof(seed)] ^= in[i];
+    }
+
+    /* initialize the PRNG state */
+    fortuna_init(&fortuna_state);
+
+    /* update the PRNG seed (seed will be overwritten by design!) */
+    fortuna_update_seed(&fortuna_state, &seed);
+}
+
+/**
+ * @brief Wrapper for fortuna_random_data that supports reading more than
+ *        FORTUNA_RESEED_LIMIT and also checks the result.
+ */
+static void _read(uint8_t *out, size_t bytes)
+{
+    do {
+        /* read at most chunk bytes to not exhaust the state at once */
+        size_t chunk = (bytes < FORTUNA_RESEED_LIMIT) ?
+                       bytes : FORTUNA_RESEED_LIMIT;
+
+        int res = fortuna_random_data(&fortuna_state, out, chunk);
+
+        if (res == -1) {
+            LOG_ERROR("random: reading more bytes than allowed.\n");
+        }
+        else if (res == -2) {
+            LOG_ERROR("random: PRNG not initialized.\n");
+        }
+        else if (res == -3) {
+            LOG_ERROR("random: unknown error.\n");
+        }
+
+        /* advance bytes and buffer */
+        bytes -= chunk;
+        out += chunk;
+    } while (bytes > FORTUNA_RESEED_LIMIT);
+}
+
+void random_init_by_array(uint32_t init_key[], int key_length)
+{
+    _init((uint8_t *) init_key, sizeof(uint32_t) * key_length);
+}
+
+void random_init(uint32_t s)
+{
+    _init((uint8_t *) &s, sizeof(s));
+}
+
+uint32_t random_uint32(void)
+{
+    uint32_t data;
+
+    _read((uint8_t *) &data, sizeof(data));
+
+    return data;
+}
diff --git a/sys/random/fortuna/LICENSE.txt b/sys/random/fortuna/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2036fd7bf87a4a0bdfa36b9c740ff93a7a93e1b0
--- /dev/null
+++ b/sys/random/fortuna/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Brandon Lin
+Copyright (c) 2016-2018 Bas Stottelaar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/sys/random/fortuna/Makefile b/sys/random/fortuna/Makefile
new file mode 100755
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/sys/random/fortuna/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/random/fortuna/fortuna.c b/sys/random/fortuna/fortuna.c
new file mode 100755
index 0000000000000000000000000000000000000000..2a60865978978fd4b8e01e5a46c52c2a291fa4a7
--- /dev/null
+++ b/sys/random/fortuna/fortuna.c
@@ -0,0 +1,274 @@
+/**
+ * @brief Fortuna PRNG implementation.
+ *
+ * The MIT License applies to this software. See the included LICENSE.txt file
+ * for more information.
+ */
+
+#include "fortuna.h"
+
+/**
+ * @brief Helper to increment the 128-bit counter (see section 9.4).
+ */
+static inline void fortuna_increment_counter(fortuna_state_t *state)
+{
+    state->gen.counter.split.l++;
+
+    /* on overflow of low, increment high */
+    if (state->gen.counter.split.l == 0) {
+        state->gen.counter.split.h++;
+    }
+}
+
+/*
+ * Corresponds to section 9.4.2.
+ */
+static void fortuna_reseed(fortuna_state_t *state, const uint8_t *seed,
+                           size_t length)
+{
+    sha256_context_t ctx;
+
+    sha256_init(&ctx);
+    sha256_update(&ctx, state->gen.key, 32);
+    sha256_update(&ctx, seed, length);
+    sha256_final(&ctx, state->gen.key);
+
+    /* if the generator was unseeded, this will mark it as seeded */
+    fortuna_increment_counter(state);
+
+#if FORTUNA_CLEANUP
+    memset(&ctx, 0, sizeof(ctx));
+#endif
+}
+
+/*
+ * Corresponds to section 9.4.3.
+ */
+static int fortuna_generate_blocks(fortuna_state_t *state, uint8_t *out,
+                                   size_t blocks)
+{
+    cipher_context_t cipher;
+
+    /* check if generator has been seeded */
+    if (state->gen.counter.split.l == 0 && state->gen.counter.split.h == 0) {
+        return -1;
+    }
+
+    /* initialize cipher based on state */
+    int res = aes_init(&cipher, state->gen.key, FORTUNA_AES_KEY_SIZE);
+
+    if (res != CIPHER_INIT_SUCCESS) {
+#if FORTUNA_CLEANUP
+        memset(&cipher, 0, sizeof(cipher));
+#endif
+        return -2;
+    }
+
+    for (size_t i = 0; i < blocks; i++) {
+        aes_encrypt(&cipher, state->gen.counter.bytes, out + (i * 16));
+        fortuna_increment_counter(state);
+    }
+
+#if FORTUNA_CLEANUP
+    memset(&cipher, 0, sizeof(cipher));
+#endif
+
+    return 0;
+}
+
+/*
+ * Corresponds to section 9.4.4.
+ */
+static int fortuna_pseudo_random_data(fortuna_state_t *state, uint8_t *out,
+                                      size_t bytes)
+{
+    uint8_t buf[16];
+
+#if FORTUNA_RESEED_LIMIT
+    /* maximum number of bytes per read is FORTUNA_RESEED_LIMIT */
+    if (bytes > FORTUNA_RESEED_LIMIT) {
+        return -1;
+    }
+#endif
+
+    /* check if generator has been seeded */
+    if (state->gen.counter.split.l == 0 && state->gen.counter.split.h == 0) {
+        return -2;
+    }
+
+    /* generate blocks per 16 bytes */
+    size_t blocks = bytes / 16;
+
+    if (fortuna_generate_blocks(state, out, blocks)) {
+#if FORTUNA_CLEANUP
+        memset(buf, 0, sizeof(buf));
+#endif
+        return -3;
+    }
+
+    /* generate one block for the remaining bytes */
+    size_t remaining = bytes % 16;
+
+    if (remaining) {
+        if (fortuna_generate_blocks(state, buf, 1)) {
+#if FORTUNA_CLEANUP
+            memset(buf, 0, sizeof(buf));
+#endif
+            return -3;
+        }
+
+        memcpy(out + (blocks * 16), buf, remaining);
+    }
+
+    /* switch to a new key to avoid later compromises of this output */
+    if (fortuna_generate_blocks(state, state->gen.key, 2)) {
+#if FORTUNA_CLEANUP
+        memset(buf, 0, sizeof(buf));
+#endif
+        return -3;
+    }
+
+#if FORTUNA_CLEANUP
+    memset(buf, 0, sizeof(buf));
+#endif
+
+    return 0;
+}
+
+/*
+ * Corresponds to section 9.4.1 and 9.5.4.
+ */
+int fortuna_init(fortuna_state_t *state)
+{
+    /* set everything to zero, then initialize the pools */
+    memset(state, 0, sizeof(fortuna_state_t));
+
+    for (int i = 0; i < (int) FORTUNA_POOLS; i++) {
+        sha256_init(&state->pools[i].ctx);
+    }
+
+#if FORTUNA_RESEED_INTERVAL
+    /* set last reseed to ensure initial time diff is correct */
+    state->last_reseed = xtimer_now_usec64();
+#endif
+
+#if FORTUNA_LOCK
+    /* initialize the lock */
+    mutex_init(&state->lock);
+#endif
+
+    return 0;
+}
+
+/*
+ * Corresponds to section 9.5.5.
+ */
+int fortuna_random_data(fortuna_state_t *state, uint8_t *out, size_t bytes)
+{
+    uint8_t buf[FORTUNA_POOLS * 32];
+
+#if FORTUNA_LOCK
+    mutex_lock(&state->lock);
+#endif
+
+    /* reseed the generator if needed, before returning data */
+#if FORTUNA_RESEED_INTERVAL
+    if (state->pools[0].len >= FORTUNA_MIN_POOL_SIZE &&
+        (xtimer_now_usec64() - state->last_reseed) > FORTUNA_RESEED_INTERVAL) {
+#else
+    if (state->pools[0].len >= FORTUNA_MIN_POOL_SIZE) {
+#endif
+        state->reseeds++;
+        size_t len = 0;
+
+        for (int i = 0; i < (int) FORTUNA_POOLS; i++) {
+            if (state->reseeds | (1 << i)) {
+                sha256_final(&state->pools[i].ctx, &buf[len]);
+                sha256_init(&state->pools[i].ctx);
+                state->pools[i].len = 0;
+
+                /* append length of SHA-256 hash */
+                len += 32;
+            }
+        }
+
+        fortuna_reseed(state, buf, len);
+
+#if FORTUNA_RESEED_INTERVAL
+        state->last_reseed = xtimer_now_usec64();
+#endif
+
+#if FORTUNA_CLEANUP
+        memset(buf, 0, sizeof(buf));
+#endif
+    }
+
+    /* read bytes from the generator */
+    int res = fortuna_pseudo_random_data(state, out, bytes);
+
+#if FORTUNA_LOCK
+    mutex_unlock(&state->lock);
+#endif
+
+    return res;
+}
+
+/*
+ * Corresponds to section 9.5.6.
+ */
+int fortuna_add_random_event(fortuna_state_t *state, const uint8_t *data,
+                             uint8_t length, uint8_t source, uint8_t pool)
+{
+    if (length < 1 || length > 32) {
+        return -1;
+    }
+
+    if (pool >= FORTUNA_POOLS) {
+        return -2;
+    }
+
+#if FORTUNA_LOCK
+    mutex_lock(&state->lock);
+#endif
+
+    uint8_t header[2];
+    header[0] = source;
+    header[1] = length;
+    sha256_update(&state->pools[pool].ctx, header, 2);
+    sha256_update(&state->pools[pool].ctx, (uint8_t *) data, length);
+    state->pools[pool].len += length;
+
+#if FORTUNA_LOCK
+    mutex_unlock(&state->lock);
+#endif
+
+    return 0;
+}
+
+/*
+ * Corresponds to section 9.6.2.
+ */
+int fortuna_write_seed(fortuna_state_t *state, fortuna_seed_t *out)
+{
+    return fortuna_random_data(state, (uint8_t *)out, FORTUNA_SEED_SIZE);
+}
+
+/*
+ * Corresponds to section 9.6.2.
+ */
+int fortuna_update_seed(fortuna_state_t *state, fortuna_seed_t *inout)
+{
+#if FORTUNA_LOCK
+    mutex_lock(&state->lock);
+#endif
+
+    /* reseed using the provided seed */
+    fortuna_reseed(state, (uint8_t *)inout, FORTUNA_SEED_SIZE);
+
+#if FORTUNA_LOCK
+    mutex_unlock(&state->lock);
+#endif
+
+    /* the seed file must be overwritten by a new seed file */
+    return fortuna_random_data(state, (uint8_t *)inout, FORTUNA_SEED_SIZE);
+}
diff --git a/sys/random/fortuna/fortuna.h b/sys/random/fortuna/fortuna.h
new file mode 100755
index 0000000000000000000000000000000000000000..5282e8a9a052745fbd22f27cb3f26041582fea0f
--- /dev/null
+++ b/sys/random/fortuna/fortuna.h
@@ -0,0 +1,232 @@
+/**
+ * @brief Fortuna PRNG implementation.
+ *
+ * The MIT License applies to this software. See the included LICENSE.txt file
+ * for more information.
+ */
+
+/*
+ * This is not your general purpose PRNG: it is hungry for memory.
+ *
+ * See https://www.schneier.com/cryptography/paperfiles/fortuna.pdf for the
+ * implementation details. Code insipred by https://github.com/blin00/os (MIT
+ * licensed), but heavily modified, stripped and improved for RIOT-OS.
+ *
+ * The implementation follows the paper, with a few exceptions:
+ *
+ * - Initialization of the generator and PRNG is combined in fortuna_init.
+ * - The check if the generator is seeded is moved from fortuna_generate_blocks
+ *   to fortuna_pseudo_random_data for optimization reasons.
+ * - In fortuna_random_data, the check on state->reseeds == 0 is not performed.
+ *   It would never provide a seed whenever the PRNG is initialized with a seed
+ *   file, when entropy sources are not available (yet). It would still fail
+ *   whenever fortuna_pseudo_random_data checks if the generator is seeded! See
+ *   https://crypto.stackexchange.com/q/56390 for more information.
+ */
+
+#ifndef FORTUNA_H
+#define FORTUNA_H
+
+#include "xtimer.h"
+#include "mutex.h"
+
+#include "crypto/aes.h"
+#include "hashes/sha256.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @bief Number of pools to use, which may not exceed 32. More pools means more
+ *       memory usage, but makes it harder for an attacker to influence the
+ *       state. The recommended value is 32, as discussed in section 9.5.2.
+ */
+#ifndef FORTUNA_POOLS
+#define FORTUNA_POOLS               (32U)
+#endif
+
+/**
+ * @brief Minimum number of bytes that should be in a pool. Higher values will
+ *        delay reseeds even if good random entropy is collected. In section
+ *        9.5.5, a suitable value of 64 bytes is suggested.
+ */
+#ifndef FORTUNA_MIN_POOL_SIZE
+#define FORTUNA_MIN_POOL_SIZE       (64U)
+#endif
+
+
+/**
+ * @brief Definition of the seed file size used to initialize the PRNG. The
+ *        default value of 64 bytes is suggested by section 9.6.1.
+ */
+#ifndef FORTUNA_SEED_SIZE
+#define FORTUNA_SEED_SIZE           (64U)
+#endif
+
+/**
+ * @brief Reseed interval in us. After this interval, the PRNG must be
+ *        reseeded. Per section 9.5.5, the recommended value is 100ms. Set to
+ *        zero to disable this security feature.
+ */
+#ifndef FORTUNA_RESEED_INTERVAL
+#define FORTUNA_RESEED_INTERVAL     (0)
+#endif
+
+/**
+ * @brief Reseed limit in bytes. After this number of bytes, the PRNG must be
+ *        reseeded. Per section 9.4.4, the recommended value is 2^20 bytes
+ *        (=1 MB). Set to zero to disable this security feature.
+ */
+#ifndef FORTUNA_RESEED_LIMIT
+#define FORTUNA_RESEED_LIMIT        (1U << 20)
+#endif
+
+/**
+ * @brief Fortuna requires a block cipher with a 256-bit key to minimize
+ *        permutation attacks.
+ *
+ * @note  RIOT-OS does not (yet) support AES-256. Therefore, at the expense of
+ *        security, AES-128 is used instead. An detailed explanation on the
+ *        effects can be found at https://crypto.stackexchange.com/a/5736.
+ */
+#ifndef FORTUNA_AES_KEY_SIZE
+#define FORTUNA_AES_KEY_SIZE        (16U)
+#endif
+
+/**
+ * @brief Use a mutex to lock concurrent access when running sensitive
+ *        operations in multi-threaded applications.
+ */
+#ifndef FORTUNA_LOCK
+#define FORTUNA_LOCK                (1)
+#endif
+
+/**
+ * @brief When set, perform additional instructions to clear temporary
+ *        variables to prevent leaking sensitive information.
+ */
+#ifndef FORTUNA_CLEANUP
+#define FORTUNA_CLEANUP             (1)
+#endif
+
+/**
+ * @brief Generator counter and key.
+ */
+typedef struct {
+    uint8_t key[32];
+    union {
+        struct {
+            uint64_t l;
+            uint64_t h;
+        } split;
+        uint8_t bytes[16];
+    } counter;
+} fortuna_generator_t;
+
+/**
+ * @brief Pool for storing entropy.
+ */
+typedef struct {
+    sha256_context_t ctx;
+    uint32_t len;
+} fortuna_pool_t;
+
+/**
+ * @brief The Fortuna state.
+ */
+typedef struct {
+    fortuna_generator_t gen;
+    fortuna_pool_t pools[FORTUNA_POOLS];
+    uint32_t reseeds;
+#if FORTUNA_RESEED_INTERVAL > 0
+    uint64_t last_reseed;
+#endif
+#if FORTUNA_LOCK
+    mutex_t lock;
+#endif
+} fortuna_state_t;
+
+/**
+ * @brief Type definition of a Fortuna seed file.
+ */
+typedef uint32_t fortuna_seed_t[FORTUNA_SEED_SIZE];
+
+/**
+ * @brief   Initialize the Fortuna PRNG state.
+ *
+ * It is possible to use this method to clear an existing state.
+ *
+ * @param[inout] state      PRNG state
+ *
+ * @return                  zero on succesful initialization
+ * @return                  non-zero on error
+ */
+int fortuna_init(fortuna_state_t *state);
+
+/**
+ * @brief   Read random bytes from the PRNG. The number of bytes may not exceed
+ *          FORTUNA_RESEED_LIMIT bytes.
+ *
+ * @param[inout] state      PRNG state
+ * @param[out] out          pointer to buffer
+ * @param[in] bytes         number of bytes to write in buffer
+ *
+ * @return                  zero on success
+ * @return                  -1 on reading more that FORTUNA_RESEED_LIMIT bytes
+ * @return                  -2 on reading from unseeded PRNG
+ * @return                  -3 on other error
+ */
+int fortuna_random_data(fortuna_state_t *state, uint8_t *out, size_t bytes);
+
+/**
+ * @brief   Add a entropy of a random event to one PRNG pool. The pool must
+ *          exist and the source length must be 1-32 bytes.
+ *
+ * @param[inout] state      PRNG state
+ * @param[in] data          pointer to entropy source
+ * @param[in] length        length of entropy source
+ * @param[in] source        source identifier (each source has its own ID)
+ * @param[in] pool          pool number to add entropy to
+ *
+ * @return                  zero on success
+ * @return                  -1 on zero bytes or more than 32 bytes
+ * @return                  -2 on invalid pool number
+ */
+int fortuna_add_random_event(fortuna_state_t *state, const uint8_t *data,
+                             uint8_t length, uint8_t source, uint8_t pool);
+
+/**
+ * @brief   Generate 64 bytes from the PRNG and write that to an output
+ *          buffer for use on next startup.
+ *
+ * This method must be invoked before shutting down the PRNG (e.g. on system
+ * shutdown).
+ *
+ * @param[inout] state      PRNG state
+ * @param[out] data         pointer to output buffer for the seed
+ *
+ * @return                  zero on success
+ */
+int fortuna_write_seed(fortuna_state_t *state, fortuna_seed_t *out);
+
+/**
+ * @brief   Reseed the PRNG using a seed. By design, this value will be
+ *          overwritten after use.
+ *
+ * This method should be invoked once on PRNG startup, in case a seed is
+ * available.
+ *
+ * @param[inout] state      PRNG state
+ * @param[inout] data       pointer to input and output buffer for the seed
+ *
+ * @return                  zero on success
+ */
+int fortuna_update_seed(fortuna_state_t *state, fortuna_seed_t *inout);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FORTUNA_H */
+/** @} */