diff --git a/daemon/worker.c b/daemon/worker.c
index 75c851519457ed738f6e439251d9be094ce4d8de..c98afd1627103a2e0202a8bfad0d711ec1502118 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -28,6 +28,7 @@
 #if defined(ENABLE_COOKIES)
 #include <arpa/inet.h> /* inet_ntop() */
 #include "lib/cookies/control.h"
+#include "lib/cookies/helper.h"
 #endif /* defined(ENABLE_COOKIES) */
 #include "lib/utils.h"
 #include "lib/layer.h"
@@ -465,6 +466,8 @@ static bool subreq_update_cookies(uv_udp_t *handle, struct sockaddr *srvr_addr,
 		return true;
 	}
 
+	struct sockaddr_storage *sockaddr_ptr = NULL; /* Not supported yet. */
+#if 0
 	/* Libuv does not offer a convenient way how to obtain a source IP
 	 * address from a UDP handle that has been initialised using
 	 * uv_udp_init(). The uv_udp_getsockname() fails because of the lazy
@@ -473,7 +476,6 @@ static bool subreq_update_cookies(uv_udp_t *handle, struct sockaddr *srvr_addr,
 	 * TODO -- A solution might be opening a separate socket and trying
 	 * to obtain the IP address from it.
 	 */
-
 	struct sockaddr_storage sockaddr = {0, };
 	struct sockaddr_storage *sockaddr_ptr = &sockaddr;
 	int sockaddr_len = sizeof(sockaddr);
@@ -482,6 +484,7 @@ static bool subreq_update_cookies(uv_udp_t *handle, struct sockaddr *srvr_addr,
 	if (ret != 0) {
 		sockaddr_ptr = NULL;
 	}
+#endif /* 0 */
 
 	kr_request_put_cookie(&kr_glob_cookie_ctx.clnt.current, cookie_cache,
 	                      (struct sockaddr*) sockaddr_ptr, srvr_addr, pkt);
@@ -511,6 +514,10 @@ static int qr_task_send(struct qr_task *task, uv_handle_t *handle, struct sockad
 	}
 	if (handle->type == UV_UDP) {
 #if defined(ENABLE_COOKIES)
+		/* The actual server IP address is needed before generating the
+		 * actual cookie. Also the resolver somehow mangles the query
+		 * packets before building the query i.e. the space needed for
+		 * the cookie cannot be allocated in the cookie layer. */
 		if (knot_wire_get_qr(pkt->wire) == 0) {
 			/* Update DNS cookies data in query. */
 			subreq_update_cookies((uv_udp_t *) handle, addr,
diff --git a/lib/cookies/alg_containers.c b/lib/cookies/alg_containers.c
index 13d002d1fb4eab5b27ca12380e9256400c05e3c3..1a5767b7a15f84dea5c2a8f0da0a55fd247f5fca 100644
--- a/lib/cookies/alg_containers.c
+++ b/lib/cookies/alg_containers.c
@@ -49,9 +49,7 @@ const struct kr_cc_alg_descr *kr_cc_alg(const struct kr_cc_alg_descr cc_algs[],
 }
 
 const struct kr_sc_alg_descr kr_sc_algs[] = {
-	{ "FNV-64-SIMPLE", &knot_sc_alg_fnv64_simple },
 	{ "FNV-64", &knot_sc_alg_fnv64 },
-	{ "HMAC-SHA256-64-SIMPLE", &knot_sc_alg_hmac_sha256_64_simple },
 	{ "HMAC-SHA256-64", &knot_sc_alg_hmac_sha256_64 },
 	{ NULL, NULL }
 };
@@ -64,7 +62,7 @@ const struct kr_sc_alg_descr *kr_sc_alg(const struct kr_sc_alg_descr sc_algs[],
 	}
 
 	const struct kr_sc_alg_descr *aux_ptr = sc_algs;
-	while (aux_ptr && aux_ptr->alg && aux_ptr->alg->gen_func) {
+	while (aux_ptr && aux_ptr->alg && aux_ptr->alg->hash_func) {
 		assert(aux_ptr->name);
 		if (strcmp(aux_ptr->name, name) == 0) {
 			return aux_ptr;
diff --git a/lib/cookies/alg_sha.c b/lib/cookies/alg_sha.c
new file mode 100644
index 0000000000000000000000000000000000000000..c58073b76da74ba302fe309d357a1a2024df9e21
--- /dev/null
+++ b/lib/cookies/alg_sha.c
@@ -0,0 +1,197 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h> /* htonl(), ... */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <libknot/errcode.h>
+#include <libknot/rrtype/opt-cookie.h>
+
+#include "lib/cookies/alg_sha.h"
+#include "lib/utils.h"
+
+/**
+ * Compute client cookie using HMAC_SHA256-64.
+ * @note At least one of the arguments must be non-null.
+ * @param input  Input parameters.
+ * @param cc_out Buffer for computed client cookie.
+ * @param cc_len Size of buffer/written data.
+ * @return KNOT_EOK on success, error code else.
+ */
+static int cc_gen_hmac_sha256_64(const struct knot_cc_input *input,
+                                 uint8_t *cc_out, uint16_t *cc_len)
+{
+	if (!input || !cc_out || !cc_len) {
+		return KNOT_EINVAL;
+	}
+
+	if ((!input->clnt_sockaddr && !input->srvr_sockaddr) ||
+	    !(input->secret_data && input->secret_len)) {
+		return KNOT_EINVAL;
+	}
+
+	const uint8_t *addr = NULL;
+	int addr_len = 0; /* Address length. */
+
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	unsigned int digest_len = SHA256_DIGEST_LENGTH;
+
+	/* text: (client IP | server IP)
+	 * key: client secret */
+
+	HMAC_CTX ctx;
+	HMAC_CTX_init(&ctx);
+
+	int ret = HMAC_Init_ex(&ctx, input->secret_data, input->secret_len,
+	                       EVP_sha256(), NULL);
+	if (ret != 1) {
+		ret = KNOT_EINVAL;
+		goto fail;
+	}
+
+	if (input->clnt_sockaddr) {
+		addr = (uint8_t *)kr_inaddr(input->clnt_sockaddr);
+		addr_len = kr_inaddr_len(input->clnt_sockaddr);
+		if (addr && addr_len > 0) {
+			ret = HMAC_Update(&ctx, addr, addr_len);
+			if (ret != 1) {
+				ret = KNOT_EINVAL;
+				goto fail;
+			}
+		}
+	}
+
+	if (input->srvr_sockaddr) {
+		addr = (uint8_t *)kr_inaddr(input->srvr_sockaddr);
+		addr_len = kr_inaddr_len(input->srvr_sockaddr);
+		if (addr && addr_len > 0) {
+			ret = HMAC_Update(&ctx, addr, addr_len);
+			if (ret != 1) {
+				ret = KNOT_EINVAL;
+				goto fail;
+			}
+		}
+	}
+
+	if (1 != HMAC_Final(&ctx, digest, &digest_len)) {
+		ret = KNOT_EINVAL;
+		goto fail;
+	}
+
+	assert(KNOT_OPT_COOKIE_CLNT <= SHA256_DIGEST_LENGTH);
+	if (*cc_len < KNOT_OPT_COOKIE_CLNT) {
+		return KNOT_ESPACE;
+	}
+
+	*cc_len = KNOT_OPT_COOKIE_CLNT;
+	memcpy(cc_out, digest, *cc_len);
+	ret = KNOT_EOK;
+
+fail:
+	HMAC_CTX_cleanup(&ctx);
+	return ret;
+}
+
+#define SRVR_HMAC_SHA256_64_HASH_SIZE 8
+
+/**
+ * @brief Compute server cookie using HMAC-SHA256-64).
+ * @note Server cookie = nonce | time | HMAC-SHA256-64( server secret, client cookie | nonce| time | client IP )
+ * @param input    data to compute cookie from
+ * @param hash_out hash cookie output buffer
+ * @param hash_len buffer size / written data size
+ * @return KNOT_EOK or error code.
+ */
+static int sc_gen_hmac_sha256_64(const struct knot_sc_input *input,
+                                 uint8_t *hash_out, uint16_t *hash_len)
+{
+	if (!input || !hash_out ||
+	    !hash_len || (*hash_len < SRVR_HMAC_SHA256_64_HASH_SIZE)) {
+		return KNOT_EINVAL;
+	}
+
+	if (!input->cc || !input->cc_len || !input->srvr_data ||
+	    !input->srvr_data->secret_data || !input->srvr_data->secret_len) {
+		return KNOT_EINVAL;
+	}
+
+	const uint8_t *addr = NULL;
+	size_t addr_len = 0; /* Address length. */
+
+	uint8_t digest[SHA256_DIGEST_LENGTH];
+	unsigned int digest_len = SHA256_DIGEST_LENGTH;
+
+	HMAC_CTX ctx;
+	HMAC_CTX_init(&ctx);
+
+	int ret = HMAC_Init_ex(&ctx, input->srvr_data->secret_data,
+	                       input->srvr_data->secret_len,
+	                       EVP_sha256(), NULL);
+	if (ret != 1) {
+		ret = KNOT_EINVAL;
+		goto fail;
+	}
+
+	ret = HMAC_Update(&ctx, input->cc, input->cc_len);
+	if (ret != 1) {
+		ret = KNOT_EINVAL;
+		goto fail;
+	}
+
+	if (input->nonce && input->nonce_len) {
+		ret = HMAC_Update(&ctx, (void *)input->nonce, input->nonce_len);
+		if (ret != 1) {
+			ret = KNOT_EINVAL;
+			goto fail;
+		}
+	}
+
+	if (input->srvr_data->clnt_sockaddr) {
+		addr = (uint8_t *)kr_inaddr(input->srvr_data->clnt_sockaddr);
+		addr_len = kr_inaddr_len(input->srvr_data->clnt_sockaddr);
+		if (addr && addr_len > 0) {
+			ret = HMAC_Update(&ctx, addr, addr_len);
+			if (ret != 1) {
+				ret = KNOT_EINVAL;
+				goto fail;
+			}
+		}
+	}
+
+	if (1 != HMAC_Final(&ctx, digest, &digest_len)) {
+		ret = KNOT_EINVAL;
+		goto fail;
+	}
+
+	assert(SRVR_HMAC_SHA256_64_HASH_SIZE <= SHA256_DIGEST_LENGTH);
+
+	*hash_len = SRVR_HMAC_SHA256_64_HASH_SIZE;
+	memcpy(hash_out, digest, *hash_len);
+
+	ret = KNOT_EOK;
+
+fail:
+	HMAC_CTX_cleanup(&ctx);
+	return ret;
+}
+
+const struct knot_cc_alg knot_cc_alg_hmac_sha256_64 = { KNOT_OPT_COOKIE_CLNT, cc_gen_hmac_sha256_64 };
+
+const struct knot_sc_alg knot_sc_alg_hmac_sha256_64 = { SRVR_HMAC_SHA256_64_HASH_SIZE, sc_gen_hmac_sha256_64 };
diff --git a/lib/cookies/alg_sha.h b/lib/cookies/alg_sha.h
new file mode 100644
index 0000000000000000000000000000000000000000..2d686aa4c798a3a0feaa17c211933d92b754fe33
--- /dev/null
+++ b/lib/cookies/alg_sha.h
@@ -0,0 +1,30 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <libknot/cookies/client.h>
+#include <libknot/cookies/server.h>
+
+#include "lib/defines.h"
+
+/** FNV-64 client cookie algorithm. */
+KR_EXPORT
+extern const struct knot_cc_alg knot_cc_alg_hmac_sha256_64;
+
+/** FNV-64 server cookie algorithm. */
+KR_EXPORT
+extern const struct knot_sc_alg knot_sc_alg_hmac_sha256_64;
diff --git a/lib/cookies/cache.c b/lib/cookies/cache.c
index 1ec38c1ef78e07d86d06eac98db997cafdeec808..98fa31a79214a37cd4aacf82164345c2fe950ba1 100644
--- a/lib/cookies/cache.c
+++ b/lib/cookies/cache.c
@@ -25,6 +25,7 @@
 #include "lib/cdb_lmdb.h"
 #include "lib/cookies/cache.h"
 #include "lib/cookies/control.h"
+#include "lib/utils.h"
 
 /* Key size */
 #define KEY_HSIZE (sizeof(uint8_t))
@@ -36,17 +37,16 @@
 /**
  * @internal Composed key as { u8 tag, u8[4,16] IP address }
  */
-static size_t cache_key(uint8_t *buf, uint8_t tag, const void *sockaddr)
+static size_t cache_key(uint8_t *buf, uint8_t tag, const struct sockaddr *sa)
 {
-	assert(buf && sockaddr);
+	assert(buf && sa);
 
-	const uint8_t *addr = NULL;
-	size_t addr_len = 0;
+	const char *addr = kr_inaddr(sa);
+	int addr_len = kr_inaddr_len(sa);
 
-	if (kr_ok() != knot_sockaddr_bytes(sockaddr, &addr, &addr_len)) {
+	if (!addr || (addr_len <= 0)) {
 		return 0;
 	}
-	assert(addr_len > 0);
 
 	buf[0] = tag;
 	memcpy(buf + sizeof(uint8_t), addr, addr_len);
@@ -55,14 +55,14 @@ static size_t cache_key(uint8_t *buf, uint8_t tag, const void *sockaddr)
 }
 
 static struct kr_cache_entry *lookup(struct kr_cache *cache, uint8_t tag,
-                                     const void *sockaddr)
+                                     const struct sockaddr *sa)
 {
-	if (!cache || !sockaddr) {
+	if (!cache || !sa) {
 		return NULL;
 	}
 
 	uint8_t keybuf[KEY_SIZE];
-	size_t key_len = cache_key(keybuf, tag, sockaddr);
+	size_t key_len = cache_key(keybuf, tag, sa);
 
 	/* Look up and return value */
 	knot_db_val_t key = { keybuf, key_len };
@@ -96,14 +96,14 @@ static int check_lifetime(struct kr_cache_entry *found, uint32_t *timestamp)
 }
 
 int kr_cookie_cache_peek(struct kr_cache *cache, uint8_t tag,
-                         const void *sockaddr, struct kr_cache_entry **entry,
+                         const struct sockaddr *sa, struct kr_cache_entry **entry,
                          uint32_t *timestamp)
 {
-	if (!cache_isvalid(cache) || !sockaddr || !entry) {
+	if (!cache_isvalid(cache) || !sa || !entry) {
 		return kr_error(EINVAL);
 	}
 
-	struct kr_cache_entry *found = lookup(cache, tag, sockaddr);
+	struct kr_cache_entry *found = lookup(cache, tag, sa);
 	if (!found) {
 		cache->stats.miss += 1;
 		return kr_error(ENOENT);
@@ -130,16 +130,16 @@ static void entry_write(struct kr_cache_entry *dst, struct kr_cache_entry *heade
 }
 
 int kr_cookie_cache_insert(struct kr_cache *cache,
-                           uint8_t tag, const void *sockaddr,
+                           uint8_t tag, const struct sockaddr *sa,
                            struct kr_cache_entry *header, knot_db_val_t data)
 {
-	if (!cache_isvalid(cache) || !sockaddr || !header) {
+	if (!cache_isvalid(cache) || !sa || !header) {
 		return kr_error(EINVAL);
 	}
 
 	/* Insert key */
 	uint8_t keybuf[KEY_SIZE];
-	size_t key_len = cache_key(keybuf, tag, sockaddr);
+	size_t key_len = cache_key(keybuf, tag, sa);
 	if (key_len == 0) {
 		return kr_error(EILSEQ);
 	}
@@ -169,14 +169,14 @@ int kr_cookie_cache_insert(struct kr_cache *cache,
 }
 
 int kr_cookie_cache_remove(struct kr_cache *cache,
-                           uint8_t tag, const void *sockaddr)
+                           uint8_t tag, const struct sockaddr *sa)
 {
-	if (!cache_isvalid(cache) || !sockaddr) {
+	if (!cache_isvalid(cache) || !sa) {
 		return kr_error(EINVAL);
 	}
 
 	uint8_t keybuf[KEY_SIZE];
-	size_t key_len = cache_key(keybuf, tag, sockaddr);
+	size_t key_len = cache_key(keybuf, tag, sa);
 	if (key_len == 0) {
 		return kr_error(EILSEQ);
 	}
@@ -185,16 +185,16 @@ int kr_cookie_cache_remove(struct kr_cache *cache,
 	return cache_op(cache, remove, &key, 1);
 }
 
-int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const void *sockaddr,
+int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const struct sockaddr *sa,
                                 struct timed_cookie *cookie, uint32_t *timestamp)
 {
-	if (!cache_isvalid(cache) || !sockaddr || !cookie || !timestamp) {
+	if (!cache_isvalid(cache) || !sa || !cookie || !timestamp) {
 		return kr_error(EINVAL);
 	}
 
 	/* Check if the RRSet is in the cache. */
 	struct kr_cache_entry *entry = NULL;
-	int ret = kr_cookie_cache_peek(cache, KR_CACHE_COOKIE, sockaddr,
+	int ret = kr_cookie_cache_peek(cache, KR_CACHE_COOKIE, sa,
 	                               &entry, timestamp);
 	if (ret != 0) {
 		return ret;
@@ -204,11 +204,11 @@ int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const void *sockaddr,
 	return kr_ok();
 }
 
-int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const void *sockaddr,
+int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const struct sockaddr *sa,
                                   const struct timed_cookie *cookie,
                                   uint32_t timestamp)
 {
-	if (!cache_isvalid(cache) || !sockaddr) {
+	if (!cache_isvalid(cache) || !sa) {
 		return kr_error(EINVAL);
 	}
 
@@ -230,6 +230,6 @@ int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const void *sockaddr,
 	                         knot_edns_opt_get_length(cookie->cookie_opt);
 
 	knot_db_val_t data = { (uint8_t *) cookie->cookie_opt, cookie_opt_size };
-	return kr_cookie_cache_insert(cache, KR_CACHE_COOKIE, sockaddr,
+	return kr_cookie_cache_insert(cache, KR_CACHE_COOKIE, sa,
 	                              &header, data);
 }
diff --git a/lib/cookies/cache.h b/lib/cookies/cache.h
index afd83c406729e3f838a637383f83daebc21ec6c9..bbe26ed47a71b1077b1fd7a800a28213866eb470 100644
--- a/lib/cookies/cache.h
+++ b/lib/cookies/cache.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <netinet/in.h>
+
 #include "lib/cache.h"
 
 /** DNS cookie cache entry tag. */
@@ -26,40 +28,40 @@
  * @note The 'drift' is the time passed between the inception time and now (in seconds).
  * @param cache cache structure
  * @param tag  asset tag
- * @param sockaddr asset socket address
+ * @param sa asset socket address
  * @param entry cache entry, will be set to valid pointer or NULL
  * @param timestamp current time (will be replaced with drift if successful)
  * @return 0 or an error code
  */
 KR_EXPORT
 int kr_cookie_cache_peek(struct kr_cache *cache,
-                         uint8_t tag, const void *sockaddr,
+                         uint8_t tag, const struct sockaddr *sa,
                          struct kr_cache_entry **entry, uint32_t *timestamp);
 
 /**
  * Insert asset into cache, replacing any existing data.
  * @param cache cache structure
  * @param tag  asset tag
- * @param sockaddr asset socket address
+ * @param sa asset socket address
  * @param header filled entry header (ttl and time stamp)
  * @param data inserted data
  * @return 0 or an error code
  */
 KR_EXPORT
 int kr_cookie_cache_insert(struct kr_cache *cache,
-                           uint8_t tag, const void *sockaddr,
+                           uint8_t tag, const struct sockaddr *sa,
                            struct kr_cache_entry *header, knot_db_val_t data);
 
 /**
  * Remove asset from cache.
  * @param cache cache structure
  * @param tag asset tag
- * @param sockaddr asset socket address
+ * @param sa asset socket address
  * @return 0 or an error code
  */
 KR_EXPORT
 int kr_cookie_cache_remove(struct kr_cache *cache,
-                           uint8_t tag, const void *sockaddr);
+                           uint8_t tag, const struct sockaddr *sa);
 
 /**
  * Structure used for cookie cache interface.
@@ -74,33 +76,33 @@ struct timed_cookie {
  * Peek the cache for given cookie (socket address)
  * @note The 'drift' is the time passed between the cache time of the cookie and now (in seconds).
  * @param cache cache structure
- * @param sockaddr socket address
+ * @param sa socket address
  * @param cookie asset
  * @param timestamp current time (will be replaced with drift if successful)
  * @return 0 or an error code
  */
 KR_EXPORT
-int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const void *sockaddr,
+int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const struct sockaddr *sa,
                                 struct timed_cookie *cookie, uint32_t *timestamp);
 
 /**
  * Insert a DNS cookie (client and server) entry for the given server signature (IP address).
  * @param cache cache structure
- * @param sockaddr server IP address
+ * @param sa server IP address
  * @param cookie ttl and whole EDNS cookie option (header, client and server cookies)
  * @param timestamp current time
  * @return 0 or an error code
  */
 KR_EXPORT
-int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const void *sockaddr,
+int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const struct sockaddr *sa,
                                   const struct timed_cookie *cookie,
                                   uint32_t timestamp);
 
 /**
  * Remove asset from cache.
  * @param txn transaction instance
- * @param sockaddr socket address
+ * @param sa socket address
  * @return 0 or an error code
  */
-#define kr_cookie_cache_remove_cookie(cache, sockaddr) \
-	kr_cookie_cache_remove((cache), KR_CACHE_COOKIE, (sockaddr))
+#define kr_cookie_cache_remove_cookie(cache, sa) \
+	kr_cookie_cache_remove((cache), KR_CACHE_COOKIE, (sa))
diff --git a/lib/cookies/control.c b/lib/cookies/control.c
index c2f2c55246e95e641b8f00bdda0d61c9e00620ad..f2ae2fca189580d653f2eb151fff0b5d292edf71 100644
--- a/lib/cookies/control.c
+++ b/lib/cookies/control.c
@@ -14,253 +14,9 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-//#define MODULE_DEBUG_MSGS 1 /* Comment out if debug messages are not desired. */
-
-#include <assert.h>
-#include <libknot/error.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "lib/cookies/cache.h"
 #include "lib/cookies/control.h"
-#include "lib/layer.h"
-#include "lib/utils.h"
-
-#if defined(MODULE_DEBUG_MSGS)
-#  define DEBUG_MSG(qry, fmt...) QRDEBUG(qry, "cookies_control",  fmt)
-#else /* !defined(MODULE_DEBUG_MSGS) */
-#  define DEBUG_MSG(qry, fmt...) do { } while (0)
-#endif /* defined(MODULE_DEBUG_MSGS) */
 
 struct kr_cookie_ctx kr_glob_cookie_ctx = {
 	.clnt = { false, { NULL, NULL }, { NULL, NULL}, DFLT_COOKIE_TTL },
 	.srvr = { false, { NULL, NULL }, { NULL, NULL} }
 };
-
-static int opt_rr_add_cookies(knot_rrset_t *opt_rr,
-                              uint8_t *cc, uint16_t cc_len,
-                              uint8_t *sc, uint16_t sc_len,
-                              knot_mm_t *mm)
-{
-	uint16_t cookies_size = 0;
-	uint8_t *cookies_data = NULL;
-
-	cookies_size = knot_edns_opt_cookie_data_len(cc_len, sc_len);
-
-	int ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE,
-	                                   cookies_size, &cookies_data, mm);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-	assert(cookies_data != NULL);
-
-	ret = knot_edns_opt_cookie_write(cc, cc_len, sc, sc_len,
-	                                 cookies_data, &cookies_size);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	assert(cookies_size == knot_edns_opt_cookie_data_len(cc_len, sc_len));
-
-	return KNOT_EOK;
-}
-
-static int opt_rr_add_option(knot_rrset_t *opt_rr, uint8_t *option,
-                             knot_mm_t *mm)
-{
-	assert(opt_rr && option);
-
-	uint8_t *reserved_data = NULL;
-	uint16_t opt_code = knot_edns_opt_get_code(option);
-	uint16_t opt_len = knot_edns_opt_get_length(option);
-	uint8_t *opt_data = knot_edns_opt_get_data(option);
-
-	int ret = knot_edns_reserve_option(opt_rr, opt_code,
-	                                   opt_len, &reserved_data, mm);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-	assert(reserved_data);
-
-	memcpy(reserved_data, opt_data, opt_len);
-	return KNOT_EOK;
-}
-
-/**
- * Check whether there is a cached cookie that matches the current client
- * cookie.
- */
-static const uint8_t *peek_and_check_cc(struct kr_cache *cache,
-                                        const void *sockaddr,
-                                        const uint8_t *cc, uint16_t cc_len)
-{
-	assert(cache && sockaddr && cc && cc_len);
-
-	uint32_t timestamp = 0;
-	struct timed_cookie timed_cookie = { 0, };
-
-	int ret = kr_cookie_cache_peek_cookie(cache, sockaddr, &timed_cookie,
-	                                      &timestamp);
-	if (ret != kr_ok()) {
-		return NULL;
-	}
-	assert(timed_cookie.cookie_opt);
-
-	/* Ignore the timestamp and time to leave. If the cookie is in cache
-	 * then just use it. The cookie control should be prerformed in the
-	 * cookie module/layer. */
-
-	const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) timed_cookie.cookie_opt);
-
-	if (cc_len == KNOT_OPT_COOKIE_CLNT &&
-	    0 == memcmp(cc, cached_cc, cc_len)) {
-		return timed_cookie.cookie_opt;
-	}
-
-	return NULL;
-}
-
-int kr_request_put_cookie(const struct kr_clnt_cookie_settings *clnt_cntrl,
-                          struct kr_cache *cookie_cache,
-                          const void *clnt_sockaddr, const void *srvr_sockaddr,
-                          knot_pkt_t *pkt)
-{
-	if (!clnt_cntrl || !pkt) {
-		return kr_error(EINVAL);
-	}
-
-	if (!pkt->opt_rr) {
-		return kr_ok();
-	}
-
-	if (!clnt_cntrl->csec || !clnt_cntrl->calg ||
-	    !cookie_cache) {
-		return kr_error(EINVAL);
-	}
-
-	/* Generate client cookie.
-	 * TODO -- generate client cookie from client address, server address
-	 * and secret quantity. */
-	struct knot_ccookie_input input = {
-		.clnt_sockaddr = clnt_sockaddr,
-		.srvr_sockaddr = srvr_sockaddr,
-		.secret_data = clnt_cntrl->csec->data,
-		.secret_len = clnt_cntrl->csec->size
-	};
-	uint8_t cc[KNOT_OPT_COOKIE_CLNT];
-	uint16_t cc_len = KNOT_OPT_COOKIE_CLNT;
-	assert(clnt_cntrl->calg && clnt_cntrl->calg->alg &&
-	       clnt_cntrl->calg->alg->gen_func);
-	int ret = clnt_cntrl->calg->alg->gen_func(&input, cc, &cc_len);
-	if (ret != kr_ok()) {
-		return ret;
-	}
-	assert(cc_len == KNOT_OPT_COOKIE_CLNT);
-
-	const uint8_t *cached_cookie = peek_and_check_cc(cookie_cache,
-	                                                 srvr_sockaddr,
-	                                                 cc, cc_len);
-
-	/* This is a very nasty hack that prevents the packet to be corrupted
-	 * when using contemporary 'Cookie interface'. */
-	assert(pkt->current == KNOT_ADDITIONAL);
-	pkt->sections[KNOT_ADDITIONAL].count -= 1;
-	pkt->rrset_count -= 1;
-	pkt->size -= knot_edns_wire_size(pkt->opt_rr);
-	knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1);
-#if 0
-	/* Reclaim reserved size -- does not work as intended.. */
-	ret = knot_pkt_reclaim(pkt, knot_edns_wire_size(pkt->opt_rr));
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-#endif
-
-	if (cached_cookie) {
-		ret = opt_rr_add_option(pkt->opt_rr, (uint8_t *) cached_cookie,
-		                        &pkt->mm);
-	} else {
-		ret = opt_rr_add_cookies(pkt->opt_rr, cc, cc_len,
-		                         NULL, 0, &pkt->mm);
-	}
-
-	/* Write to packet. */
-	assert(pkt->current == KNOT_ADDITIONAL);
-	return knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, pkt->opt_rr, KNOT_PF_FREE);
-}
-
-int kr_answer_opt_rr_add_cookies(const struct knot_scookie_input *input,
-                                 const struct kr_sc_alg_descr *sc_alg,
-                                 knot_pkt_t *pkt)
-{
-	if (!input || !sc_alg || !sc_alg->alg || pkt) {
-		kr_error(EINVAL);
-	}
-
-	uint16_t cookie_size = input->cc_len + sc_alg->alg->sc_size;
-	uint8_t *data = NULL;
-
-	if (!pkt->opt_rr) {
-		kr_error(EINVAL);
-	}
-	int ret = knot_edns_reserve_option(pkt->opt_rr,
-	                                   KNOT_EDNS_OPTION_COOKIE,
-	                                   cookie_size, &data, &pkt->mm);
-	if (ret != KNOT_EOK) {
-		return kr_error(ret);
-	}
-
-	memcpy(data, input->cc, input->cc_len);
-	cookie_size = sc_alg->alg->sc_size;
-	ret = sc_alg->alg->gen_func(input, data + input->cc_len, &cookie_size);
-	if (ret != kr_ok()) {
-		/* TODO -- Delete COOKIE option. */
-		return ret;
-	}
-
-	return ret;
-}
-
-int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode)
-{
-	if (!pkt || !knot_pkt_has_edns(pkt)) {
-		return kr_error(EINVAL);
-	}
-
-	uint8_t rcode = whole_rcode & 0x0f;
-	uint8_t ext_rcode = whole_rcode >> 4;
-	knot_wire_set_rcode(pkt->wire, rcode);
-	knot_edns_set_ext_rcode(pkt->opt_rr, ext_rcode);
-
-	return kr_ok();
-}
-
-uint8_t *kr_is_cookie_query(const knot_pkt_t *pkt)
-{
-	if (!pkt || knot_wire_get_qdcount(pkt->wire) > 0) {
-		return false;
-	}
-
-	if (knot_wire_get_qr(pkt->wire) != 0 || !pkt->opt_rr) {
-		return false;
-	}
-
-	return knot_edns_get_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE);
-}
-
-int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies)
-{
-	if (!cookie_opt || !cookies) {
-		kr_error(EINVAL);
-	}
-
-	const uint8_t *cookie_data = knot_edns_opt_get_data(cookie_opt);
-	uint16_t cookie_len = knot_edns_opt_get_length(cookie_opt);
-	assert(cookie_data && cookie_len);
-
-	int ret =  knot_edns_opt_cookie_parse(cookie_data, cookie_len,
-	                                      &cookies->cc, &cookies->cc_len,
-	                                      &cookies->sc, &cookies->sc_len);
-
-	return (ret == KNOT_EOK) ? kr_ok() : kr_error(EINVAL);
-}
diff --git a/lib/cookies/control.h b/lib/cookies/control.h
index e3bf44738f9a25bc2bfe6a8fceda6731036055b4..c2842d81c9dbb739aba41eccf4b100674cbd253f 100644
--- a/lib/cookies/control.h
+++ b/lib/cookies/control.h
@@ -16,9 +16,10 @@
 
 #pragma once
 
-#include <libknot/packet/pkt.h>
 #include <libknot/rrtype/opt-cookie.h>
 #include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
 
 #include "lib/cookies/alg_containers.h"
 #include "lib/cache.h"
@@ -72,58 +73,3 @@ struct kr_cookie_ctx {
 /** Global cookie control context. */
 KR_EXPORT
 extern struct kr_cookie_ctx kr_glob_cookie_ctx;
-
-/**
- * @brief Insert a DNS cookie into query packet.
- * @note The packet must already contain ENDS section.
- * @param clnt_cntrl    Client cookie control structure.
- * @param cookie_cache  Cookie cache.
- * @param clnt_sockaddr Client address.
- * @param srvr_sockaddr Server address.
- * @param pkt           DNS request packet.
- */
-KR_EXPORT
-int kr_request_put_cookie(const struct kr_clnt_cookie_settings *clnt_cntrl,
-                          struct kr_cache *cookie_cache,
-                          const void *clnt_sockaddr, const void *srvr_sockaddr,
-                          knot_pkt_t *pkt);
-
-/**
- * @brief Add cookies into answer.
- * @note Data are only added into the OPT RR.
- * @param input input data to generate server cookie from
- * @param sc_alg algorithm to use
- * @param pkt packet which to put cookie into
- * @return kr_ok() or error code
- */
-KR_EXPORT
-int kr_answer_opt_rr_add_cookies(const struct knot_scookie_input *input,
-                                 const struct kr_sc_alg_descr *sc_alg,
-                                 knot_pkt_t *pkt);
-
-/**
- * @brief Set RCODE and extended RCODE.
- * @param pkt DNS packet
- * @param whole_rcode RCODE value
- * @return kr_ok() or error code
- */
-KR_EXPORT
-int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode);
-
-/**
- * @brief Check whether packet is a server cookie request.
- * @param pkt     Packet to be examined.
- * @param cookies Received cookies.
- * @return Pointer to entire cookie option if is a cookie query, NULL else.
- */
-KR_EXPORT
-uint8_t *kr_is_cookie_query(const knot_pkt_t *pkt);
-
-/**
- * @brief Parse cookies from cookie option.
- * @param cookie_opt Cookie option.
- * @param cookies    Cookie structure to be set.
- * @return kr_ok() on success, error if cookies are malformed.
- */
-KR_EXPORT
-int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies);
diff --git a/lib/cookies/helper.c b/lib/cookies/helper.c
new file mode 100644
index 0000000000000000000000000000000000000000..5d4c3b869998b7d9e969617b178fe37ebd44b663
--- /dev/null
+++ b/lib/cookies/helper.c
@@ -0,0 +1,285 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <libknot/rrtype/opt-cookie.h>
+
+#include "lib/cookies/cache.h"
+#include "lib/cookies/helper.h"
+#include "lib/defines.h"
+
+/**
+ * @brief Check whether there is a cached cookie that matches the current
+ *        client cookie.
+ */
+static const uint8_t *peek_and_check_cc(struct kr_cache *cache,
+                                        const void *sockaddr,
+                                        const uint8_t *cc, uint16_t cc_len)
+{
+	assert(cache && sockaddr && cc && cc_len);
+
+	uint32_t timestamp = 0;
+	struct timed_cookie timed_cookie = { 0, };
+
+	int ret = kr_cookie_cache_peek_cookie(cache, sockaddr, &timed_cookie,
+	                                      &timestamp);
+	if (ret != kr_ok()) {
+		return NULL;
+	}
+	assert(timed_cookie.cookie_opt);
+
+	/* Ignore the time stamp and time to leave. If the cookie is in cache
+	 * then just use it. The cookie control should be performed in the
+	 * cookie module/layer. */
+
+	const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) timed_cookie.cookie_opt);
+
+	if (cc_len == KNOT_OPT_COOKIE_CLNT &&
+	    0 == memcmp(cc, cached_cc, cc_len)) {
+		return timed_cookie.cookie_opt;
+	}
+
+	return NULL;
+}
+
+/**
+ * @brief Adds entire EDNS option into the RR Set.
+ */
+static int opt_rr_add_opt(knot_rrset_t *opt_rr, uint8_t *option, knot_mm_t *mm)
+{
+	assert(opt_rr && option);
+
+	uint8_t *reserved_data = NULL;
+	uint16_t opt_code = knot_edns_opt_get_code(option);
+	uint16_t opt_len = knot_edns_opt_get_length(option);
+	uint8_t *opt_data = knot_edns_opt_get_data(option);
+
+	int ret = knot_edns_reserve_option(opt_rr, opt_code,
+	                                   opt_len, &reserved_data, mm);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	assert(reserved_data);
+
+	memcpy(reserved_data, opt_data, opt_len);
+	return KNOT_EOK;
+}
+
+/**
+ * @brief Add a client cookie option into the RR Set.
+ */
+static int opt_rr_add_cc(knot_rrset_t *opt_rr, uint8_t *cc, uint16_t cc_len,
+                              knot_mm_t *mm)
+{
+#define SC NULL
+#define SC_LEN 0
+	uint16_t cookies_size = 0;
+	uint8_t *cookies_data = NULL;
+
+	cookies_size = knot_edns_opt_cookie_data_len(cc_len, SC_LEN);
+
+	int ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE,
+	                                   cookies_size, &cookies_data, mm);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	assert(cookies_data != NULL);
+
+	ret = knot_edns_opt_cookie_write(cc, cc_len, SC, SC_LEN,
+	                                 cookies_data, &cookies_size);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	assert(cookies_size == knot_edns_opt_cookie_data_len(cc_len, SC_LEN));
+
+	return KNOT_EOK;
+#undef SC
+#undef SC_LEN
+}
+
+int kr_request_put_cookie(const struct kr_clnt_cookie_settings *clnt_cntrl,
+                          struct kr_cache *cookie_cache,
+                          const struct sockaddr *clnt_sa,
+                          const struct sockaddr *srvr_sa,
+                          knot_pkt_t *pkt)
+{
+	if (!clnt_cntrl || !pkt) {
+		return kr_error(EINVAL);
+	}
+
+	if (!pkt->opt_rr) {
+		return kr_ok();
+	}
+
+	if (!clnt_cntrl->csec || !clnt_cntrl->calg ||
+	    !cookie_cache) {
+		return kr_error(EINVAL);
+	}
+
+	/* Generate client cookie.
+	 * TODO -- generate client cookie from client address, server address
+	 * and secret quantity. */
+	struct knot_cc_input input = {
+		.clnt_sockaddr = clnt_sa,
+		.srvr_sockaddr = srvr_sa,
+		.secret_data = clnt_cntrl->csec->data,
+		.secret_len = clnt_cntrl->csec->size
+	};
+	uint8_t cc[KNOT_OPT_COOKIE_CLNT];
+	uint16_t cc_len = KNOT_OPT_COOKIE_CLNT;
+	assert(clnt_cntrl->calg && clnt_cntrl->calg->alg &&
+	       clnt_cntrl->calg->alg->gen_func);
+	int ret = clnt_cntrl->calg->alg->gen_func(&input, cc, &cc_len);
+	if (ret != kr_ok()) {
+		return ret;
+	}
+	assert(cc_len == KNOT_OPT_COOKIE_CLNT);
+
+	const uint8_t *cached_cookie = peek_and_check_cc(cookie_cache,
+	                                                 srvr_sa, cc, cc_len);
+
+	/* This is a very nasty hack that prevents the packet to be corrupted
+	 * when using contemporary 'Cookie interface'. */
+	assert(pkt->current == KNOT_ADDITIONAL);
+	pkt->sections[KNOT_ADDITIONAL].count -= 1;
+	pkt->rrset_count -= 1;
+	pkt->size -= knot_edns_wire_size(pkt->opt_rr);
+	knot_wire_set_arcount(pkt->wire, knot_wire_get_arcount(pkt->wire) - 1);
+#if 0
+	/* Reclaim reserved size -- does not work as intended.. */
+	ret = knot_pkt_reclaim(pkt, knot_edns_wire_size(pkt->opt_rr));
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+#endif
+
+	if (cached_cookie) {
+		ret = opt_rr_add_opt(pkt->opt_rr, (uint8_t *)cached_cookie,
+		                     &pkt->mm);
+	} else {
+		ret = opt_rr_add_cc(pkt->opt_rr, cc, cc_len, &pkt->mm);
+	}
+
+	/* Write to packet. */
+	assert(pkt->current == KNOT_ADDITIONAL);
+	return knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, pkt->opt_rr, KNOT_PF_FREE);
+}
+
+int kr_answer_write_cookie(const struct knot_sc_private *srvr_data,
+                           const uint8_t *cc, uint16_t cc_len,
+                           struct kr_nonce_input *nonce,
+                           const struct knot_sc_alg *alg,
+                           knot_pkt_t *pkt)
+{
+	if (!srvr_data || !srvr_data->clnt_sockaddr ||
+	    !srvr_data->secret_data|| !srvr_data->secret_len) {
+		return kr_error(EINVAL);
+	}
+
+	if (!cc || !cc_len || !nonce) {
+		return kr_error(EINVAL);
+	}
+
+	if (!alg || !alg->hash_size || !alg->hash_func) {
+		return kr_error(EINVAL);
+	}
+
+	if (!pkt && !pkt->opt_rr) {
+		return kr_error(EINVAL);
+	}
+
+	uint16_t nonce_len = NONCE_LEN;
+	uint16_t hash_len = alg->hash_size;
+
+	uint8_t *cookie = NULL;
+	uint16_t cookie_len = knot_edns_opt_cookie_data_len(cc_len,
+	                                                    nonce_len + hash_len);
+	if (cookie_len == 0) {
+		return kr_error(EINVAL);
+	}
+
+	int ret = knot_edns_reserve_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE,
+	                                   cookie_len, &cookie, &pkt->mm);
+	if (ret != KNOT_EOK) {
+		return kr_error(ENOMEM);
+	}
+
+	struct knot_sc_input input = {
+		.cc = cookie,
+		.cc_len = cc_len,
+		.srvr_data = srvr_data
+	};
+	memcpy(cookie, cc, cc_len);
+
+	if (nonce_len) {
+		kr_nonce_write_wire(cookie + cc_len, &nonce_len, nonce);
+
+		input.nonce = cookie + cc_len;
+		input.nonce_len = nonce_len;
+	}
+
+	ret = alg->hash_func(&input, cookie + cc_len + nonce_len, &hash_len);
+	if (ret != KNOT_EOK) {
+		return kr_error(EINVAL);
+	}
+
+	return kr_ok();
+}
+
+int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode)
+{
+	if (!pkt || !knot_pkt_has_edns(pkt)) {
+		return kr_error(EINVAL);
+	}
+
+	uint8_t rcode = whole_rcode & 0x0f;
+	uint8_t ext_rcode = whole_rcode >> 4;
+	knot_wire_set_rcode(pkt->wire, rcode);
+	knot_edns_set_ext_rcode(pkt->opt_rr, ext_rcode);
+
+	return kr_ok();
+}
+
+uint8_t *kr_is_cookie_query(const knot_pkt_t *pkt)
+{
+	if (!pkt || knot_wire_get_qdcount(pkt->wire) > 0) {
+		return false;
+	}
+
+	if (knot_wire_get_qr(pkt->wire) != 0 || !pkt->opt_rr) {
+		return false;
+	}
+
+	return knot_edns_get_option(pkt->opt_rr, KNOT_EDNS_OPTION_COOKIE);
+}
+
+int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies)
+{
+	if (!cookie_opt || !cookies) {
+		kr_error(EINVAL);
+	}
+
+	const uint8_t *cookie_data = knot_edns_opt_get_data(cookie_opt);
+	uint16_t cookie_len = knot_edns_opt_get_length(cookie_opt);
+	assert(cookie_data && cookie_len);
+
+	int ret =  knot_edns_opt_cookie_parse(cookie_data, cookie_len,
+	                                      &cookies->cc, &cookies->cc_len,
+	                                      &cookies->sc, &cookies->sc_len);
+
+	return (ret == KNOT_EOK) ? kr_ok() : kr_error(EINVAL);
+}
diff --git a/lib/cookies/helper.h b/lib/cookies/helper.h
new file mode 100644
index 0000000000000000000000000000000000000000..ec4d9fc310140cc8c5927673e4fef9b4cd4adb80
--- /dev/null
+++ b/lib/cookies/helper.h
@@ -0,0 +1,84 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/cookies/control.h"
+#include "lib/cookies/nonce.h"
+#include "lib/defines.h"
+
+/**
+ * @brief Insert a DNS cookie into query packet.
+ * @note The packet must already contain ENDS section.
+ * @param clnt_cntrl   client cookie control structure
+ * @param cookie_cache cookie cache
+ * @param clnt_sa      client socket address
+ * @param srvr_sa      server socket address
+ * @param pkt          DNS request packet
+ * @return kr_ok() or error code
+ */
+KR_EXPORT
+int kr_request_put_cookie(const struct kr_clnt_cookie_settings *clnt_cntrl,
+                          struct kr_cache *cookie_cache,
+                          const struct sockaddr *clnt_sa,
+                          const struct sockaddr *srvr_sa,
+                          knot_pkt_t *pkt);
+
+/**
+ * @brief Inserts a cookie option into the OPT RR. It does not write any
+ *        wire data.
+ * @param srvr_data server knowledge
+ * @param cc        client cookie
+ * @param cc_len    client cookie length
+ * @param nonce     nonce value
+ * @param alg       hash algorithm
+ * @param pkt       DNS response packet
+ */
+KR_EXPORT
+int kr_answer_write_cookie(const struct knot_sc_private *srvr_data,
+                           const uint8_t *cc, uint16_t cc_len,
+                           struct kr_nonce_input *nonce,
+                           const struct knot_sc_alg *alg,
+                           knot_pkt_t *pkt);
+
+/**
+ * @brief Set RCODE and extended RCODE.
+ * @param pkt DNS packet
+ * @param whole_rcode RCODE value
+ * @return kr_ok() or error code
+ */
+KR_EXPORT
+int kr_pkt_set_ext_rcode(knot_pkt_t *pkt, uint16_t whole_rcode);
+
+/**
+ * @brief Check whether packet is a server cookie request.
+ * @param pkt     Packet to be examined.
+ * @param cookies Received cookies.
+ * @return Pointer to entire cookie option if is a cookie query, NULL else.
+ */
+KR_EXPORT
+uint8_t *kr_is_cookie_query(const knot_pkt_t *pkt);
+
+/**
+ * @brief Parse cookies from cookie option.
+ * @param cookie_opt Cookie option.
+ * @param cookies    Cookie structure to be set.
+ * @return kr_ok() on success, error if cookies are malformed.
+ */
+KR_EXPORT
+int kr_parse_cookie_opt(uint8_t *cookie_opt, struct knot_dns_cookies *cookies);
diff --git a/lib/cookies/nonce.c b/lib/cookies/nonce.c
new file mode 100644
index 0000000000000000000000000000000000000000..486e35dd93c8a7a14333990c56cd3cc79de508d2
--- /dev/null
+++ b/lib/cookies/nonce.c
@@ -0,0 +1,41 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <assert.h>
+
+#include "lib/cookies/nonce.h"
+
+int kr_nonce_write_wire(uint8_t *buf, uint16_t *buf_len,
+                        struct kr_nonce_input *input)
+{
+	if (!buf || !buf_len || !input) {;
+		return kr_error(EINVAL);
+	}
+
+	if (*buf_len < NONCE_LEN) {
+		kr_error(EINVAL);
+	}
+
+	uint32_t aux = htonl(input->rand);
+	memcpy(buf, &aux, sizeof(aux));
+	aux = htonl(input->time);
+	memcpy(buf + sizeof(aux), &aux, sizeof(aux));
+	*buf_len = 2 * sizeof(aux);
+	assert(NONCE_LEN == *buf_len);
+
+	return kr_ok();
+}
diff --git a/lib/cookies/nonce.h b/lib/cookies/nonce.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7311e2d786a96c5f96b382fc25d1b71f116f5da
--- /dev/null
+++ b/lib/cookies/nonce.h
@@ -0,0 +1,44 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "lib/defines.h"
+
+/* RFC7873 Appendix B.2 mentions an algorithm using two values before the
+ * actual server cookie hash. */
+
+/** Nonce value length. */
+#define NONCE_LEN 8
+
+/** Input data to generate nonce from. */
+struct kr_nonce_input {
+	uint32_t rand; /**< some random value */
+	uint32_t time; /**< time stamp */
+};
+
+/**
+ * @brief Writes server cookie nonce balue into given buffer.
+ *
+ * @param buf     buffer to write nonce data in wire format into
+ * @param buf_len on input contains nonce buffer size, on output contains
+ *                size of actual written data
+ * @param input   data to generate wire data from
+ * @return kr_ok() on success, error code else
+ */
+KR_EXPORT
+int kr_nonce_write_wire(uint8_t *buf, uint16_t *buf_len,
+                        struct kr_nonce_input *input);
diff --git a/lib/layer/cookiemonster.c b/lib/layer/cookiemonster.c
index a46bcb0ba06d1144be40a07a42e51062a92d29c8..5f1b98fb9fc085a9dacc172a0a164f68d331dd09 100644
--- a/lib/layer/cookiemonster.c
+++ b/lib/layer/cookiemonster.c
@@ -28,6 +28,8 @@
 #include "lib/cookies/alg_containers.h"
 #include "lib/cookies/cache.h"
 #include "lib/cookies/control.h"
+#include "lib/cookies/helper.h"
+#include "lib/cookies/nonce.h"
 #include "lib/module.h"
 #include "lib/layer.h"
 
@@ -72,8 +74,8 @@ static const struct sockaddr *guess_server_addr(const struct kr_nsrep *nsrep,
 
 	const struct sockaddr *sockaddr = NULL;
 
-	struct knot_ccookie_input input = {
-		.clnt_sockaddr = NULL,
+	struct knot_cc_input input = {
+		.clnt_sockaddr = NULL, /* Not supported yet. */
 		.srvr_sockaddr = NULL,
 		.secret_data = csecr->data,
 		.secret_len = csecr->size
@@ -85,10 +87,10 @@ static const struct sockaddr *guess_server_addr(const struct kr_nsrep *nsrep,
 			break;
 		}
 
-		input.srvr_sockaddr = &nsrep->addr[i];
-		int ret = knot_ccookie_check(cc, cc_len, &input, cc_alg->alg);
+		input.srvr_sockaddr = (struct sockaddr *)&nsrep->addr[i];
+		int ret = knot_cc_check(cc, cc_len, &input, cc_alg->alg);
 		if (ret == KNOT_EOK) {
-			sockaddr = (struct sockaddr *) &nsrep->addr[i];
+			sockaddr = (struct sockaddr *)&nsrep->addr[i];
 			break;
 		}
 	}
@@ -118,21 +120,21 @@ static int srvr_sockaddr_cc_check(const struct sockaddr **sockaddr,
 	if (tmp_sockaddr) {
 		assert(clnt_cntrl->current.csec);
 
-		struct knot_ccookie_input input = {
-			.clnt_sockaddr = NULL,
+		struct knot_cc_input input = {
+			.clnt_sockaddr = NULL, /* Not supported yet. */
 			.srvr_sockaddr = tmp_sockaddr,
 			.secret_data = clnt_cntrl->current.csec->data,
 			.secret_len = clnt_cntrl->current.csec->size
 		};
-		int ret = knot_ccookie_check(cc, cc_len, &input,
+		int ret = knot_cc_check(cc, cc_len, &input,
 		                             clnt_cntrl->current.calg->alg);
 		bool have_current = (ret == KNOT_EOK);
 		if ((ret != KNOT_EOK) &&
 		    clnt_cntrl->recent.csec && clnt_cntrl->recent.calg) {
 			input.secret_data = clnt_cntrl->recent.csec->data;
 			input.secret_len = clnt_cntrl->recent.csec->size;
-			ret = knot_ccookie_check(cc, cc_len, &input,
-			                         clnt_cntrl->recent.calg->alg);
+			ret = knot_cc_check(cc, cc_len, &input,
+			                    clnt_cntrl->recent.calg->alg);
 		}
 		if (ret == KNOT_EOK) {
 			*sockaddr = tmp_sockaddr;
@@ -381,8 +383,6 @@ static inline uint8_t *req_cookie_option(struct kr_request *req)
 	return knot_edns_get_option(req->qsource.opt, KNOT_EDNS_OPTION_COOKIE);
 }
 
-/* TODO -- DNS cookie request. */
-
 static int check_request(knot_layer_t *ctx, void *module_param)
 {
 	if (!kr_glob_cookie_ctx.srvr.enabled) {
@@ -417,18 +417,15 @@ static int check_request(knot_layer_t *ctx, void *module_param)
 
 	int return_state = ctx->state;
 
-	struct knot_scookie_check_ctx check_ctx = {
+	struct knot_sc_private srvr_data = {
 		.clnt_sockaddr = req->qsource.addr,
 		.secret_data = srvr_cntrl->current.ssec->data,
 		.secret_len = srvr_cntrl->current.ssec->size
 	};
 
-	struct knot_scookie_input input = {
-		.cc = cookies.cc,
-		.cc_len = cookies.cc_len,
-		.nonce = kr_rand_uint(UINT32_MAX),
-		.time = req->current_query->timestamp.tv_sec,
-		.srvr_data = &check_ctx
+	struct kr_nonce_input nonce = {
+		.rand = kr_rand_uint(UINT32_MAX),
+		.time = req->current_query->timestamp.tv_sec
 	};
 
 	if (!cookies.sc) {
@@ -451,18 +448,18 @@ static int check_request(knot_layer_t *ctx, void *module_param)
 
 	/* Check server cookie obtained in request. */
 
-	ret = knot_scookie_check(&cookies, &check_ctx,
-	                         srvr_cntrl->current.salg->alg);
+	ret = knot_sc_check(NONCE_LEN, &cookies, &srvr_data,
+	                    srvr_cntrl->current.salg->alg);
 	if (ret == KNOT_EINVAL &&
 	    srvr_cntrl->recent.ssec && srvr_cntrl->recent.salg->alg) {
 		/* Try recent algorithm. */
-		struct knot_scookie_check_ctx recent_ctx = {
+		struct knot_sc_private recent_srvr_data = {
 			.clnt_sockaddr = req->qsource.addr,
 			.secret_data = srvr_cntrl->recent.ssec->data,
 			.secret_len = srvr_cntrl->recent.ssec->size
 		};
-		ret = knot_scookie_check(&cookies, &recent_ctx,
-		                         srvr_cntrl->recent.salg->alg);
+		ret = knot_sc_check(NONCE_LEN, &cookies, &recent_srvr_data,
+		                    srvr_cntrl->recent.salg->alg);
 	}
 	if (ret != KNOT_EOK) {
 		/* TODO -- Silently discard? */
@@ -486,8 +483,12 @@ static int check_request(knot_layer_t *ctx, void *module_param)
 
 answer_add_cookies:
 	/* Add server cookie into response. */
-	kr_answer_opt_rr_add_cookies(&input, srvr_cntrl->current.salg,
+	ret = kr_answer_write_cookie(&srvr_data, cookies.cc, cookies.cc_len,
+	                             &nonce, srvr_cntrl->current.salg->alg,
 	                             req->answer);
+	if (ret != kr_ok()) {
+		return_state = KNOT_STATE_FAIL;
+	}
 	return return_state;
 }
 
diff --git a/lib/lib.mk b/lib/lib.mk
index 17b8456701185625ee4ff11475f21588aa849cbf..4eccea64c80672c4f1176e9234fe58eb6793e10d 100644
--- a/lib/lib.mk
+++ b/lib/lib.mk
@@ -48,15 +48,19 @@ ifeq ($(HAS_libcrypto),yes)
 libkres_SOURCES += \
 	lib/layer/cookiemonster.c \
 	lib/cookies/alg_containers.c \
-	lib/cookies/alg_sha.c  \
-	lib/cookies/cache.c    \
-	lib/cookies/control.c
+	lib/cookies/alg_sha.c \
+	lib/cookies/cache.c \
+	lib/cookies/control.c \
+	lib/cookies/helper.c \
+	lib/cookies/nonce.c
 
 libkres_HEADERS += \
-	lib/cookies/alg_containers..h \
-	lib/cookies/alg_sha.h  \
-	lib/cookies/cache.h    \
-	lib/cookies/control.h
+	lib/cookies/alg_containers.h \
+	lib/cookies/alg_sha.h \
+	lib/cookies/cache.h \
+	lib/cookies/control.h \
+	lib/cookies/helper.h \
+	lib/cookies/nonce.h
 
 libkres_LIBS += $(libcrypto_LIBS)
 endif
diff --git a/lib/resolve.c b/lib/resolve.c
index e9e2728d8401ceef3cb2ebf9e39b8cde6249b40f..cc93f2b2a2e41b5ef8f02d66178f3a208c8dbe62 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -29,6 +29,8 @@
 #include "lib/dnssec/ta.h"
 #if defined(ENABLE_COOKIES)
 #include "lib/cookies/control.h"
+#include "lib/cookies/helper.h"
+#include "lib/cookies/nonce.h"
 #endif /* defined(ENABLE_COOKIES) */
 
 #define DEBUG_MSG(qry, fmt...) QRDEBUG((qry), "resl",  fmt)
@@ -410,7 +412,7 @@ static int cookie_answer(const void *clnt_sockaddr,
 	knot_wire_set_ra(answer->wire);
 	knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR);
 
-	struct knot_scookie_check_ctx check_ctx = {
+	struct knot_sc_private srvr_data = {
 		.clnt_sockaddr = clnt_sockaddr,
 		.secret_data = srvr_cntrl->current.ssec->data,
 		.secret_len = srvr_cntrl->current.ssec->size
@@ -419,28 +421,22 @@ static int cookie_answer(const void *clnt_sockaddr,
 	struct timeval tv;
 	gettimeofday(&tv, NULL);
 
-	struct knot_scookie_input input = {
-		.cc = cookies->cc,
-		.cc_len = cookies->cc_len,
-		.nonce = kr_rand_uint(UINT32_MAX),
-		.time = tv.tv_sec,
-		.srvr_data = &check_ctx
+	struct kr_nonce_input nonce = {
+		.rand = kr_rand_uint(UINT32_MAX),
+		.time = tv.tv_sec
 	};
 
-	int ret =  kr_answer_opt_rr_add_cookies(&input,
-	                                        srvr_cntrl->current.salg,
-	                                        answer);
+	/* Add fres cookie into the answer. */
+	int ret = kr_answer_write_cookie(&srvr_data,
+	                                 cookies->cc, cookies->cc_len, &nonce,
+	                                 srvr_cntrl->current.salg->alg, answer);
 	if (ret != kr_ok()) {
 		return KNOT_STATE_FAIL;
 	}
 
-	if (!cookies->sc) {
-		return KNOT_STATE_DONE;
-	}
-
 	/* Check server cookie only with current settings. */
-	ret = knot_scookie_check(cookies, &check_ctx,
-	                         srvr_cntrl->current.salg->alg);
+	ret = knot_sc_check(NONCE_LEN, cookies, &srvr_data,
+	                    srvr_cntrl->current.salg->alg);
 	if (ret != KNOT_EOK) {
 		kr_pkt_set_ext_rcode(answer, KNOT_RCODE_BADCOOKIE);
 		return KNOT_STATE_FAIL | KNOT_STATE_DONE;
@@ -459,7 +455,11 @@ static int resolve_query(struct kr_request *request, const knot_pkt_t *packet)
 	struct kr_query *qry = kr_rplan_push(rplan, NULL, qname, qclass, qtype);
 	if (!qry) {
 #if defined(ENABLE_COOKIES)
-		/* May be a DNS cookies query. */
+		/* RFC7873 5.4 specifies a query for server cookie. Such query
+		 * has QDCOUNT == 0 and contains a cookie option.
+		 *
+		 * The layers don't expect to handle queries with QDCOUNT != 1
+		 * so such queries are handled directly here. */
 		struct knot_dns_cookies cookies = { 0, };
 		uint8_t *cookie_opt = kr_is_cookie_query(packet);
 		if (cookie_opt && kr_glob_cookie_ctx.clnt.enabled) {
diff --git a/modules/cookiectl/cookiectl.c b/modules/cookiectl/cookiectl.c
index 23849b65c3333f1cf70f9cd0a05d0ba5713e4277..55e7cba9212fd806623a1f6a05e76125c05bd527 100644
--- a/modules/cookiectl/cookiectl.c
+++ b/modules/cookiectl/cookiectl.c
@@ -281,7 +281,7 @@ static bool read_available_sc_hashes(JsonNode *root)
 	}
 
 	const struct kr_sc_alg_descr *aux_ptr = kr_sc_algs;
-	while (aux_ptr && aux_ptr->alg && aux_ptr->alg->gen_func) {
+	while (aux_ptr && aux_ptr->alg && aux_ptr->alg->hash_func) {
 		assert(aux_ptr->name);
 		JsonNode *element = json_mkstring(aux_ptr->name);
 		if (!element) {
@@ -477,8 +477,7 @@ int cookiectl_init(struct kr_module *module)
 
 	kr_glob_cookie_ctx.srvr.enabled = false;
 	kr_glob_cookie_ctx.srvr.current.ssec = ss;
-	kr_glob_cookie_ctx.srvr.current.salg = kr_sc_alg(kr_sc_algs,
-	                                                 "HMAC-SHA256-64");
+	kr_glob_cookie_ctx.srvr.current.salg = kr_sc_alg(kr_sc_algs, "FNV-64");
 
 	module->data = NULL;