diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,10 +1,16 @@
-s3-signer [![Hackage](https://img.shields.io/hackage/v/s3-signer.svg?style=flat)](https://hackage.haskell.org/package/s3-signer)
+s3-signer
 ======
+![Hackage](https://img.shields.io/hackage/v/s3-signer.svg)
+![Hackage Dependencies](https://img.shields.io/hackage-deps/v/s3-signer.svg)
+![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)
+![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)
+![Build Status](https://api.travis-ci.org/dmjio/s3-signer.svg?branch=master)
+
 s3-signer is intended to be an aid in building secure cloud-based services with
 AWS. This library generates cryptographically secure URLs that
 expire at a user-defined interval. These URLs can be used to offload
 the process of uploading and downloading large files, freeing your
-webserver to focus on other things. 
+webserver to focus on other things.
 
 ### Features
  - Minimal depedencies
diff --git a/s3-signer.cabal b/s3-signer.cabal
--- a/s3-signer.cabal
+++ b/s3-signer.cabal
@@ -1,13 +1,13 @@
 name:                s3-signer
-version:             0.3.0.0
+version:             0.4.0.0
 homepage:            https://github.com/dmjio/s3-signer
 bug-reports:         https://github.com/dmjio/s3-signer/issues
 synopsis:            Pre-signed Amazon S3 URLs
-description:         
+description:
         .
-        s3-signer creates cryptographically secure Amazon S3 URLs that expire within a user-defined         
-        period. It allows uploading and downloading of content from Amazon S3. 
-        Ideal for AJAX direct-to-s3 uploads via CORS and secure downloads. 
+        s3-signer creates cryptographically secure Amazon S3 URLs that expire within a user-defined
+        period. It allows uploading and downloading of content from Amazon S3.
+        Ideal for AJAX direct-to-s3 uploads via CORS and secure downloads.
         Web framework agnostic with minimal dependencies.
         .
         > module Main where
@@ -17,25 +17,49 @@
         >   where
         >     credentials = S3Keys "<public-key-goes-here>" "<secret-key-goes-here>"
         >     request     = S3Request S3GET "application/extension" "bucket-name" "file-name.extension" 3 -- three seconds until expiration
-        . 
+        .
         Result
         .
         > S3URL "https://bucket-name.s3.amazonaws.com/file-name.extension?AWSAccessKeyId=<public-key-goes-here>&Expires=1402346638&Signature=1XraY%2Bhp117I5CTKNKPc6%2BiihRA%3D"
 
 license:             BSD3
 license-file:        LICENSE
-author:              David Johnson <djohnson.m@gmail.com>
+author:              David Johnson <djohnson.m@gmail.com>, William Casarin <jb55@jb55.com>
 maintainer:          David Johnson <djohnson.m@gmail.com>
-copyright:           2014 David Johnson
+copyright:           David Johnson (c) 2014-2018
 category:            AWS, Network
 build-type:          Simple
-cabal-version:       >=1.18
-extra-source-files:  README.md        
+cabal-version:       2.0
+extra-source-files:  README.md
+tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.1
 
+library internal
+  default-language:  Haskell2010
+  build-depends: base == 4.*
+               , base64-bytestring
+               , bytestring
+               , byteable
+               , utf8-string
+               , case-insensitive >= 1.2
+               , blaze-builder >= 0.4
+               , http-types
+               , cryptohash
+               , time
+
+  hs-source-dirs: src
+  exposed-modules: Network.S3
+                 , Network.S3.Sign
+                 , Network.S3.URL
+                 , Network.S3.Types
+
 library
   build-depends:       base == 4.*
                      , base64-bytestring
+                     , bytestring
+                     , byteable
                      , utf8-string
+                     , case-insensitive >= 1.2
+                     , blaze-builder >= 0.4
                      , http-types
                      , cryptohash
                      , time
@@ -47,6 +71,17 @@
                      , Network.S3.Time
                      , Network.S3.URL
                      , Network.S3.Types
+
+
+test-suite test-simple
+  default-language:  Haskell2010
+  type: exitcode-stdio-1.0
+  main-is: test/Test.hs
+  build-depends: internal
+               , base == 4.*
+               , bytestring
+               , blaze-builder >= 0.4
+               , time
 
 
 source-repository head
diff --git a/src/Network/S3.hs b/src/Network/S3.hs
--- a/src/Network/S3.hs
+++ b/src/Network/S3.hs
@@ -8,23 +8,15 @@
     , module Network.S3.Types
     ) where
 
+import           Data.ByteString (ByteString)
+import           Data.Time.Clock (getCurrentTime)
 import           Network.S3.Sign
-import           Network.S3.Time
 import           Network.S3.Types
-import           Network.S3.URL
 
--- | Generates a cryptographically secure URL that expires within a
--- user defined period
---
--- See README for use cases and examples: <https://github.com/dmjio/s3-signer/blob/master/README.md>
-generateS3URL :: S3Keys -- ^ Amazon S3 Keys
-              -> S3Request  -- ^ Amazon S3 Request information
-              -> IO S3URL -- ^ Generated URL
-generateS3URL S3Keys{..} S3Request{..} = do
-  time <- getExpirationTimeStamp secondsToExpire
-  let url = case s3method of
-              S3GET -> getURL bucketName objectName time
-              S3PUT -> putURL bucketName objectName time mimeType 
-      req = s3URL bucketName objectName publicKey time (sign secretKey url)
-  return (S3URL req)
 
+generateS3URL :: ByteString -- ^ Amazon S3 SecretAccessKey
+              -> S3Request -- ^ Amazon S3 Request information
+              -> IO S3SignedRequest -- ^ Generated Request
+generateS3URL secretKey req = do
+  time <- getCurrentTime
+  return (sign secretKey req time)
diff --git a/src/Network/S3/Sign.hs b/src/Network/S3/Sign.hs
--- a/src/Network/S3/Sign.hs
+++ b/src/Network/S3/Sign.hs
@@ -1,11 +1,71 @@
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
 module Network.S3.Sign  ( sign ) where
 
-import           Crypto.Hash.SHA1       (hash)
-import           Crypto.MAC.HMAC        (hmac)
-import qualified Data.ByteString.Base64 as B64
-import           Data.ByteString.UTF8   (ByteString)
-import           Network.HTTP.Types.URI (urlEncode)
+import qualified    Data.ByteString.Char8    as BS
+import qualified    Data.ByteString.Base64   as Base64
+import              Data.ByteString.UTF8     (ByteString)
+import              Blaze.ByteString.Builder (toByteString, fromByteString)
+import              Data.Monoid              ((<>))
+import              Data.Time.Clock          (UTCTime)
+import              Data.Time.Format         (formatTime, defaultTimeLocale)
+import              Network.S3.Types         (S3Request(..), S3SignedRequest(..))
+import              Network.S3.URL           (canonicalRequest)
+import              Data.Byteable            (toBytes)
+import              Crypto.Hash
 
+
+reqString :: S3Request -> Digest SHA256
+reqString = hash . toByteString . canonicalRequest
+
+hmacSHA256 :: ByteString -> ByteString -> HMAC SHA256
+hmacSHA256 key = hmac key
+
+hmac_ :: ByteString -> ByteString -> ByteString
+hmac_ key = toBytes . hmacGetDigest . hmacSHA256 key
+
+
 -- | SHA1 Encrypted Signature
-sign :: ByteString -> ByteString -> ByteString
-sign secretKey url = urlEncode True . B64.encode $ hmac hash 64 secretKey url
+sign :: ByteString -> S3Request -> UTCTime -> S3SignedRequest
+sign key req utc =
+  let
+      seconds   = BS.pack (formatTime defaultTimeLocale "T%M%H%SZ" utc)
+      date      = BS.pack (formatTime defaultTimeLocale "%Y%m%d" utc)
+
+      timestamp = fromByteString (date <> seconds)
+      reqHash   = fromByteString (digestToHexByteString (reqString req))
+      region    = regionName req
+      service   = "s3"
+
+      dateKey              = hmac_ ("AWS4" <> key) date
+      dateRegionKey        = hmac_ dateKey region
+      dateRegionServiceKey = hmac_ dateRegionKey service
+      signingKey           = hmac_ dateRegionServiceKey "aws4_request"
+
+      scope =
+        fromByteString date   <> fromByteString "/" <>
+        fromByteString region <> fromByteString "/" <>
+        fromByteString service <> fromByteString "/aws4_request"
+
+      algorithm = "AWS4-HMAC-SHA256"
+
+      signingStringBuilder =
+        algorithm <> "\n" <>
+        timestamp <> "\n" <>
+        scope     <> "\n" <>
+        reqHash
+
+      stringToSign = toByteString signingStringBuilder
+      signature    = hmacSHA256 signingKey stringToSign
+      hexSignature = digestToHexByteString (hmacGetDigest signature)
+  in
+    S3SignedRequest {
+        sigHeaders    = s3headers req
+      , sigDate       = toByteString timestamp
+      , sigCredential = toByteString scope
+      , sigPolicy     = Base64.encode stringToSign
+      , sigSignature  = hexSignature
+      , sigAlgorithm  = toByteString algorithm
+    }
diff --git a/src/Network/S3/Types.hs b/src/Network/S3/Types.hs
--- a/src/Network/S3/Types.hs
+++ b/src/Network/S3/Types.hs
@@ -1,32 +1,72 @@
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Network.S3.Types
-    ( S3URL (..)
-    , S3Keys (..)
-    , S3Method (..)
+    ( S3Method (..)
     , S3Request (..)
+    , S3SignedRequest (..)
+    , S3Header
+    , getS3Header
+    , s3Header
+    , s3HeaderBuilder
     ) where
 
-import           Data.ByteString.UTF8 (ByteString)
-import           GHC.Generics         (Generic)
+import           Data.ByteString.UTF8     (ByteString)
+import           GHC.Generics             (Generic)
+import           Data.Char                (isSpace)
+import           Network.HTTP.Types       (Query)
+import           Data.Time.Clock          (UTCTime)
 
-newtype S3URL = S3URL {
-      signedRequest :: ByteString -- ^ Generated URL
-    } deriving (Generic, Show)
+import           Blaze.ByteString.Builder (Builder, fromByteString)
+import           Data.Monoid              (mconcat)
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.CaseInsensitive as CI
 
-data S3Keys = S3Keys {
-      publicKey :: ByteString -- ^ AWS Public Key
-    , secretKey :: ByteString  -- ^ AWS Private Key
-    } deriving (Generic, Show)
 
-data S3Method = S3GET -- ^ GET Request
-              | S3PUT -- ^ PUT Request
+newtype S3Header = S3Header { getS3Header :: (ByteString, ByteString) }
+  deriving (Generic, Show)
+
+
+data S3Method = S3GET    -- ^ GET Request
+              | S3PUT    -- ^ PUT Request
+              | S3HEAD   -- ^ HEAD Request
+              | S3DELETE -- ^ DELETE Request
     deriving (Generic, Show)
 
+
 data S3Request = S3Request {
-      s3method        :: S3Method -- ^ Type of HTTP Method
-    , mimeType        :: ByteString -- ^ MIME Type
-    , bucketName      :: ByteString -- ^ Name of Amazon S3 Bucket
-    , objectName      :: ByteString -- ^ Name of Amazon S3 File
-    , secondsToExpire :: Integer -- ^ Number of seconds until expiration
+      s3method    :: S3Method         -- ^ Type of HTTP Method
+    , mimeType    :: Maybe ByteString -- ^ MIME Type
+    , bucketName  :: ByteString       -- ^ Name of Amazon S3 Bucket
+    , objectName  :: ByteString       -- ^ Name of Amazon S3 File
+    , regionName  :: ByteString       -- ^ Name of Amazon S3 Region
+    , queryString :: Query            -- ^ Optional query string items
+    , requestTime :: UTCTime          -- ^ Requests are valid within a 15 minute window of this timestamp
+    , payloadHash :: Maybe ByteString -- ^ SHA256 hash string of the payload
+    , s3headers   :: [S3Header]       -- ^ Headers
     } deriving (Generic, Show)
+
+
+data S3SignedRequest = S3SignedRequest {
+      sigHeaders    :: [S3Header] -- ^ The headers included in the signed request
+    , sigDate       :: ByteString -- ^ The date you used in creating the signing key.
+    , sigCredential :: ByteString -- ^ <your-access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request
+    , sigPolicy     :: ByteString -- ^ The Base64-encoded security policy that describes what is permitted in the request
+    , sigSignature  :: ByteString -- ^ (AWS Signature Version 4) The HMAC-SHA256 hash of the security policy.
+    , sigAlgorithm  :: ByteString -- ^ The signing algorithm used. For AWS Signature Version 4, the value is AWS4-HMAC-SHA256.
+    } deriving (Generic, Show)
+
+
+trim :: ByteString -> ByteString
+trim = BS.dropWhile isSpace . fst . BS.spanEnd isSpace
+
+s3Header :: ByteString -> ByteString -> S3Header
+s3Header header value = S3Header (lower, trimmed)
+  where
+    lower   = CI.foldCase header
+    trimmed = trim value
+
+s3HeaderBuilder :: S3Header -> Builder
+s3HeaderBuilder (S3Header (header,value)) =
+  mconcat [fromByteString header, ":", fromByteString value, "\n"]
+
diff --git a/src/Network/S3/URL.hs b/src/Network/S3/URL.hs
--- a/src/Network/S3/URL.hs
+++ b/src/Network/S3/URL.hs
@@ -1,55 +1,65 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 
 module Network.S3.URL
-    ( putURL
-    , getURL
-    , s3URL
+    ( canonicalRequest
     ) where
 
-import           Data.Monoid
-import           Data.String
+import           Data.Time.Format         (formatTime, defaultTimeLocale)
+import           Data.ByteString.Char8    (pack)
+import           Blaze.ByteString.Builder (Builder, fromByteString)
 
--- | S3 Upload URL Template
-putURL :: (Monoid m, IsString m) => m -> m -> m -> m -> m
-putURL bucket object expires mimetype =
-    mconcat [ "PUT\n\n"
-            , mimetype 
-            ,"\n"
-            , expires
-            , "\nx-amz-acl:public-read\n/"
-            , bucket
-            , "/"
-            , object
-            ]
+import qualified Network.HTTP.Types.URI as HTTP
+import           Data.Function (on)
+import           Data.List (sortBy, intersperse)
+import           Data.Monoid ((<>))
+import           Data.Maybe (fromMaybe)
 
--- | S3 Download URL Template
-getURL :: (Monoid m, IsString m) => m -> m -> m -> m
-getURL bucket object expires =
-    mconcat [ "GET\n\n\n"
-            , expires
-            , "\n/"
-            , bucket
-            , "/"
-            , object
-            ]
+import           Network.S3.Types
 
--- | Amazon S3 URL Template
-baseUrl :: (Monoid m, IsString m) => m -> m -> m
-baseUrl bucket object =
-    mconcat [ "https://"
-            , bucket
-            , ".s3.amazonaws.com/"
-            , object
-            ]
+sortS3Headers :: [S3Header] -> [S3Header]
+sortS3Headers = sortBy (compare `on` (fst . getS3Header))
 
-s3URL :: (Monoid m, IsString m) =>
-           m -> m -> m -> m -> m -> m
-s3URL bucket object publicKey expires sig
-    = mconcat [ baseUrl bucket object
-              , "?AWSAccessKeyId="
-              , publicKey
-              , "&Expires="
-              , expires
-              , "&Signature="
-              , sig
-              ]
+
+-- | Build a canonical request to be signed
+--   AWS docs: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#canonical-request
+canonicalRequest :: S3Request -> Builder
+canonicalRequest S3Request{..} =
+  let
+    -- We MUST sort the parameters in the query string alphabetically by key name
+    qs         = sortBy (compare `on` fst) queryString
+    emptyHash  = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+    bodyHash   = fromMaybe emptyHash payloadHash
+    hashHeader = s3Header "x-amz-content-sha256" bodyHash
+    hostHeader = s3Header "host" (bucketName <> ".s3.amazonaws.com")
+    seconds    = pack (formatTime defaultTimeLocale "T%M%H%SZ" requestTime)
+    date       = pack (formatTime defaultTimeLocale "%Y%m%d" requestTime)
+    timeHeader = s3Header "x-amz-date" (date <> seconds)
+    headers    = sortS3Headers (timeHeader : hostHeader : hashHeader : s3headers)
+    headerKeys = map (fst . getS3Header) headers
+
+    httpMethod       = renderS3Method s3method
+    canonicalURI     = HTTP.urlEncodeBuilder False objectName
+    canonicalQS      = HTTP.renderQueryText False (HTTP.queryToQueryText qs)
+    canonicalHeaders = foldMap s3HeaderBuilder headers
+    signedHeaders    = foldMap fromByteString (intersperse ";" headerKeys)
+    hashedPayload    = fromByteString bodyHash
+
+    uriBuilder =
+      httpMethod       <> "\n/" <>
+      canonicalURI     <> "\n" <>
+      canonicalQS      <> "\n" <>
+      canonicalHeaders <> "\n" <>
+      signedHeaders    <> "\n" <>
+      hashedPayload
+  in
+    uriBuilder
+
+
+renderS3Method :: S3Method -> Builder
+renderS3Method method =
+  case method of
+    S3GET    -> "GET"
+    S3PUT    -> "PUT"
+    S3HEAD   -> "HEAD"
+    S3DELETE -> "DELETE"
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+
+module Main where
+
+import Data.ByteString (ByteString)
+import Data.Time.Calendar (fromGregorian)
+import Data.Time.Clock (UTCTime(..))
+import Network.S3
+import Control.Monad (unless)
+import Network.S3.Sign (sign)
+import System.Exit (exitFailure)
+
+expectedSig :: ByteString
+expectedSig = "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41"
+
+testFail :: String -> IO ()
+testFail msg = do
+  print msg
+  exitFailure
+
+assertEqual :: (Show a, Eq a) => a -> a -> IO ()
+assertEqual got expected = do
+  let msg = "'" ++ show got ++ " does not match expected ''" ++ "'"
+  unless (got == expected) (testFail msg)
+
+main :: IO ()
+main = do
+  let time = UTCTime (fromGregorian 2013 5 24) 0
+      sr@S3SignedRequest{..} = sign privKey (exampleRequest time) time
+  assertEqual sigSignature expectedSig
+  print sr
+
+privKey :: ByteString
+privKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
+
+pubKey :: ByteString
+pubKey = "AKIAIOSFODNN7EXAMPLE"
+
+-- https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#example-signature-GET-object
+exampleRequest :: UTCTime -> S3Request
+exampleRequest time =
+  S3Request {
+      s3method    = S3GET
+    , mimeType    = Nothing
+    , bucketName  = "examplebucket"
+    , regionName  = "us-east-1"
+    , objectName  = "test.txt"
+    , queryString = []
+    , payloadHash = Nothing
+    , requestTime = time
+    , s3headers   = [ s3Header "range" "bytes=0-9" ]
+  }
+
+
