diff --git a/examples/nanocoap_server/Makefile b/examples/nanocoap_server/Makefile
index ec2b6e1b0e099d8a926bf280e2318fd7baa6f285..9030ca052de70fae292311825dc6e82481b306f0 100644
--- a/examples/nanocoap_server/Makefile
+++ b/examples/nanocoap_server/Makefile
@@ -27,6 +27,9 @@ USEMODULE += nanocoap_sock
 # include this for nicely formatting the returned internal value
 USEMODULE += fmt
 
+# include sha256 (used by example blockwise handler)
+USEMODULE += hashes
+
 # include this for printing IP addresses
 USEMODULE += shell_commands
 
diff --git a/examples/nanocoap_server/coap_handler.c b/examples/nanocoap_server/coap_handler.c
index 06bd294f0b873a22fae2f13348933d68bee653a4..9a254ac2da22807eed89105df408c7a9a3d2af4d 100644
--- a/examples/nanocoap_server/coap_handler.c
+++ b/examples/nanocoap_server/coap_handler.c
@@ -12,6 +12,7 @@
 
 #include "fmt.h"
 #include "net/nanocoap.h"
+#include "hashes/sha256.h"
 
 /* internal value that can be read/written via CoAP */
 static uint8_t internal_value = 0;
@@ -55,9 +56,58 @@ static ssize_t _riot_value_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, vo
             COAP_FORMAT_TEXT, (uint8_t*)rsp, p);
 }
 
+ssize_t _sha256_handler(coap_pkt_t* pkt, uint8_t *buf, size_t len, void *context)
+{
+    (void)context;
+
+    /* using a shared sha256 context *will* break if two requests are handled
+     * at the same time.  doing it anyways, as this is meant to showcase block1
+     * support, not proper synchronisation. */
+    static sha256_context_t sha256;
+
+    uint8_t digest[SHA256_DIGEST_LENGTH];
+
+    uint32_t result = COAP_CODE_204;
+
+    coap_block1_t block1;
+    int blockwise = coap_get_block1(pkt, &block1);
+
+    printf("_sha256_handler(): received data: offset=%u len=%u blockwise=%i more=%i\n", \
+            (unsigned)block1.offset, pkt->payload_len, blockwise, block1.more);
+
+    if (block1.offset == 0) {
+        puts("_sha256_handler(): init");
+        sha256_init(&sha256);
+    }
+
+    sha256_update(&sha256, pkt->payload, pkt->payload_len);
+
+    if (block1.more == 1) {
+        result = COAP_CODE_231;
+    }
+
+    size_t result_len = 0;
+    if (!blockwise || !block1.more) {
+        puts("_sha256_handler(): finish");
+        sha256_final(&sha256, digest);
+        result_len = SHA256_DIGEST_LENGTH * 2;
+    }
+
+    ssize_t reply_len = coap_build_reply(pkt, result, buf, len, 0);
+    uint8_t *pkt_pos = (uint8_t*)pkt->hdr + reply_len;
+    pkt_pos += coap_put_block1_ok(pkt_pos, &block1, 0);
+    if (result_len) {
+        *pkt_pos++ = 0xFF;
+        pkt_pos += fmt_bytes_hex((char *)pkt_pos, digest, sizeof(digest));
+    }
+
+    return pkt_pos - (uint8_t*)pkt->hdr;
+}
+
 /* must be sorted by path (alphabetically) */
 const coap_resource_t coap_resources[] = {
     COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER,
+    { "/sha256", COAP_POST, _sha256_handler, NULL },
     { "/riot/board", COAP_GET, _riot_board_handler, NULL },
     { "/riot/value", COAP_GET | COAP_PUT | COAP_POST, _riot_value_handler, NULL },
 };
diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h
index 80d7fa6e371fb29711899044931fdeb7583d0346..e0af70a155a4b9ebf45928109e4c02a89680f780 100644
--- a/sys/include/net/nanocoap.h
+++ b/sys/include/net/nanocoap.h
@@ -65,6 +65,8 @@ extern "C" {
 #define COAP_OPT_URI_PATH       (11)
 #define COAP_OPT_CONTENT_FORMAT (12)
 #define COAP_OPT_URI_QUERY      (15)
+#define COAP_OPT_BLOCK2         (23)
+#define COAP_OPT_BLOCK1         (27)
 /** @} */
 
 /**
@@ -211,6 +213,16 @@ extern "C" {
 #define COAP_DEFAULT_LEISURE    (5)
 /** @} */
 
+/**
+ * @name Blockwise transfer (RFC7959)
+ * @{
+ */
+#define COAP_BLOCKWISE_NUM_OFF  (4)
+#define COAP_BLOCKWISE_MORE_OFF (3)
+#define COAP_BLOCKWISE_SZX_MASK (0x07)
+#define COAP_BLOCKWISE_SZX_MAX  (7)
+/** @} */
+
 /**
  * @brief   Raw CoAP PDU header structure
  */
@@ -262,6 +274,17 @@ typedef struct {
     void *context;                  /**< ptr to user defined context data   */
 } coap_resource_t;
 
+/**
+ * @brief   Block1 helper struct
+ */
+typedef struct {
+    size_t offset;                  /**< offset of received data            */
+    uint32_t blknum;                /**< block number                       */
+    unsigned szx;                   /**< szx value                          */
+    int more;                       /**< -1 for no option, 0 for last block,
+                                          1 for more blocks coming          */
+} coap_block1_t;
+
 /**
  * @brief   Global CoAP resource list
  */
@@ -409,6 +432,71 @@ size_t coap_put_option_ct(uint8_t *buf, uint16_t lastonum, uint16_t content_type
  */
 size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uint16_t optnum);
 
+/**
+ * @brief    Generic block option getter
+ *
+ * @param[in]   pkt     pkt to work on
+ * @param[in]   option  actual block option number to get
+ * @param[out]  blknum  block number
+ * @param[out]  szx     SZX value
+ *
+ * @returns     -1 if option not found
+ * @returns     0 if more flag is not set
+ * @returns     1 if more flag is set
+ */
+int coap_get_blockopt(coap_pkt_t *pkt, uint16_t option, uint32_t *blknum, unsigned *szx);
+
+/**
+ * @brief    Block1 option getter
+ *
+ * This function gets a CoAP packet's block1 option and parses it into a helper
+ * structure.
+ *
+ * If no block1 option is present in @p pkt, the values in @p block1 will be
+ * initialized with zero. That implies both block1->offset and block1->more are
+ * also valid in that case, as packet with offset==0 and more==0 means it contains
+ * all the payload for the corresponding request.
+ *
+ * @param[in]   pkt     pkt to work on
+ * @param[out]  block1  ptr to preallocated coap_block1_t structure
+ *
+ * @returns     0 if block1 option not present
+ * @returns     1 if structure has been filled
+ */
+int coap_get_block1(coap_pkt_t *pkt, coap_block1_t *block1);
+
+/**
+ * @brief   Insert block1 option into buffer
+ *
+ * @param[out]  buf         buffer to write to
+ * @param[in]   lastonum    number of previous option (for delta calculation),
+ *                          must be < 27
+ * @param[in]   blknum      block number
+ * @param[in]   szx         SXZ value
+ * @param[in]   more        more flag (1 or 0)
+ *
+ * @returns     amount of bytes written to @p buf
+ */
+size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum, unsigned blknum, unsigned szx, int more);
+
+/**
+ * @brief   Insert block1 option into buffer (from coap_block1_t)
+ *
+ * This function is wrapper around @ref coap_put_option_block1(),
+ * taking its arguments from a coap_block1_t struct.
+ *
+ * It will write option Nr. 27 (COAP_OPT_BLOCK1).
+ *
+ * It is safe to be called when @p block1 was generated for a non-blockwise
+ * request.
+ *
+ * @param[in]   pkt_pos     buffer to write to
+ * @param[in]   block1      ptr to block1 struct (created by coap_get_block1())
+ * @param[in]   lastonum    last option number (must be < 27)
+ *
+ * @returns     amount of bytes written to @p pkt_pos
+ */
+size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum);
 
 /**
  * @brief   Get content type from packet
@@ -436,6 +524,18 @@ unsigned coap_get_content_type(coap_pkt_t *pkt);
  */
 int coap_get_uri(coap_pkt_t *pkt, uint8_t *target);
 
+/**
+ * @brief    Helper to decode SZX value to size in bytes
+ *
+ * @param[in]   szx     SZX value to decode
+ *
+ * @returns     SZX value decoded to bytes
+ */
+static inline unsigned coap_szx2size(unsigned szx)
+{
+    return (1 << (szx + 4));
+}
+
 /**
  * @brief   Get the CoAP version number
  *
diff --git a/sys/net/application_layer/nanocoap/nanocoap.c b/sys/net/application_layer/nanocoap/nanocoap.c
index 0f04395e3d1bcde9c3d4db24c26331c032185adc..e72bda605981b250c4304226e063405c8f4d944b 100644
--- a/sys/net/application_layer/nanocoap/nanocoap.c
+++ b/sys/net/application_layer/nanocoap/nanocoap.c
@@ -30,6 +30,8 @@
 
 static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end);
 int coap_get_option_uint(coap_pkt_t *pkt, unsigned opt_num, uint32_t *target);
+static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes);
+static size_t _encode_uint(uint32_t *val);
 
 /* http://tools.ietf.org/html/rfc7252#section-3
  *  0                   1                   2                   3
@@ -161,17 +163,6 @@ static uint8_t *_parse_option(coap_pkt_t *pkt, uint8_t *pkt_pos, uint16_t *delta
     return pkt_pos;
 }
 
-static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes)
-{
-    assert(nbytes <= 4);
-
-    uint32_t res = 0;
-    if (nbytes) {
-        memcpy(((uint8_t *)&res) + (4 - nbytes), pkt_pos, nbytes);
-    }
-    return ntohl(res);
-}
-
 int coap_get_option_uint(coap_pkt_t *pkt, unsigned opt_num, uint32_t *target)
 {
     assert(target);
@@ -265,6 +256,29 @@ int coap_get_uri(coap_pkt_t *pkt, uint8_t *target)
     return NANOCOAP_URI_MAX - left;
 }
 
+int coap_get_blockopt(coap_pkt_t *pkt, uint16_t option, uint32_t *blknum, unsigned *szx)
+{
+    uint8_t *optpos = coap_find_option(pkt, option);
+    if (!optpos) {
+        *blknum = 0;
+        *szx = 0;
+        return -1;
+    }
+
+    int option_len;
+    uint16_t delta;
+
+    uint8_t *data_start = _parse_option(pkt, optpos, &delta, &option_len);
+    uint32_t blkopt = _decode_uint(data_start, option_len);
+
+    DEBUG("nanocoap: blkopt len: %i\n", option_len);
+    DEBUG("nanocoap: blkopt: 0x%08x\n", (unsigned)blkopt);
+    *blknum = blkopt >> COAP_BLOCKWISE_NUM_OFF;
+    *szx = blkopt & COAP_BLOCKWISE_SZX_MASK;
+
+    return (blkopt & 0x8) ? 1 : 0;
+}
+
 ssize_t coap_handle_req(coap_pkt_t *pkt, uint8_t *resp_buf, unsigned resp_buf_len)
 {
     if (coap_get_code_class(pkt) != COAP_REQ) {
@@ -416,6 +430,43 @@ static int _decode_value(unsigned val, uint8_t **pkt_pos_ptr, uint8_t *pkt_end)
     return res;
 }
 
+static uint32_t _decode_uint(uint8_t *pkt_pos, unsigned nbytes)
+{
+    assert(nbytes <= 4);
+
+    uint32_t res = 0;
+    if (nbytes) {
+        memcpy(((uint8_t *)&res) + (4 - nbytes), pkt_pos, nbytes);
+    }
+    return ntohl(res);
+}
+
+static size_t _encode_uint(uint32_t *val)
+{
+    uint8_t *tgt = (uint8_t *)val;
+    size_t size = 0;
+
+    /* count number of used bytes */
+    uint32_t tmp = *val;
+    while(tmp) {
+        size++;
+        tmp >>= 8;
+    }
+
+    /* convert to network byte order */
+    tmp = htonl(*val);
+
+    /* copy bytewise, starting with first actually used byte */
+    *val = 0;
+    uint8_t *tmp_u8 = (uint8_t *)&tmp;
+    tmp_u8 += (4 - size);
+    for (unsigned n = 0; n < size; n++) {
+        *tgt++ = *tmp_u8++;
+    }
+
+    return size;
+}
+
 static unsigned _put_delta_optlen(uint8_t *buf, unsigned offset, unsigned shift, unsigned val)
 {
     if (val < 13) {
@@ -469,6 +520,46 @@ size_t coap_put_option_ct(uint8_t *buf, uint16_t lastonum, uint16_t content_type
     }
 }
 
+static size_t coap_put_option_block(uint8_t *buf, uint16_t lastonum, unsigned blknum, unsigned szx, int more, uint16_t option)
+{
+    uint32_t blkopt = (blknum << 4) | szx | (more ? 0x8 : 0);
+    size_t olen = _encode_uint(&blkopt);
+    return coap_put_option(buf, lastonum, option, (uint8_t*)&blkopt, olen);
+}
+
+size_t coap_put_option_block1(uint8_t *buf, uint16_t lastonum, unsigned blknum, unsigned szx, int more)
+{
+    return coap_put_option_block(buf, lastonum, blknum, szx, more, COAP_OPT_BLOCK1);
+}
+
+int coap_get_block1(coap_pkt_t *pkt, coap_block1_t *block1)
+{
+    uint32_t blknum;
+    unsigned szx;
+    block1->more = coap_get_blockopt(pkt, COAP_OPT_BLOCK1, &blknum, &szx);
+    if (block1->more >= 0) {
+        block1->offset = blknum << (szx + 4);
+    }
+    else {
+        block1->offset = 0;
+    }
+
+    block1->blknum = blknum;
+    block1->szx = szx;
+
+    return (block1->more >= 0);
+}
+
+size_t coap_put_block1_ok(uint8_t *pkt_pos, coap_block1_t *block1, uint16_t lastonum)
+{
+    if (block1->more >= 1) {
+        return coap_put_option_block1(pkt_pos, lastonum, block1->blknum, block1->szx, block1->more);
+    }
+    else {
+        return 0;
+    }
+}
+
 size_t coap_put_option_uri(uint8_t *buf, uint16_t lastonum, const char *uri, uint16_t optnum)
 {
     char separator = (optnum == COAP_OPT_URI_PATH) ? '/' : '&';