summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHerbertValerioRiedel <>2017-11-13 08:17:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2017-11-13 08:17:00 (GMT)
commit4c737801ac83345d6eb8f91bd2273e5b8d65edf0 (patch)
treed91d0f87f417263ae7f5918a73233c4206b4700a
parent56cb7fb09b0bbf2d471fa071f990e7da3ebb88f1 (diff)
version 0.11.101.0HEAD0.11.101.0master
-rw-r--r--cbits/hs_sha256.h (renamed from cbits/sha256.c)43
-rw-r--r--cbits/sha256.h47
-rw-r--r--changelog.md16
-rw-r--r--cryptohash-sha256.cabal98
-rw-r--r--src-exe/sha256sum.hs78
-rw-r--r--src-tests/test-sha256.hs41
-rw-r--r--src/Crypto/Hash/SHA256.hs180
-rw-r--r--src/Crypto/Hash/SHA256/FFI.hs61
8 files changed, 428 insertions, 136 deletions
diff --git a/cbits/sha256.c b/cbits/hs_sha256.h
index f9495fc..b936c7c 100644
--- a/cbits/sha256.c
+++ b/cbits/hs_sha256.h
@@ -23,12 +23,30 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "sha256.h"
+#ifndef HS_CRYPTOHASH_SHA256_H
+#define HS_CRYPTOHASH_SHA256_H
+#include <stdint.h>
+#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <ghcautoconf.h>
+struct sha256_ctx
+{
+ uint64_t sz;
+ uint8_t buf[64];
+ uint32_t h[8];
+};
+
+/* keep this synchronised with 'digestSize'/'sizeCtx' in SHA256.hs */
+#define SHA256_DIGEST_SIZE 32
+#define SHA256_CTX_SIZE 104
+
+static inline void hs_cryptohash_sha256_init (struct sha256_ctx *ctx);
+static inline void hs_cryptohash_sha256_update (struct sha256_ctx *ctx, const uint8_t *data, size_t len);
+static inline uint64_t hs_cryptohash_sha256_finalize (struct sha256_ctx *ctx, uint8_t *out);
+
#if defined(static_assert)
static_assert(sizeof(struct sha256_ctx) == SHA256_CTX_SIZE, "unexpected sha256_ctx size");
#else
@@ -81,7 +99,7 @@ cpu_to_be64(const uint64_t hll)
}
-void
+static inline void
hs_cryptohash_sha256_init (struct sha256_ctx *ctx)
{
memset(ctx, 0, SHA256_CTX_SIZE);
@@ -179,7 +197,7 @@ sha256_do_chunk(struct sha256_ctx *ctx, const uint8_t buf[])
sha256_do_chunk_aligned(ctx, w);
}
-void
+static inline void
hs_cryptohash_sha256_update(struct sha256_ctx *ctx, const uint8_t *data, size_t len)
{
size_t index = ctx->sz & 0x3f;
@@ -209,10 +227,11 @@ hs_cryptohash_sha256_update(struct sha256_ctx *ctx, const uint8_t *data, size_t
memcpy(ctx->buf + index, data, len);
}
-void
+static inline uint64_t
hs_cryptohash_sha256_finalize (struct sha256_ctx *ctx, uint8_t *out)
{
static const uint8_t padding[64] = { 0x80, };
+ const uint64_t sz = ctx->sz;
/* add padding and update data with it */
uint64_t bits = cpu_to_be64(ctx->sz << 3);
@@ -227,4 +246,20 @@ hs_cryptohash_sha256_finalize (struct sha256_ctx *ctx, uint8_t *out)
/* output hash */
cpu_to_be32_array((uint32_t *) out, ctx->h, 8);
+
+ return sz;
}
+
+static inline void
+hs_cryptohash_sha256_hash (const uint8_t *data, size_t len, uint8_t *out)
+{
+ struct sha256_ctx ctx;
+
+ hs_cryptohash_sha256_init(&ctx);
+
+ hs_cryptohash_sha256_update(&ctx, data, len);
+
+ hs_cryptohash_sha256_finalize(&ctx, out);
+}
+
+#endif
diff --git a/cbits/sha256.h b/cbits/sha256.h
deleted file mode 100644
index d909962..0000000
--- a/cbits/sha256.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2006-2009 Vincent Hanquez <vincent@snarc.org>
- * 2016 Herbert Valerio Riedel <hvr@gnu.org>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef CRYPTOHASH_SHA256_H
-#define CRYPTOHASH_SHA256_H
-
-#include <stdint.h>
-#include <stddef.h>
-
-struct sha256_ctx
-{
- uint64_t sz;
- uint8_t buf[64];
- uint32_t h[8];
-};
-
-/* keep this synchronised with 'digestSize'/'sizeCtx' in SHA256.hs */
-#define SHA256_DIGEST_SIZE 32
-#define SHA256_CTX_SIZE 104
-
-void hs_cryptohash_sha256_init (struct sha256_ctx *ctx);
-void hs_cryptohash_sha256_update (struct sha256_ctx *ctx, const uint8_t *data, size_t len);
-void hs_cryptohash_sha256_finalize (struct sha256_ctx *ctx, uint8_t *out);
-
-#endif
diff --git a/changelog.md b/changelog.md
index ea705ec..51b80a0 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,17 @@
+## 0.11.101.0
+
+ - Add `hkdf` function providing HKDF-SHA256 conforming to RFC5869
+ - Declare `Crypto.Hash.SHA256` module `-XTrustworthy`
+ - Remove ineffective RULES
+ - Convert to `CApiFFI`
+ - Added `...AndLength` variants of hashing functions:
+
+ - `finalizeAndLength`
+ - `hashlazyAndLength`
+ - `hmaclazyAndLength`
+
+ - Minor optimizations in `hmac` and `hash`
+
## 0.11.100.1
- Use `__builtin_bswap{32,64}` only with GCC >= 4.3
@@ -5,7 +19,7 @@
## 0.11.100.0
- - new `hmac` and `hmaclazy` functions providing HMAC-SHA1
+ - new `hmac` and `hmaclazy` functions providing HMAC-SHA256
computation conforming to RFC2104 and RFC4231
- fix unaligned memory-accesses
diff --git a/cryptohash-sha256.cabal b/cryptohash-sha256.cabal
index 2fe10a2..6cfc4d8 100644
--- a/cryptohash-sha256.cabal
+++ b/cryptohash-sha256.cabal
@@ -1,20 +1,44 @@
+cabal-version: 1.12
name: cryptohash-sha256
-version: 0.11.100.1
-description:
- A practical incremental and one-pass, pure API to the
- <https://en.wikipedia.org/wiki/SHA-2 SHA-256 hash algorithm>
- (including <https://en.wikipedia.org/wiki/HMAC HMAC> support)
- with performance close to the fastest implementations available in other languages.
- .
- The implementation is made in C with a haskell FFI wrapper that hides the C implementation.
- .
- NOTE: This package has been forked off @cryptohash-0.11.7@ because the @cryptohash@ package has been
- deprecated and so this package continues to satisfy the need for a lightweight package
- providing the SHA256 hash algorithm without any dependencies on packages other than
- @base@ and @bytestring@.
- .
- Consequently, this package can be used as a drop-in replacement for @cryptohash@'s
- "Crypto.Hash.SHA256" module, though with a clearly smaller footprint.
+version: 0.11.101.0
+
+synopsis: Fast, pure and practical SHA-256 implementation
+description: {
+
+A practical incremental and one-pass, pure API to
+the [SHA-256 cryptographic hash algorithm](https://en.wikipedia.org/wiki/SHA-2) according
+to [FIPS 180-4](http://dx.doi.org/10.6028/NIST.FIPS.180-4)
+with performance close to the fastest implementations available in other languages.
+.
+The core SHA-256 algorithm is implemented in C and is thus expected
+to be as fast as the standard [sha256sum(1) tool](https://linux.die.net/man/1/sha256sum);
+for instance, on an /Intel Core i7-3770/ at 3.40GHz this implementation can
+compute a SHA-256 hash over 230 MiB of data in under one second.
+(If, instead, you require a pure Haskell implementation and performance is secondary, please refer to the [SHA package](https://hackage.haskell.org/package/SHA).)
+.
+
+.
+Additionally, this package provides support for
+.
+- HMAC-SHA-256: SHA-256-based [Hashed Message Authentication Codes](https://en.wikipedia.org/wiki/HMAC) (HMAC)
+- HKDF-SHA-256: [HMAC-SHA-256-based Key Derivation Function](https://en.wikipedia.org/wiki/HKDF) (HKDF)
+.
+conforming to [RFC6234](https://tools.ietf.org/html/rfc6234), [RFC4231](https://tools.ietf.org/html/rfc4231), [RFC5869](https://tools.ietf.org/html/rfc5869), et al..
+.
+=== Relationship to the @cryptohash@ package and its API
+.
+This package has been originally a fork of @cryptohash-0.11.7@ because the @cryptohash@
+package had been deprecated and so this package continues to satisfy the need for a
+lightweight package providing the SHA-256 hash algorithm without any dependencies on packages
+other than @base@ and @bytestring@. The API exposed by @cryptohash-sha256-0.11.*@'s
+"Crypto.Hash.SHA256" module is guaranteed to remain a compatible superset of the API provided
+by the @cryptohash-0.11.7@'s module of the same name.
+.
+Consequently, this package is designed to be used as a drop-in replacement for @cryptohash-0.11.7@'s
+"Crypto.Hash.SHA256" module, though with
+a [clearly smaller footprint by almost 3 orders of magnitude](https://www.reddit.com/r/haskell/comments/5lxv75/psa_please_use_unique_module_names_when_uploading/dbzegx3/).
+
+}
license: BSD3
license-file: LICENSE
@@ -22,37 +46,62 @@ copyright: Vincent Hanquez, Herbert Valerio Riedel
maintainer: Herbert Valerio Riedel <hvr@gnu.org>
homepage: https://github.com/hvr/cryptohash-sha256
bug-reports: https://github.com/hvr/cryptohash-sha256/issues
-synopsis: Fast, pure and practical SHA-256 implementation
category: Data, Cryptography
build-type: Simple
-cabal-version: >=1.10
tested-with: GHC == 7.4.2
, GHC == 7.6.3
, GHC == 7.8.4
, GHC == 7.10.3
- , GHC == 8.0.1
+ , GHC == 8.0.2
+ , GHC == 8.2.1
-extra-source-files: cbits/sha256.h
+extra-source-files: cbits/hs_sha256.h
changelog.md
source-repository head
type: git
location: https://github.com/hvr/cryptohash-sha256.git
+flag exe
+ description: Enable building @sha256sum@ executable
+ manual: True
+ default: False
+
library
default-language: Haskell2010
- build-depends: base >= 4.5 && < 4.10
+ other-extensions: BangPatterns
+ CApiFFI
+ Trustworthy
+ Unsafe
+
+ build-depends: base >= 4.5 && < 4.11
, bytestring >= 0.9.2 && < 0.11
+ ghc-options: -Wall
+
hs-source-dirs: src
exposed-modules: Crypto.Hash.SHA256
- ghc-options: -Wall -fno-cse -O2
- cc-options: -Wall -O3
- c-sources: cbits/sha256.c
+ other-modules: Crypto.Hash.SHA256.FFI
include-dirs: cbits
+executable sha256sum
+ hs-source-dirs: src-exe
+ main-is: sha256sum.hs
+ ghc-options: -Wall -threaded
+ if flag(exe)
+ default-language: Haskell2010
+ other-extensions: RecordWildCards
+ build-depends: cryptohash-sha256
+ , base
+ , bytestring
+
+ , base16-bytestring >= 0.1.1 && < 0.2
+ else
+ buildable: False
+
test-suite test-sha256
default-language: Haskell2010
+ other-extensions: OverloadedStrings
type: exitcode-stdio-1.0
hs-source-dirs: src-tests
main-is: test-sha256.hs
@@ -69,6 +118,7 @@ test-suite test-sha256
benchmark bench-sha256
default-language: Haskell2010
+ other-extensions: BangPatterns
type: exitcode-stdio-1.0
main-is: bench-sha256.hs
hs-source-dirs: src-bench
diff --git a/src-exe/sha256sum.hs b/src-exe/sha256sum.hs
new file mode 100644
index 0000000..e0a353a
--- /dev/null
+++ b/src-exe/sha256sum.hs
@@ -0,0 +1,78 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module Main where
+
+import Control.Monad
+import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy as BL
+import System.Console.GetOpt
+import System.Environment
+import System.Exit
+import System.IO
+
+import qualified Crypto.Hash.SHA256 as H
+
+
+data Options = Options
+ { optBinary :: Bool
+ , optHelp :: Bool
+ , optTag :: Bool
+ } deriving Show
+
+defOptions :: Options
+defOptions = Options
+ { optBinary = True
+ , optHelp = False
+ , optTag = False
+ }
+
+options :: [OptDescr (Options -> Options)]
+options = [ Option ['b'] ["binary"]
+ (NoArg (\o -> o { optBinary = True}))
+ "read in binary mode (default)"
+ , Option ['t'] ["text"]
+ (NoArg (\o -> o { optBinary = False}))
+ "read in text mode (ignored)"
+ , Option [] ["help"]
+ (NoArg (\o -> o { optHelp = True}))
+ "display help and exit"
+ , Option [] ["tag"]
+ (NoArg (\o -> o { optTag = True}))
+ "create a BSD-style checksum"
+ ]
+
+main :: IO ()
+main = do
+ argv <- getArgs
+
+ let Options{..} = foldl (flip id) defOptions optset
+ (optset,args0,cliErr) = getOpt Permute options argv
+ args | null args0 = ["-"]
+ | otherwise = args0
+
+ unless (null cliErr) $ do
+ hPutStrLn stderr ("sha256sum: " ++ head cliErr ++ "Try 'sha256sum --help' for more information.")
+ exitFailure
+
+ when optHelp $ do
+ putStrLn (usageInfo "Usage: sha256sum [OPTION]... [FILE]...\nPrint or check SHA-256 hashes\n" options)
+ exitSuccess
+
+ forM_ args $ \fn -> do
+ h <- (B16.encode . H.hashlazy) `fmap` bReadFile fn
+
+ case optTag of
+ False -> do
+ B.hPutStr stdout h
+ hPutStrLn stdout (' ':' ':fn)
+ True -> do
+ hPutStrLn stdout $ concat [ "SHA256 (", fn, ") = ", B.unpack h ]
+
+ return ()
+
+bReadFile :: FilePath -> IO BL.ByteString
+bReadFile "-" = do
+ clsd <- hIsClosed stdin
+ if clsd then return BL.empty else BL.getContents
+bReadFile fn = BL.readFile fn
diff --git a/src-tests/test-sha256.hs b/src-tests/test-sha256.hs
index 75b577f..0750dce 100644
--- a/src-tests/test-sha256.hs
+++ b/src-tests/test-sha256.hs
@@ -4,8 +4,9 @@ module Main (main) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString.Lazy as BL
+import Data.Word
-- reference implementation
import qualified Data.Digest.Pure.SHA as REF
@@ -124,6 +125,29 @@ rfc4231Tests = zipWith makeTest [1::Int ..] rfc4231Vectors
hex = B16.encode
+rfc5869Vectors :: [(Int,ByteString,ByteString,ByteString,ByteString)]
+rfc5869Vectors = -- (l,ikm,salt,info,okm)
+ [ (42, rep 22 0x0b, x"000102030405060708090a0b0c", x"f0f1f2f3f4f5f6f7f8f9", x"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
+ , ( 82
+ , x"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
+ , x"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+ , x"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+ , x"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"
+ )
+ , ( 42, rep 22 0x0b, "", "", x"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8")
+ ]
+ where
+ x = fst.B16.decode
+ rep n c = B.replicate n c
+
+rfc5869Tests :: [TestTree]
+rfc5869Tests = zipWith makeTest [1::Int ..] rfc5869Vectors
+ where
+ makeTest i (l,ikm,salt,info,okm) = testGroup ("vec"++show i) $
+ [ testCase "hkdf" (hex okm @=? hex (IUT.hkdf ikm salt info l)) ]
+
+ hex = B16.encode
+
-- define own 'foldl' here to avoid RULE rewriting to 'hashlazy'
myfoldl' :: (b -> a -> b) -> b -> [a] -> b
myfoldl' f z0 xs0 = lgo z0 xs0
@@ -155,8 +179,10 @@ refImplTests :: [TestTree]
refImplTests =
[ testProperty "hash" prop_hash
, testProperty "hashlazy" prop_hashlazy
+ , testProperty "hashlazyAndLength" prop_hashlazyAndLength
, testProperty "hmac" prop_hmac
, testProperty "hmaclazy" prop_hmaclazy
+ , testProperty "hmaclazyAndLength" prop_hmaclazyAndLength
]
where
prop_hash (RandBS bs)
@@ -165,24 +191,36 @@ refImplTests =
prop_hashlazy (RandLBS bs)
= ref_hashlazy bs == IUT.hashlazy bs
+ prop_hashlazyAndLength (RandLBS bs)
+ = ref_hashlazyAndLength bs == IUT.hashlazyAndLength bs
+
prop_hmac (RandBS k) (RandBS bs)
= ref_hmac k bs == IUT.hmac k bs
prop_hmaclazy (RandBS k) (RandLBS bs)
= ref_hmaclazy k bs == IUT.hmaclazy k bs
+ prop_hmaclazyAndLength (RandBS k) (RandLBS bs)
+ = ref_hmaclazyAndLength k bs == IUT.hmaclazyAndLength k bs
+
ref_hash :: ByteString -> ByteString
ref_hash = ref_hashlazy . fromStrict
ref_hashlazy :: BL.ByteString -> ByteString
ref_hashlazy = toStrict . REF.bytestringDigest . REF.sha256
+ ref_hashlazyAndLength :: BL.ByteString -> (ByteString,Word64)
+ ref_hashlazyAndLength x = (ref_hashlazy x, fromIntegral (BL.length x))
+
ref_hmac :: ByteString -> ByteString -> ByteString
ref_hmac secret = ref_hmaclazy secret . fromStrict
ref_hmaclazy :: ByteString -> BL.ByteString -> ByteString
ref_hmaclazy secret = toStrict . REF.bytestringDigest . REF.hmacSha256 (fromStrict secret)
+ ref_hmaclazyAndLength :: ByteString -> BL.ByteString -> (ByteString,Word64)
+ ref_hmaclazyAndLength secret msg = (ref_hmaclazy secret msg, fromIntegral (BL.length msg))
+
-- toStrict/fromStrict only available with bytestring-0.10 and later
toStrict = B.concat . BL.toChunks
fromStrict = BL.fromChunks . (:[])
@@ -191,5 +229,6 @@ main :: IO ()
main = defaultMain $ testGroup "cryptohash-sha256"
[ testGroup "KATs" katTests
, testGroup "RFC4231" rfc4231Tests
+ , testGroup "RFC5869" rfc5869Tests
, testGroup "REF" refImplTests
]
diff --git a/src/Crypto/Hash/SHA256.hs b/src/Crypto/Hash/SHA256.hs
index 8a5ced5..cd56d1d 100644
--- a/src/Crypto/Hash/SHA256.hs
+++ b/src/Crypto/Hash/SHA256.hs
@@ -1,9 +1,11 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE Trustworthy #-}
+
-- |
-- Module : Crypto.Hash.SHA256
--- License : BSD-style
+-- License : BSD-3
-- Maintainer : Herbert Valerio Riedel <hvr@gnu.org>
-- Stability : stable
--- Portability : unknown
--
-- A module containing <https://en.wikipedia.org/wiki/SHA-2 SHA-256> bindings
--
@@ -40,6 +42,7 @@ module Crypto.Hash.SHA256
, update -- :: Ctx -> ByteString -> Ctx
, updates -- :: Ctx -> [ByteString] -> Ctx
, finalize -- :: Ctx -> ByteString
+ , finalizeAndLength -- :: Ctx -> (ByteString,Word64)
-- * Single Pass API
--
@@ -49,6 +52,7 @@ module Crypto.Hash.SHA256
--
-- - 'hash': create a digest ('init' + 'update' + 'finalize') from a strict 'ByteString'
-- - 'hashlazy': create a digest ('init' + 'update' + 'finalize') from a lazy 'L.ByteString'
+ -- - 'hashlazyAndLength': create a digest ('init' + 'update' + 'finalizeAndLength') from a lazy 'L.ByteString'
--
-- Example:
--
@@ -64,6 +68,7 @@ module Crypto.Hash.SHA256
, hash -- :: ByteString -> ByteString
, hashlazy -- :: L.ByteString -> ByteString
+ , hashlazyAndLength -- :: L.ByteString -> (ByteString,Int64)
-- ** HMAC-SHA-256
--
@@ -72,21 +77,33 @@ module Crypto.Hash.SHA256
, hmac -- :: ByteString -> ByteString -> ByteString
, hmaclazy -- :: ByteString -> L.ByteString -> ByteString
+ , hmaclazyAndLength -- :: ByteString -> L.ByteString -> (ByteString,Word64)
+
+ -- ** HKDF-SHA-256
+ --
+ -- | <https://tools.ietf.org/html/rfc5869 RFC5869>-compatible
+ -- <https://en.wikipedia.org/wiki/HKDF HKDF>-SHA-256 key derivation function
+
+ , hkdf
) where
-import Prelude hiding (init)
-import Foreign.C.Types
-import Foreign.Ptr
-import Foreign.ForeignPtr (withForeignPtr)
-import Foreign.Marshal.Alloc
-import qualified Data.ByteString.Lazy as L
-import qualified Data.ByteString as B
-import Data.ByteString (ByteString)
-import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
-import Data.ByteString.Internal (create, toForeignPtr, memcpy)
-import Data.Bits (xor)
-import Data.Word
-import System.IO.Unsafe (unsafeDupablePerformIO)
+import Data.Bits (xor)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import Data.ByteString.Internal (ByteString (PS), create,
+ createAndTrim, mallocByteString,
+ memcpy, toForeignPtr)
+import qualified Data.ByteString.Lazy as L
+import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
+import Data.Word
+import Foreign.C.Types
+import Foreign.ForeignPtr (withForeignPtr)
+import Foreign.Marshal.Alloc
+import Foreign.Ptr
+import Prelude hiding (init)
+import System.IO.Unsafe (unsafeDupablePerformIO)
+
+import Crypto.Hash.SHA256.FFI
-- | perform IO for hashes that do allocation and ffi.
-- unsafeDupablePerformIO is used when possible as the
@@ -96,22 +113,6 @@ import System.IO.Unsafe (unsafeDupablePerformIO)
unsafeDoIO :: IO a -> a
unsafeDoIO = unsafeDupablePerformIO
--- | SHA-256 Context
---
--- The context data is exactly 104 bytes long, however
--- the data in the context is stored in host-endianness.
---
--- The context data is made up of
---
--- * a 'Word64' representing the number of bytes already feed to hash algorithm so far,
---
--- * a 64-element 'Word8' buffer holding partial input-chunks, and finally
---
--- * a 8-element 'Word32' array holding the current work-in-progress digest-value.
---
--- Consequently, a SHA-256 digest as produced by 'hash', 'hashlazy', or 'finalize' is 32 bytes long.
-newtype Ctx = Ctx ByteString
-
-- keep this synchronised with cbits/sha256.h
{-# INLINE digestSize #-}
digestSize :: Int
@@ -121,18 +122,21 @@ digestSize = 32
sizeCtx :: Int
sizeCtx = 104
-{-# RULES "digestSize" B.length (finalize init) = digestSize #-}
-{-# RULES "hash" forall b. finalize (update init b) = hash b #-}
-{-# RULES "hash.list1" forall b. finalize (updates init [b]) = hash b #-}
-{-# RULES "hashmany" forall b. finalize (foldl update init b) = hashlazy (L.fromChunks b) #-}
-{-# RULES "hashlazy" forall b. finalize (foldl update init $ L.toChunks b) = hashlazy b #-}
-
{-# INLINE withByteStringPtr #-}
withByteStringPtr :: ByteString -> (Ptr Word8 -> IO a) -> IO a
withByteStringPtr b f =
withForeignPtr fptr $ \ptr -> f (ptr `plusPtr` off)
where (fptr, off, _) = toForeignPtr b
+{-# INLINE create' #-}
+-- | Variant of 'create' which allows to return an argument
+create' :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString,a)
+create' l f = do
+ fp <- mallocByteString l
+ x <- withForeignPtr fp $ \p -> f p
+ let bs = PS fp 0 l
+ return $! x `seq` bs `seq` (bs,x)
+
copyCtx :: Ptr Ctx -> Ptr Ctx -> IO ()
copyCtx dst src = memcpy (castPtr dst) (castPtr src) (fromIntegral sizeCtx)
@@ -157,23 +161,17 @@ withCtxNew f = Ctx `fmap` create sizeCtx (f . castPtr)
withCtxNewThrow :: (Ptr Ctx -> IO a) -> IO a
withCtxNewThrow f = allocaBytes sizeCtx (f . castPtr)
-foreign import ccall unsafe "sha256.h hs_cryptohash_sha256_init"
- c_sha256_init :: Ptr Ctx -> IO ()
-
-foreign import ccall unsafe "sha256.h hs_cryptohash_sha256_update"
- c_sha256_update_unsafe :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
-
-foreign import ccall safe "sha256.h hs_cryptohash_sha256_update"
- c_sha256_update_safe :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
-
-- 'safe' call overhead neglible for 4KiB and more
c_sha256_update :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
c_sha256_update pctx pbuf sz
| sz < 4096 = c_sha256_update_unsafe pctx pbuf sz
| otherwise = c_sha256_update_safe pctx pbuf sz
-foreign import ccall unsafe "sha256.h hs_cryptohash_sha256_finalize"
- c_sha256_finalize :: Ptr Ctx -> Ptr Word8 -> IO ()
+-- 'safe' call overhead neglible for 4KiB and more
+c_sha256_hash :: Ptr Word8 -> CSize -> Ptr Word8 -> IO ()
+c_sha256_hash pbuf sz pout
+ | sz < 4096 = c_sha256_hash_unsafe pbuf sz pout
+ | otherwise = c_sha256_hash_safe pbuf sz pout
updateInternalIO :: Ptr Ctx -> ByteString -> IO ()
updateInternalIO ptr d =
@@ -182,10 +180,14 @@ updateInternalIO ptr d =
finalizeInternalIO :: Ptr Ctx -> IO ByteString
finalizeInternalIO ptr = create digestSize (c_sha256_finalize ptr)
+finalizeInternalIO' :: Ptr Ctx -> IO (ByteString,Word64)
+finalizeInternalIO' ptr = create' digestSize (c_sha256_finalize_len ptr)
+
+
{-# NOINLINE init #-}
-- | create a new hash context
init :: Ctx
-init = unsafeDoIO $ withCtxNew $ c_sha256_init
+init = unsafeDoIO $ withCtxNew c_sha256_init
validCtx :: Ctx -> Bool
validCtx (Ctx b) = B.length b == sizeCtx
@@ -211,28 +213,44 @@ finalize ctx
| validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO
| otherwise = error "SHA256.finalize: invalid Ctx"
+{-# NOINLINE finalizeAndLength #-}
+-- | Variant of 'finalize' also returning length of hashed content
+--
+-- @since 0.11.101.0
+finalizeAndLength :: Ctx -> (ByteString,Word64)
+finalizeAndLength ctx
+ | validCtx ctx = unsafeDoIO $ withCtxThrow ctx finalizeInternalIO'
+ | otherwise = error "SHA256.finalize: invalid Ctx"
+
{-# NOINLINE hash #-}
-- | hash a strict bytestring into a digest bytestring (32 bytes)
hash :: ByteString -> ByteString
-hash d = unsafeDoIO $ withCtxNewThrow $ \ptr -> do
- c_sha256_init ptr >> updateInternalIO ptr d >> finalizeInternalIO ptr
+-- hash d = unsafeDoIO $ withCtxNewThrow $ \ptr -> c_sha256_init ptr >> updateInternalIO ptr d >> finalizeInternalIO ptr
+hash d = unsafeDoIO $ unsafeUseAsCStringLen d $ \(cs, len) -> create digestSize (c_sha256_hash (castPtr cs) (fromIntegral len))
{-# NOINLINE hashlazy #-}
-- | hash a lazy bytestring into a digest bytestring (32 bytes)
hashlazy :: L.ByteString -> ByteString
-hashlazy l = unsafeDoIO $ withCtxNewThrow $ \ptr -> do
+hashlazy l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO ptr
+{-# NOINLINE hashlazyAndLength #-}
+-- | Variant of 'hashlazy' which simultaneously computes the hash and length of a lazy bytestring.
+--
+-- @since 0.11.101.0
+hashlazyAndLength :: L.ByteString -> (ByteString,Word64)
+hashlazyAndLength l = unsafeDoIO $ withCtxNewThrow $ \ptr ->
+ c_sha256_init ptr >> mapM_ (updateInternalIO ptr) (L.toChunks l) >> finalizeInternalIO' ptr
+
-{-# NOINLINE hmac #-}
-- | Compute 32-byte <https://tools.ietf.org/html/rfc2104 RFC2104>-compatible
--- HMAC-SHA1 digest for a strict bytestring message
+-- HMAC-SHA-256 digest for a strict bytestring message
--
-- @since 0.11.100.0
hmac :: ByteString -- ^ secret
-> ByteString -- ^ message
- -> ByteString
-hmac secret msg = hash $ B.append opad (hash $ B.append ipad msg)
+ -> ByteString -- ^ digest (32 bytes)
+hmac secret msg = hash $ B.append opad (hashlazy $ L.fromChunks [ipad,msg])
where
opad = B.map (xor 0x5c) k'
ipad = B.map (xor 0x36) k'
@@ -242,14 +260,13 @@ hmac secret msg = hash $ B.append opad (hash $ B.append ipad msg)
pad = B.replicate (64 - B.length kt) 0
-{-# NOINLINE hmaclazy #-}
-- | Compute 32-byte <https://tools.ietf.org/html/rfc2104 RFC2104>-compatible
--- HMAC-SHA1 digest for a lazy bytestring message
+-- HMAC-SHA-256 digest for a lazy bytestring message
--
-- @since 0.11.100.0
hmaclazy :: ByteString -- ^ secret
-> L.ByteString -- ^ message
- -> ByteString
+ -> ByteString -- ^ digest (32 bytes)
hmaclazy secret msg = hash $ B.append opad (hashlazy $ L.append ipad msg)
where
opad = B.map (xor 0x5c) k'
@@ -258,3 +275,48 @@ hmaclazy secret msg = hash $ B.append opad (hashlazy $ L.append ipad msg)
k' = B.append kt pad
kt = if B.length secret > 64 then hash secret else secret
pad = B.replicate (64 - B.length kt) 0
+
+
+-- | Variant of 'hmaclazy' which also returns length of message
+--
+-- @since 0.11.101.0
+hmaclazyAndLength :: ByteString -- ^ secret
+ -> L.ByteString -- ^ message
+ -> (ByteString,Word64) -- ^ digest (32 bytes) and length of message
+hmaclazyAndLength secret msg =
+ (hash (B.append opad htmp), sz' - fromIntegral ipadLen)
+ where
+ (htmp, sz') = hashlazyAndLength (L.append ipad msg)
+
+ opad = B.map (xor 0x5c) k'
+ ipad = L.fromChunks [B.map (xor 0x36) k']
+ ipadLen = B.length k'
+
+ k' = B.append kt pad
+ kt = if B.length secret > 64 then hash secret else secret
+ pad = B.replicate (64 - B.length kt) 0
+
+{-# NOINLINE hkdf #-}
+-- | <https://tools.ietf.org/html/rfc6234 RFC6234>-compatible
+-- HKDF-SHA-256 key derivation function.
+--
+-- @since 0.11.101.0
+hkdf :: ByteString -- ^ /IKM/ Input keying material
+ -> ByteString -- ^ /salt/ Optional salt value, a non-secret random value (can be @""@)
+ -> ByteString -- ^ /info/ Optional context and application specific information (can be @""@)
+ -> Int -- ^ /L/ length of output keying material in octets (at most 255*32 bytes)
+ -> ByteString -- ^ /OKM/ Output keying material (/L/ bytes)
+hkdf ikm salt info l
+ | l == 0 = B.empty
+ | 0 > l || l > 255*32 = error "hkdf: invalid L parameter"
+ | otherwise = unsafeDoIO $ createAndTrim (32*fromIntegral cnt) (go 0 B.empty)
+ where
+ prk = hmac salt ikm
+ cnt = fromIntegral ((l+31) `div` 32) :: Word8
+
+ go :: Word8 -> ByteString -> Ptr Word8 -> IO Int
+ go !i t !p | i == cnt = return l
+ | otherwise = do
+ let t' = hmaclazy prk (L.fromChunks [t,info,B.singleton (i+1)])
+ withByteStringPtr t' $ \tptr' -> memcpy p tptr' 32
+ go (i+1) t' (p `plusPtr` 32)
diff --git a/src/Crypto/Hash/SHA256/FFI.hs b/src/Crypto/Hash/SHA256/FFI.hs
new file mode 100644
index 0000000..a11bb5a
--- /dev/null
+++ b/src/Crypto/Hash/SHA256/FFI.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE CApiFFI #-}
+{-# LANGUAGE Unsafe #-}
+
+-- Ugly hack to workaround https://ghc.haskell.org/trac/ghc/ticket/14452
+{-# OPTIONS_GHC -O0
+ -fdo-lambda-eta-expansion
+ -fcase-merge
+ -fstrictness
+ -fno-omit-interface-pragmas
+ -fno-ignore-interface-pragmas #-}
+
+{-# OPTIONS_GHC -optc-Wall -optc-O3 #-}
+
+-- |
+-- Module : Crypto.Hash.SHA256.FFI
+-- License : BSD-3
+-- Maintainer : Herbert Valerio Riedel <hvr@gnu.org>
+--
+module Crypto.Hash.SHA256.FFI where
+
+import Data.ByteString (ByteString)
+import Data.Word
+import Foreign.C.Types
+import Foreign.Ptr
+
+-- | SHA-256 Context
+--
+-- The context data is exactly 104 bytes long, however
+-- the data in the context is stored in host-endianness.
+--
+-- The context data is made up of
+--
+-- * a 'Word64' representing the number of bytes already feed to hash algorithm so far,
+--
+-- * a 64-element 'Word8' buffer holding partial input-chunks, and finally
+--
+-- * a 8-element 'Word32' array holding the current work-in-progress digest-value.
+--
+-- Consequently, a SHA-256 digest as produced by 'hash', 'hashlazy', or 'finalize' is 32 bytes long.
+newtype Ctx = Ctx ByteString
+
+foreign import capi unsafe "hs_sha256.h hs_cryptohash_sha256_init"
+ c_sha256_init :: Ptr Ctx -> IO ()
+
+foreign import capi unsafe "hs_sha256.h hs_cryptohash_sha256_update"
+ c_sha256_update_unsafe :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
+
+foreign import capi safe "hs_sha256.h hs_cryptohash_sha256_update"
+ c_sha256_update_safe :: Ptr Ctx -> Ptr Word8 -> CSize -> IO ()
+
+foreign import capi unsafe "hs_sha256.h hs_cryptohash_sha256_finalize"
+ c_sha256_finalize_len :: Ptr Ctx -> Ptr Word8 -> IO Word64
+
+foreign import capi unsafe "hs_sha256.h hs_cryptohash_sha256_finalize"
+ c_sha256_finalize :: Ptr Ctx -> Ptr Word8 -> IO ()
+
+foreign import capi unsafe "hs_sha256.h hs_cryptohash_sha256_hash"
+ c_sha256_hash_unsafe :: Ptr Word8 -> CSize -> Ptr Word8 -> IO ()
+
+foreign import capi safe "hs_sha256.h hs_cryptohash_sha256_hash"
+ c_sha256_hash_safe :: Ptr Word8 -> CSize -> Ptr Word8 -> IO ()