diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 2ad19134960d6ec35e110266ea0abab3754b255a..6139ae688ba4eec4b1a75af57ad1fd54f59a5dfe 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -119,6 +119,7 @@ typedef _Bool bool;
 #define API_GPIO_READ_PIN          0xA3
 
 #define API_TRNG_READ              0xB0
+#define API_CSPRNG_READ            0XB1
 
 #define API_PERSONAL_STATE_SET     0xc0
 #define API_PERSONAL_STATE_GET     0xc1
@@ -1921,13 +1922,19 @@ API(API_RTC_SCHEDULE_ALARM, int epic_rtc_schedule_alarm(uint32_t timestamp));
 API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm);
 
 /**
- * TRNG
+ * RNG
  * ====
  */
 
 /**
  * Read random bytes from the TRNG.
  *
+ * Be aware that this function returns raw unprocessed bytes from
+ * the TRNG. They might be biased or have other kinds of imperfections.
+ *
+ * Use :c:func:`epic_csprng_read` for cryptographically safe random
+ * numbers instead.
+ *
  * :param uint8_t * dest: Destination buffer
  * :param size: Number of bytes to read.
  * :return: `0` on success or a negative value if an error occured. Possible
@@ -1937,6 +1944,20 @@ API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm);
  */
 API(API_TRNG_READ, int epic_trng_read(uint8_t *dest, size_t size));
 
+/**
+ * Read random bytes from the CSPRNG.
+ *
+ * The random bytes returned are safe to be used for cryptography.
+ *
+ * :param uint8_t * dest: Destination buffer
+ * :param size: Number of bytes to read.
+ * :return: `0` on success or a negative value if an error occured. Possible
+ *    errors:
+ *
+ *    - ``-EFAULT``: Invalid destination address.
+ */
+API(API_CSPRNG_READ, int epic_csprng_read(uint8_t *dest, size_t size));
+
 /**
  * MAX30001
  * ========
diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c
index a9241525bdd68619baaa728c7b1e37c964e3c054..055ae486b391ca0b46b6737689141e1ea175014b 100644
--- a/epicardium/modules/hardware.c
+++ b/epicardium/modules/hardware.c
@@ -18,7 +18,6 @@
 #include "i2c.h"
 #include "rtc.h"
 #include "spi.h"
-#include "trng.h"
 #include "wdt.h"
 
 /*
@@ -83,6 +82,11 @@ int hardware_early_init(void)
 	       E_BUSY)
 		;
 
+	/*
+	 * RNG
+	 */
+	rng_init();
+
 	/* If we don't have a valid time yet, set it to 2019-01-01 */
 	if (RTC_GetSecond() < 1546300800L) {
 		epic_rtc_set_milliseconds(1546300800UL * 1000);
diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h
index c26a1ce6cf7a2480eb982d8b80ab1215d4c06278..14184bd9ca96b367ebcf57a7d8b7739a407df817 100644
--- a/epicardium/modules/modules.h
+++ b/epicardium/modules/modules.h
@@ -136,4 +136,8 @@ extern gpio_cfg_t gpio_configs[];
 
 /* ---------- Sleep -------------------------------------------------------- */
 void sleep_deepsleep(void);
+
+
+void rng_init(void);
+
 #endif /* MODULES_H */
diff --git a/epicardium/modules/rng.c b/epicardium/modules/rng.c
index 91e001dcdbc7409c42c403a3d884c37091ffff9e..0357f10697889798f9bd9c731611f46a7f1bcc7c 100644
--- a/epicardium/modules/rng.c
+++ b/epicardium/modules/rng.c
@@ -1,6 +1,20 @@
 #include "epicardium.h"
+
+#include "modules.h"
+
+#include "MAX77650-Arduino-Library.h"
+#include "tiny-AES-c/aes.h"
+
+#include "mxc_sys.h"
+#include "adc.h"
+#include "mxc_delay.h"
+#include "rtc.h"
 #include "trng.h"
 
+#include <string.h>
+
+static struct AES_ctx ctx;
+
 int epic_trng_read(uint8_t *dest, size_t size)
 {
 	if (dest == NULL)
@@ -11,3 +25,81 @@ int epic_trng_read(uint8_t *dest, size_t size)
 
 	return 0;
 }
+
+int epic_csprng_read(uint8_t *dest, size_t size)
+{
+	if (size >= AES_BLOCKLEN) {
+		int block_count = size / AES_BLOCKLEN;
+		AES_CTR_xcrypt_buffer(&ctx, dest, block_count * AES_BLOCKLEN);
+		size -= block_count * AES_BLOCKLEN;
+		dest += block_count * AES_BLOCKLEN;
+	}
+
+	if (size > 0) {
+		uint8_t out[AES_BLOCKLEN];
+		AES_CTR_xcrypt_buffer(&ctx, out, sizeof(out));
+		memcpy(dest, out, size);
+	}
+
+	return 0;
+}
+
+static void xor
+	(uint8_t * a, uint8_t *b, size_t size) {
+		while (size--) {
+			*a = *a ^ *b;
+			a++;
+			b++;
+		}
+	}
+
+	static void seed(uint8_t *entropy, size_t size)
+{
+	uint8_t key_new[AES_BLOCKLEN];
+	uint8_t iv[AES_BLOCKLEN] = { 0 };
+	epic_csprng_read(key_new, AES_BLOCKLEN);
+	xor(key_new, entropy, size);
+	AES_init_ctx_iv(&ctx, key_new, iv);
+}
+
+void rng_init(void)
+{
+	uint8_t key[AES_BLOCKLEN] = { 0 };
+	uint8_t iv[AES_BLOCKLEN]  = { 0 };
+	int i;
+
+	AES_init_ctx_iv(&ctx, key, iv);
+
+	/* Seed from TRNG.
+	 * Takes about 30 ms. */
+	for (i = 0; i < 256; i++) {
+		uint8_t entropy[AES_BLOCKLEN];
+		epic_trng_read(entropy, AES_BLOCKLEN);
+		seed(entropy, AES_BLOCKLEN);
+	}
+
+	// Seed from RTC
+	uint32_t sec, subsec;
+	while (RTC_GetTime(&sec, &subsec) == E_BUSY) {
+		mxc_delay(4000);
+	}
+	seed((uint8_t *)&sec, sizeof(sec));
+	seed((uint8_t *)&subsec, sizeof(subsec));
+
+	// Seed from SysTick
+	uint32_t systick = SysTick->VAL;
+	seed((uint8_t *)&systick, sizeof(systick));
+
+	/* Seed from ADC.
+	 * Takes about 80 ms */
+	ADC_Init(0x9, NULL);
+	GPIO_Config(&gpio_cfg_adc0);
+	MAX77650_setMUX_SEL(PMIC_AMUX_BATT_U);
+	for (i = 0; i < 256; i++) {
+		uint16_t adc_data;
+		ADC_StartConvert(ADC_CH_0, 0, 0);
+		ADC_GetData(&adc_data);
+		seed((uint8_t *)&adc_data, sizeof(adc_data));
+	}
+	MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
+}