diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 63e4971e53b9a40be9ae8d13eb1f5655d78621f5..f82c3c2ac59cfcb66ce9b5c7ebe9ddfe649a6134 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -2765,6 +2765,15 @@ It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`.
     * This is only a rough approximation of a normal distribution with mean=(max-min)/2 and variance=1
     * Increasing num_trials improves accuracy of the approximation
 
+### `SecureRandom`
+Interface for the operating system's crypto-secure PRNG.
+
+It can be created via `SecureRandom()`.  The constructor returns nil if a secure random device cannot be
+be found on the system.
+
+#### Methods
+* `next_bytes([count])`: return next `count` (default 1, capped at 2048) many random bytes, as a string.
+
 ### `PerlinNoise`
 A perlin noise generator.
 It can be created via `PerlinNoise(seed, octaves, persistence, scale)`
diff --git a/src/porting.cpp b/src/porting.cpp
index ced41d4fb748083e0886440661620ece2498bf89..3e39fc8131ff672df2f6e3bd27a542d15de80bf1 100644
--- a/src/porting.cpp
+++ b/src/porting.cpp
@@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	#include <sys/types.h>
 	#include <sys/sysctl.h>
 #elif defined(_WIN32)
+	#include <windows.h>
+	#include <wincrypt.h>
 	#include <algorithm>
 #endif
 #if !defined(_WIN32)
@@ -701,5 +703,44 @@ v2u32 getDisplaySize()
 #	endif // __ANDROID__
 #endif // SERVER
 
-} //namespace porting
 
+////
+//// OS-specific Secure Random
+////
+
+#ifdef WIN32
+
+bool secure_rand_fill_buf(void *buf, size_t len)
+{
+	HCRYPTPROV wctx;
+
+	if (!CryptAcquireContext(&wctx, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
+		return false;
+
+	CryptGenRandom(wctx, len, (BYTE *)buf);
+	CryptReleaseContext(wctx, 0);
+	return true;
+}
+
+#else
+
+bool secure_rand_fill_buf(void *buf, size_t len)
+{
+	// N.B.  This function checks *only* for /dev/urandom, because on most
+	// common OSes it is non-blocking, whereas /dev/random is blocking, and it
+	// is exceptionally uncommon for there to be a situation where /dev/random
+	// exists but /dev/urandom does not.  This guesswork is necessary since
+	// random devices are not covered by any POSIX standard...
+	FILE *fp = fopen("/dev/urandom", "rb");
+	if (!fp)
+		return false;
+
+	bool success = fread(buf, len, 1, fp) == 1;
+
+	fclose(fp);
+	return success;
+}
+
+#endif
+
+} //namespace porting
diff --git a/src/porting.h b/src/porting.h
index a86d37fbb91a50dcc1fc1381959ad34a8640f7c1..1e89cd04482d5083a1fe694054fd3a0175fa6be8 100644
--- a/src/porting.h
+++ b/src/porting.h
@@ -343,6 +343,7 @@ void setXorgClassHint(const video::SExposedVideoData &video_data,
 // threads in the process inherit this exception handler
 void setWin32ExceptionHandler();
 
+bool secure_rand_fill_buf(void *buf, size_t len);
 } // namespace porting
 
 #ifdef __ANDROID__
diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp
index 6dcffa31ca6282dc4807f3c0f9ff5359f9e48765..04dc6048fbc8c5ea3713eace4e9a648f33b677c9 100644
--- a/src/script/lua_api/l_noise.cpp
+++ b/src/script/lua_api/l_noise.cpp
@@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common/c_converter.h"
 #include "common/c_content.h"
 #include "log.h"
+#include "porting.h"
+#include "util/numeric.h"
 
 ///////////////////////////////////////
 /*
@@ -600,3 +602,116 @@ const luaL_reg LuaPcgRandom::methods[] = {
 	luamethod(LuaPcgRandom, rand_normal_dist),
 	{0,0}
 };
+
+///////////////////////////////////////
+/*
+	LuaSecureRandom
+*/
+
+bool LuaSecureRandom::fillRandBuf()
+{
+	return porting::secure_rand_fill_buf(m_rand_buf, RAND_BUF_SIZE);
+}
+
+int LuaSecureRandom::l_next_bytes(lua_State *L)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	LuaSecureRandom *o = checkobject(L, 1);
+	u32 count = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : 1;
+
+	// Limit count
+	count = MYMIN(RAND_BUF_SIZE, count);
+
+	// Find out whether we can pass directly from our array, or have to do some gluing
+	size_t count_remaining = RAND_BUF_SIZE - o->m_rand_idx;
+	if (count_remaining >= count) {
+		lua_pushlstring(L, o->m_rand_buf + o->m_rand_idx, count);
+		o->m_rand_idx += count;
+	} else {
+		char output_buf[RAND_BUF_SIZE];
+
+		// Copy over with what we have left from our current buffer
+		memcpy(output_buf, o->m_rand_buf + o->m_rand_idx, count_remaining);
+
+		// Refill buffer and copy over the remainder of what was requested
+		o->fillRandBuf();
+		memcpy(output_buf + count_remaining, o->m_rand_buf, count - count_remaining);
+
+		// Update index
+		o->m_rand_idx = count - count_remaining;
+
+		lua_pushlstring(L, output_buf, count);
+	}
+
+	return 1;
+}
+
+
+int LuaSecureRandom::create_object(lua_State *L)
+{
+	LuaSecureRandom *o = new LuaSecureRandom();
+
+	// Fail and return nil if we can't securely fill the buffer
+	if (!o->fillRandBuf()) {
+		delete o;
+		return 0;
+	}
+
+	*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
+	luaL_getmetatable(L, className);
+	lua_setmetatable(L, -2);
+	return 1;
+}
+
+
+int LuaSecureRandom::gc_object(lua_State *L)
+{
+	LuaSecureRandom *o = *(LuaSecureRandom **)(lua_touserdata(L, 1));
+	delete o;
+	return 0;
+}
+
+
+LuaSecureRandom *LuaSecureRandom::checkobject(lua_State *L, int narg)
+{
+	luaL_checktype(L, narg, LUA_TUSERDATA);
+	void *ud = luaL_checkudata(L, narg, className);
+	if (!ud)
+		luaL_typerror(L, narg, className);
+	return *(LuaSecureRandom **)ud;
+}
+
+
+void LuaSecureRandom::Register(lua_State *L)
+{
+	lua_newtable(L);
+	int methodtable = lua_gettop(L);
+	luaL_newmetatable(L, className);
+	int metatable = lua_gettop(L);
+
+	lua_pushliteral(L, "__metatable");
+	lua_pushvalue(L, methodtable);
+	lua_settable(L, metatable);
+
+	lua_pushliteral(L, "__index");
+	lua_pushvalue(L, methodtable);
+	lua_settable(L, metatable);
+
+	lua_pushliteral(L, "__gc");
+	lua_pushcfunction(L, gc_object);
+	lua_settable(L, metatable);
+
+	lua_pop(L, 1);
+
+	luaL_openlib(L, 0, methods, 0);
+	lua_pop(L, 1);
+
+	lua_register(L, className, create_object);
+}
+
+const char LuaSecureRandom::className[] = "SecureRandom";
+const luaL_reg LuaSecureRandom::methods[] = {
+	luamethod(LuaSecureRandom, next_bytes),
+	{0,0}
+};
diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h
index e958c5a2388b3005e69e992677e0c5af7fa70a92..492eb7550651a8ebc236bacb6df51b8b6763d71a 100644
--- a/src/script/lua_api/l_noise.h
+++ b/src/script/lua_api/l_noise.h
@@ -160,4 +160,37 @@ class LuaPcgRandom : public ModApiBase {
 	static void Register(lua_State *L);
 };
 
+
+/*
+	LuaSecureRandom
+*/
+class LuaSecureRandom : public ModApiBase {
+private:
+	static const size_t RAND_BUF_SIZE = 2048;
+	static const char className[];
+	static const luaL_reg methods[];
+
+	u32 m_rand_idx;
+	char m_rand_buf[RAND_BUF_SIZE];
+
+	// Exported functions
+
+	// garbage collector
+	static int gc_object(lua_State *L);
+
+	// next_bytes(self, count) -> get count many bytes
+	static int l_next_bytes(lua_State *L);
+
+public:
+	bool fillRandBuf();
+
+	// LuaSecureRandom()
+	// Creates an LuaSecureRandom and leaves it on top of stack
+	static int create_object(lua_State *L);
+
+	static LuaSecureRandom *checkobject(lua_State *L, int narg);
+
+	static void Register(lua_State *L);
+};
+
 #endif /* L_NOISE_H_ */
diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp
index 4f0350d418b3ba752fba3429a6bb7a4d2beec095..33bc5c2a776ad5e6eebb1588153159333c802a2d 100644
--- a/src/script/scripting_game.cpp
+++ b/src/script/scripting_game.cpp
@@ -98,6 +98,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top)
 	LuaPerlinNoiseMap::Register(L);
 	LuaPseudoRandom::Register(L);
 	LuaPcgRandom::Register(L);
+	LuaSecureRandom::Register(L);
 	LuaVoxelManip::Register(L);
 	NodeMetaRef::Register(L);
 	NodeTimerRef::Register(L);