diff --git a/.gitmodules b/.gitmodules
index a736023248aa81de2230ad5d23950646a9d2850f..a21dc049a14f22cdefc6a9cd09fc38c3a542404c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,9 @@
 [submodule "lib/micropython/micropython-lib"]
 	path = lib/micropython/micropython-lib
 	url = https://github.com/micropython/micropython-lib.git
+[submodule "lib/crypto/tiny-AES-c"]
+	path = lib/crypto/tiny-AES-c
+	url = https://github.com/kokke/tiny-AES-c
+[submodule "lib/crypto/SHA256"]
+	path = lib/crypto/SHA256
+	url = https://github.com/ilvn/SHA256
diff --git a/bootstrap.sh b/bootstrap.sh
index e9de7e9748ad859d65363d6b94aa421662503480..f8fb29bfb7dd948006697347a19ac061986af66f 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -4,7 +4,8 @@ set -xe
 cd "$(dirname "$0")"
 test -d build/ && rm -r build/
 
-git submodule update --init ./lib/micropython
+# Get external libs (MicroPython, tiny-AES-c, SHA256)
+git submodule update --init ./lib
 meson --cross-file card10-cross.ini build/ "$@"
 
 set +x
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 3e1df57875d60c776b0acb4afbdf614ce81728d7..a90ad4b70b91caf486fdcee741ced98943c0958d 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,26 @@ 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.
+ *
+ * .. warning::
+ *
+ *    The exact behaviour of the TRNG is not well understood. Its
+ *    distribution and other parameters are unknown. Only use this
+ *    function if you really want the unmodified values from the
+ *    hardware TRNG to experiment with it.
+ *
  * :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 +1951,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/meson.build b/epicardium/meson.build
index 0d10151a0bc31e64daeeca645d8e2ff5bd79fcd1..1966750b038090dfd7a5ca7b35405256efbc9e18 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -91,7 +91,7 @@ elf = executable(
   l0der_sources,
   ble_sources,
   version_hdr,
-  dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble, bhy1],
+  dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble, bhy1, libcrypto],
   link_with: [api_dispatcher_lib, freertos],
   link_whole: [max32665_startup_core0_lib, board_card10_lib, newlib_heap_lib],
   include_directories: [freertos_includes],
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/meson.build b/epicardium/modules/meson.build
index 548d8563ea1e7ac2943843931e1733af5fc07dd2..d77cfc2a2b7f491ad5b179d78ff4fc71fba7951c 100644
--- a/epicardium/modules/meson.build
+++ b/epicardium/modules/meson.build
@@ -24,7 +24,7 @@ module_sources = files(
   'serial.c',
   'sleep.c',
   'stream.c',
-  'trng.c',
+  'rng.c',
   'usb.c',
   'vibra.c',
   'watchdog.c',
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
new file mode 100644
index 0000000000000000000000000000000000000000..3be1d66be3dbddd9fdfde933865e492509874b4c
--- /dev/null
+++ b/epicardium/modules/rng.c
@@ -0,0 +1,97 @@
+#include "epicardium.h"
+
+#include "modules.h"
+
+#include "MAX77650-Arduino-Library.h"
+#include "tiny-AES-c/aes.h"
+#include "SHA256/mark2/sha256.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 aes_ctx;
+
+int epic_trng_read(uint8_t *dest, size_t size)
+{
+	if (dest == NULL)
+		return -EFAULT;
+
+	TRNG_Init(NULL);
+	TRNG_Read(MXC_TRNG, dest, 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(
+			&aes_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(&aes_ctx, out, sizeof(out));
+		memcpy(dest, out, size);
+	}
+
+	return 0;
+}
+
+void rng_init(void)
+{
+	uint8_t key[AES_BLOCKLEN];
+	uint8_t iv[AES_BLOCKLEN];
+	uint8_t hash[32];
+	sha256_context ctx;
+	int i;
+
+	sha256_init(&ctx);
+
+	/* Seed from TRNG.
+	 * Takes about 10 ms. */
+	for (i = 0; i < 256; i++) {
+		uint8_t entropy[AES_BLOCKLEN];
+		epic_trng_read(entropy, AES_BLOCKLEN);
+		sha256_hash(&ctx, entropy, AES_BLOCKLEN);
+	}
+
+	// Seed from RTC
+	uint32_t sec, subsec;
+	while (RTC_GetTime(&sec, &subsec) == E_BUSY) {
+		mxc_delay(4000);
+	}
+	sha256_hash(&ctx, &sec, sizeof(sec));
+	sha256_hash(&ctx, &subsec, sizeof(subsec));
+
+	// Seed from SysTick
+	uint32_t systick = SysTick->VAL;
+	sha256_hash(&ctx, &systick, sizeof(systick));
+
+	/* Seed from ADC.
+	 * Takes about 50 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);
+		sha256_hash(&ctx, &adc_data, sizeof(adc_data));
+	}
+	MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
+
+	sha256_done(&ctx, hash);
+	memcpy(key, hash, AES_BLOCKLEN);
+	memcpy(iv, hash + AES_BLOCKLEN, AES_BLOCKLEN);
+	AES_init_ctx_iv(&aes_ctx, key, iv);
+}
diff --git a/epicardium/modules/trng.c b/epicardium/modules/trng.c
deleted file mode 100644
index 91e001dcdbc7409c42c403a3d884c37091ffff9e..0000000000000000000000000000000000000000
--- a/epicardium/modules/trng.c
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "epicardium.h"
-#include "trng.h"
-
-int epic_trng_read(uint8_t *dest, size_t size)
-{
-	if (dest == NULL)
-		return -EFAULT;
-
-	TRNG_Init(NULL);
-	TRNG_Read(MXC_TRNG, dest, size);
-
-	return 0;
-}
diff --git a/lib/crypto/SHA256 b/lib/crypto/SHA256
new file mode 160000
index 0000000000000000000000000000000000000000..1c9e3886f69f9ca83c1af49968d8e4389035ff44
--- /dev/null
+++ b/lib/crypto/SHA256
@@ -0,0 +1 @@
+Subproject commit 1c9e3886f69f9ca83c1af49968d8e4389035ff44
diff --git a/lib/crypto/meson.build b/lib/crypto/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..d5eea1f34f6d95423306a4931ff01fbf709b331a
--- /dev/null
+++ b/lib/crypto/meson.build
@@ -0,0 +1,20 @@
+includes = include_directories(
+  './',
+)
+
+sources = files(
+  './tiny-AES-c/aes.c',
+  './SHA256/mark2/sha256.c',
+)
+
+lib = static_library(
+  'crypto',
+  sources,
+  include_directories: includes,
+  c_args: '-w',
+)
+
+libcrypto = declare_dependency(
+  include_directories: includes,
+  link_with: lib,
+)
diff --git a/lib/crypto/tiny-AES-c b/lib/crypto/tiny-AES-c
new file mode 160000
index 0000000000000000000000000000000000000000..3f69a5899e58e2e398e8c32ce7b3a954dd593ed4
--- /dev/null
+++ b/lib/crypto/tiny-AES-c
@@ -0,0 +1 @@
+Subproject commit 3f69a5899e58e2e398e8c32ce7b3a954dd593ed4
diff --git a/lib/meson.build b/lib/meson.build
index e43c62dbd5ab9b144d6189318973f4db212cb9eb..a51f0c69fca1d1dbe75ad842ef8ce71491e4a81d 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -10,6 +10,7 @@ subdir('./gfx/')
 subdir('./FreeRTOS/')
 subdir('./FreeRTOS-Plus/')
 subdir('./micropython/')
+subdir('./crypto/')
 
 subdir('./card10/')
 subdir('./mx25lba/')
diff --git a/pycardium/modules/os.c b/pycardium/modules/os.c
index d8e6a105f0061e75afd0fae7bce2a8aa8d9e5b79..ea8b4d0f9a814bdc37853fd873697d3fd2ae1d83 100644
--- a/pycardium/modules/os.c
+++ b/pycardium/modules/os.c
@@ -188,7 +188,7 @@ static mp_obj_t mp_os_urandom(mp_obj_t size_in)
 	vstr_t vstr;
 
 	vstr_init_len(&vstr, size);
-	epic_trng_read((uint8_t *)vstr.buf, size);
+	epic_csprng_read((uint8_t *)vstr.buf, size);
 
 	return mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
 }