diff --git a/extra/exports b/extra/exports
index b4330ea90d9a734bc6ff8b9d91335ac31ddce21c..22439ee69f1bbaea585897b8f0dc526fe9e9e4b7 100644
--- a/extra/exports
+++ b/extra/exports
@@ -127,6 +127,7 @@ box_tuple_format_default
 box_tuple_format_new
 box_tuple_format_ref
 box_tuple_format_unref
+box_tuple_hash
 box_tuple_iterator
 box_tuple_iterator_free
 box_tuple_new
diff --git a/src/box/key_def.c b/src/box/key_def.c
index f2e6c65f2e9a1bd9323c84e8e7ac27229c7ef267..52771e6b78e6a9f1e41f11a15c1cdf2f05994434 100644
--- a/src/box/key_def.c
+++ b/src/box/key_def.c
@@ -632,6 +632,12 @@ box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b,
 
 }
 
+uint32_t
+box_tuple_hash(box_tuple_t *tuple, box_key_def_t *key_def)
+{
+	return tuple_hash(tuple, key_def);
+}
+
 box_key_def_t *
 box_key_def_merge(const box_key_def_t *first, const box_key_def_t *second)
 {
diff --git a/src/box/key_def.h b/src/box/key_def.h
index 03f71796626ef46d80856c4de4d1134e0947112e..42237374aec7f4816caa2d05d96b9f0cfa0cd22a 100644
--- a/src/box/key_def.h
+++ b/src/box/key_def.h
@@ -539,6 +539,16 @@ API_EXPORT int
 box_tuple_compare_with_key(box_tuple_t *tuple_a, const char *key_b,
 			   box_key_def_t *key_def);
 
+/**
+ * Calculate a tuple hash value with a given key definition.
+ *
+ * @param tuple tuple
+ * @param key_def key definition
+ * @return - hash value
+ */
+API_EXPORT uint32_t
+box_tuple_hash(box_tuple_t *tuple, box_key_def_t *key_def);
+
 /**
  * Allocate a new key_def with a set union of key parts from
  * first and second key defs.
diff --git a/src/box/lua/key_def.c b/src/box/lua/key_def.c
index 14cb770fc924994065996605a132275872f82482..a95431de2fb755d45d714756b8aa2eff8a4b4358 100644
--- a/src/box/lua/key_def.c
+++ b/src/box/lua/key_def.c
@@ -398,6 +398,33 @@ lbox_key_def_compare_with_key(struct lua_State *L)
 	return 1;
 }
 
+/**
+ * Calculate a tuple hash with a given key definition.
+ * At the moment 32-bit murmur3 hash is used but it may
+ * change in future.
+ * Push hash integer to a LUA stack on success.
+ * Raise error otherwise.
+ */
+static int
+lbox_key_def_hash(struct lua_State *L)
+{
+	struct key_def *key_def = luaT_check_key_def(L, 1);
+	if (lua_gettop(L) != 2 || key_def == NULL)
+		return luaL_error(L, "Usage: key_def:hash(tuple)");
+
+	struct tuple *tuple = luaT_key_def_check_tuple(L, key_def, 2);
+	if (tuple == NULL)
+		return luaT_error(L);
+
+	struct region *region = &fiber()->gc;
+	size_t region_svp = region_used(region);
+	uint32_t hash = box_tuple_hash(tuple, key_def);
+	region_truncate(region, region_svp);
+	tuple_unref(tuple);
+	lua_pushinteger(L, hash);
+	return 1;
+}
+
 /**
  * Construct and export to LUA a new key definition with a set
  * union of key parts from first and second key defs. Parts of
@@ -519,6 +546,7 @@ luaopen_key_def(struct lua_State *L)
 		{"extract_key", lbox_key_def_extract_key},
 		{"compare", lbox_key_def_compare},
 		{"compare_with_key", lbox_key_def_compare_with_key},
+		{"hash", lbox_key_def_hash},
 		{"merge", lbox_key_def_merge},
 		{"totable", lbox_key_def_to_table},
 		{NULL, NULL}
diff --git a/src/box/lua/key_def.lua b/src/box/lua/key_def.lua
index 97905eebfba8eefdd4a34662cb98bfc4cf443b39..ce73b41c90624e34be5158471c159fca4f9e46fb 100644
--- a/src/box/lua/key_def.lua
+++ b/src/box/lua/key_def.lua
@@ -6,6 +6,7 @@ local methods = {
     ['extract_key'] = key_def.extract_key,
     ['compare'] = key_def.compare,
     ['compare_with_key'] = key_def.compare_with_key,
+    ['hash'] = key_def.hash,
     ['merge'] = key_def.merge,
     ['totable'] = key_def.totable,
     ['__serialize'] = key_def.totable,
diff --git a/test/app-tap/module_api.c b/test/app-tap/module_api.c
index 2068f8e0dca76ba84a2f6937f9d382506025160c..8c8a89d0adff22da13c7d63e7e17022103b0b34e 100644
--- a/test/app-tap/module_api.c
+++ b/test/app-tap/module_api.c
@@ -1973,6 +1973,72 @@ test_key_def_validate_key(struct lua_State *L)
 	return 1;
 }
 
+static int
+test_key_def_hash(struct lua_State *L)
+{
+	size_t region_svp = box_region_used();
+
+	/* Prepare a new tuple [1, 2, 3] */
+	box_tuple_t *tuple_1 = new_runtime_tuple("\x93\x01\x02\x03", 4);
+	/* Create a key definition from the first two columns. */
+	box_key_part_def_t parts_1[2];
+	box_key_part_def_create(&parts_1[0]);
+	box_key_part_def_create(&parts_1[1]);
+	parts_1[0].fieldno = 0;
+	parts_1[0].field_type = "integer";
+	parts_1[1].fieldno = 1;
+	parts_1[1].field_type = "integer";
+	box_key_def_t *key_def_1 = box_key_def_new_v2(parts_1, 2);
+	fail_unless(key_def_1 != NULL);
+	/* Calculate tuple's hash */
+	uint32_t hash_1 = box_tuple_hash(tuple_1, key_def_1);
+	fail_unless(hash_1 == 605624609);
+	/* Clean up */
+	box_key_def_delete(key_def_1);
+	box_tuple_unref(tuple_1);
+
+	/* Prepare a new tuple [1] */
+	box_tuple_t *tuple_2 = new_runtime_tuple("\x91\x01", 2);
+	/* Create a key definition from its column. */
+	box_key_part_def_t parts_2[1];
+	box_key_part_def_create(&parts_2[0]);
+	parts_2[0].fieldno = 0;
+	parts_2[0].field_type = "integer";
+	box_key_def_t *key_def_2 = box_key_def_new_v2(parts_2, 1);
+	fail_unless(key_def_2 != NULL);
+	/* Calculate tuple's hash */
+	uint32_t hash_2 = box_tuple_hash(tuple_2, key_def_2);
+	fail_unless(hash_2 == 1457374933);
+	/* Clean up */
+	box_key_def_delete(key_def_2);
+	box_tuple_unref(tuple_2);
+
+	/* Prepare a new tuple [1] */
+	box_tuple_t *tuple_3 = new_runtime_tuple("\x91\x01", 2);
+	/* Create a key defenition where the second column is nullable */
+	box_key_part_def_t parts_3[2];
+	box_key_part_def_create(&parts_3[0]);
+	box_key_part_def_create(&parts_3[1]);
+	parts_3[0].fieldno = 0;
+	parts_3[0].field_type = "integer";
+	parts_3[1].fieldno = 1;
+	parts_3[1].field_type = "integer";
+	parts_3[1].flags = BOX_KEY_PART_DEF_IS_NULLABLE;
+	box_key_def_t *key_def_3 = box_key_def_new_v2(parts_3, 2);
+	fail_unless(key_def_3 != NULL);
+	/* Calculate tuple's hash */
+	uint32_t hash_3 = box_tuple_hash(tuple_3, key_def_3);
+	fail_unless(hash_3 == 766361540);
+	/* Clean up */
+	box_key_def_delete(key_def_3);
+	box_tuple_unref(tuple_3);
+
+	box_region_truncate(region_svp);
+
+	lua_pushboolean(L, 1);
+	return 1;
+}
+
 static int
 test_key_def_dup(lua_State *L)
 {
@@ -3197,6 +3263,7 @@ luaopen_module_api(lua_State *L)
 		{"test_key_def_merge", test_key_def_merge},
 		{"test_key_def_extract_key", test_key_def_extract_key},
 		{"test_key_def_validate_key", test_key_def_validate_key},
+		{"test_key_def_hash", test_key_def_hash},
 		{"test_box_ibuf", test_box_ibuf},
 		{"tuple_validate_def", test_tuple_validate_default},
 		{"tuple_validate_fmt", test_tuple_validate_formatted},
diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua
index 76303a24ecbc03ad3db8d4d8adf25fcdf9d18dbb..5642b9a59b2d0b656ca235326db4a34155f98487 100755
--- a/test/app-tap/module_api.test.lua
+++ b/test/app-tap/module_api.test.lua
@@ -664,7 +664,7 @@ local function test_box_iproto_override(test, module)
 end
 
 require('tap').test("module_api", function(test)
-    test:plan(49)
+    test:plan(50)
     local status, module = pcall(require, 'module_api')
     test:is(status, true, "module")
     test:ok(status, "module is loaded")
diff --git a/test/box-luatest/key_def_test.lua b/test/box-luatest/key_def_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..80dbd9e73c7c1ffe1751592573bd5d4b6c2a60a5
--- /dev/null
+++ b/test/box-luatest/key_def_test.lua
@@ -0,0 +1,25 @@
+local t = require('luatest')
+
+local g = t.group('tuple_hash')
+
+g.test_key_def_tuple_hash = function()
+    local key_def = require('key_def')
+    local tuple = box.tuple.new({1, 2, 3})
+
+    local def = key_def.new({
+        {fieldno = 1, type = 'integer'},
+        {fieldno = 2, type = 'integer'}
+    })
+    t.assert_equals(def:hash(tuple), 605624609)
+
+    def = key_def.new({{fieldno = 1, type = 'integer'}})
+    tuple = box.tuple.new({1})
+    t.assert_equals(def:hash(tuple), 1457374933)
+
+    def = key_def.new({
+        {fieldno = 1, type = 'integer'},
+        {fieldno = 2, type = 'integer', is_nullable = true}
+    })
+    tuple = box.tuple.new({1})
+    t.assert_equals(def:hash(tuple), 766361540)
+end