diff --git a/src/hmac.rs b/src/hmac.rs
index 601ae01..465781e 100644
--- a/src/hmac.rs
+++ b/src/hmac.rs
@@ -1,10 +1,12 @@
-use crate::cvt_p;
 use crate::error::ErrorStack;
 use crate::md::MdRef;
+use crate::{cvt, cvt_p};
+use ffi::HMAC_CTX;
 use foreign_types::ForeignTypeRef;
+use libc::{c_uint, c_void};
 use openssl_macros::corresponds;
-use libc::{c_void, c_uint};
 use std::convert::TryFrom;
+use std::ptr;
 
 /// Computes the HMAC as a one-shot operation.
 ///
@@ -20,8 +22,9 @@ pub fn hmac<'a>(
     md: &MdRef,
     key: &[u8],
     data: &[u8],
-    out: &'a mut [u8]
+    out: &'a mut [u8],
 ) -> Result<&'a [u8], ErrorStack> {
+    assert!(out.len() >= md.size());
     let mut out_len = c_uint::try_from(out.len()).unwrap();
     unsafe {
         cvt_p(ffi::HMAC(
@@ -31,38 +34,184 @@ pub fn hmac<'a>(
             data.as_ptr(),
             data.len(),
             out.as_mut_ptr(),
-            &mut out_len
-            ))?;
+            &mut out_len,
+        ))?;
     }
     Ok(&out[..out_len as usize])
 }
 
+/// A context object used to perform HMAC operations.
+///
+/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message
+/// authentication, which is based on a hash function.
+///
+/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead.
+#[cfg(boringssl)]
+pub struct HmacCtx {
+    ctx: *mut HMAC_CTX,
+    output_size: usize,
+}
+
+#[cfg(boringssl)]
+impl HmacCtx {
+    /// Creates a new [HmacCtx] to use the hash function `md` and key `key`.
+    #[corresponds(HMAC_Init_ex)]
+    pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> {
+        unsafe {
+            // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into
+            // ErrorStack in the returned result by `cvt_p`.
+            let ctx = cvt_p(ffi::HMAC_CTX_new())?;
+            // Safety:
+            // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new,
+            //   which is the line above.
+            // - HMAC_Init_ex may return an error if key is null but the md is different from
+            //   before. This is avoided here since key is guaranteed to be non-null.
+            cvt(ffi::HMAC_Init_ex(
+                ctx,
+                key.as_ptr() as *const c_void,
+                key.len(),
+                md.as_ptr(),
+                ptr::null_mut(),
+            ))?;
+            Ok(Self {
+                ctx,
+                output_size: md.size(),
+            })
+        }
+    }
+
+    /// `update` can be called repeatedly with chunks of the message `data` to be authenticated.
+    #[corresponds(HMAC_Update)]
+    pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
+        unsafe {
+            // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the
+            // returned result by `cvt`.
+            cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ())
+        }
+    }
+
+    /// Finishes the HMAC process, and places the message authentication code in `output`.
+    /// The number of bytes written to `output` is returned.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the `output` is smaller than the required size. The output size is indicated by
+    /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will
+    /// always be large enough.
+    #[corresponds(HMAC_Final)]
+    pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
+        assert!(output.len() >= self.output_size);
+        unsafe {
+            // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer
+            // than the length of `output`.
+            let mut size: c_uint = 0;
+            cvt(ffi::HMAC_Final(
+                self.ctx,
+                output.as_mut_ptr(),
+                &mut size as *mut c_uint,
+            ))
+            .map(|_| size as usize)
+        }
+    }
+}
+
+impl Drop for HmacCtx {
+    #[corresponds(HMAC_CTX_free)]
+    fn drop(&mut self) {
+        unsafe {
+            ffi::HMAC_CTX_free(self.ctx);
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::md::Md;
-    use crate::memcmp;
 
-    const SHA_256_DIGEST_SIZE:usize = 32;
+    const SHA_256_DIGEST_SIZE: usize = 32;
 
     #[test]
     fn hmac_sha256_test() {
-        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
+        let expected_hmac = [
+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
+            0x2e, 0x32, 0xcf, 0xf7,
+        ];
         let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-        let key:[u8; 20] = [0x0b; 20];
+        let key: [u8; 20] = [0x0b; 20];
         let data = b"Hi There";
-        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-        expect!(memcmp::eq(&hmac_result, &expected_hmac));
+        let hmac_result =
+            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
+        assert_eq!(&hmac_result, &expected_hmac);
+    }
+
+    #[test]
+    #[should_panic]
+    fn hmac_sha256_output_too_short() {
+        let mut out = vec![0_u8; 1];
+        let key: [u8; 20] = [0x0b; 20];
+        let data = b"Hi There";
+        hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
     }
 
     #[test]
     fn hmac_sha256_test_big_buffer() {
-        let expected_hmac = [0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7];
+        let expected_hmac = [
+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
+            0x2e, 0x32, 0xcf, 0xf7,
+        ];
         let mut out: [u8; 100] = [0; 100];
-        let key:[u8;20] = [0x0b; 20];
+        let key: [u8; 20] = [0x0b; 20];
+        let data = b"Hi There";
+        let hmac_result =
+            hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
+        assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
+        assert_eq!(&hmac_result, &expected_hmac);
+    }
+
+    #[test]
+    fn hmac_sha256_update_test() {
+        let expected_hmac = [
+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
+            0x2e, 0x32, 0xcf, 0xf7,
+        ];
+        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
+        let key: [u8; 20] = [0x0b; 20];
         let data = b"Hi There";
-        let hmac_result = hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-        expect_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
-        expect!(memcmp::eq(&hmac_result, &expected_hmac));
+        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
+        hmac_ctx.update(data).unwrap();
+        let size = hmac_ctx.finalize(&mut out).unwrap();
+        assert_eq!(&out, &expected_hmac);
+        assert_eq!(size, SHA_256_DIGEST_SIZE);
+    }
+
+    #[test]
+    fn hmac_sha256_update_chunks_test() {
+        let expected_hmac = [
+            0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
+            0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
+            0x2e, 0x32, 0xcf, 0xf7,
+        ];
+        let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
+        let key: [u8; 20] = [0x0b; 20];
+        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
+        hmac_ctx.update(b"Hi").unwrap();
+        hmac_ctx.update(b" There").unwrap();
+        let size = hmac_ctx.finalize(&mut out).unwrap();
+        assert_eq!(&out, &expected_hmac);
+        assert_eq!(size, SHA_256_DIGEST_SIZE);
+    }
+
+    #[test]
+    #[should_panic]
+    fn hmac_sha256_update_output_too_short() {
+        let mut out = vec![0_u8; 1];
+        let key: [u8; 20] = [0x0b; 20];
+        let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
+        hmac_ctx.update(b"Hi There").unwrap();
+        hmac_ctx.finalize(&mut out).unwrap();
     }
 }
