fastpbkdf2 (empty) → 0.1.0.0
raw patch · 9 files changed
+830/−0 lines, 9 filesdep +basedep +base16-bytestringdep +bytestringsetup-changed
Dependencies added: base, base16-bytestring, bytestring, criterion, cryptonite, fastpbkdf2, pbkdf, tasty, tasty-hunit
Files
- LICENSE +30/−0
- README.md +38/−0
- Setup.hs +2/−0
- bench/Bench.hs +28/−0
- cbits/fastpbkdf2.c +399/−0
- fastpbkdf2.cabal +68/−0
- include/fastpbkdf2.h +71/−0
- src/Crypto/KDF/PBKDF2.hs +91/−0
- test/Main.hs +103/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"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 COPYRIGHT+OWNER OR CONTRIBUTORS 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.
+ README.md view
@@ -0,0 +1,38 @@+[](https://travis-ci.org/adinapoli/fastpbkdf2-hs)+[](https://ci.appveyor.com/project/adinapoli/fastpbkdf2-hs)+[](https://coveralls.io/github/adinapoli/fastpbkdf2-hs?branch=master)++## fastpbkdf2-hs++Haskell bindings to the [fastpbkdf2](https://github.com/ctz/fastpbkdf2) library. This is currently (Dec 2016) the+*fastest* Haskell library for computing the `pbkdf2`, outperforming competitors by several order of magnitude (unsuprisingly,+as it hooks into some C code!):++```+benchmarking sha1/fastpbkdf2+time 4.948 ms (4.850 ms .. 5.040 ms)+ 0.998 R² (0.997 R² .. 0.999 R²)+mean 4.767 ms (4.707 ms .. 4.825 ms)+std dev 186.5 μs (152.3 μs .. 244.2 μs)+variance introduced by outliers: 20% (moderately inflated)++benchmarking sha1/cryptonite+time 29.61 ms (29.04 ms .. 30.03 ms)+ 0.999 R² (0.997 R² .. 1.000 R²)+mean 29.82 ms (29.47 ms .. 30.40 ms)+std dev 916.0 μs (527.1 μs .. 1.579 ms)++benchmarking sha1/pbkdf2+time 8.032 s (7.899 s .. 8.248 s)+ 1.000 R² (1.000 R² .. 1.000 R²)+mean 7.941 s (7.911 s .. 7.995 s)+std dev 47.08 ms (0.0 s .. 48.71 ms)+variance introduced by outliers: 19% (moderately inflated)+```++### Installation++This library depends from `OpenSSL`. I have tried to make this self-contained, but the+crypto layer of OpenSSL (or BoringSSL) requires some fine-tuned ASM code generated during+the build process. Porting everything over in a customised `Build.hs` would have been too+much pain, but PR are super welcome!
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PackageImports #-}+module Main where++import Criterion.Main+import Crypto.Hash.Algorithms as Crypto+import "cryptonite" Crypto.KDF.PBKDF2 as Crypto+import "fastpbkdf2" Crypto.KDF.PBKDF2 as Fast+import Crypto.PBKDF.ByteString as CD+import Data.ByteString as B++data Library = FastPBKDF2+ | Cryptonite+ | PBKDF2++encryptBench :: Library -> ByteString -> ByteString+encryptBench FastPBKDF2 input = Fast.fastpbkdf2_hmac_sha1 input "salt" 10000 32+encryptBench Cryptonite input = Crypto.generate (Crypto.prfHMAC Crypto.SHA1) (Crypto.Parameters 10000 32) input ("salt" :: ByteString)+encryptBench PBKDF2 input = CD.sha1PBKDF2 input "salt" 10000 32++main :: IO ()+main = defaultMain [+ bgroup "sha1" [ bench "fastpbkdf2" $ whnf (encryptBench FastPBKDF2) (B.pack $ Prelude.replicate 100000 0x0)+ , bench "cryptonite" $ whnf (encryptBench Cryptonite) (B.pack $ Prelude.replicate 100000 0x0)+ , bench "pbkdf2" $ whnf (encryptBench PBKDF2) (B.pack $ Prelude.replicate 100000 0x0)+ ]+ ]
+ cbits/fastpbkdf2.c view
@@ -0,0 +1,399 @@+/*+ * fast-pbkdf2 - Optimal PBKDF2-HMAC calculation+ * Written in 2015 by Joseph Birr-Pixton <jpixton@gmail.com>+ *+ * To the extent possible under law, the author(s) have dedicated all+ * copyright and related and neighboring rights to this software to the+ * public domain worldwide. This software is distributed without any+ * warranty.+ *+ * You should have received a copy of the CC0 Public Domain Dedication+ * along with this software. If not, see+ * <http://creativecommons.org/publicdomain/zero/1.0/>.+ */++#include "fastpbkdf2.h"++#include <assert.h>+#include <string.h>++#include <openssl/sha.h>++/* --- MSVC doesn't support C99 --- */+#ifdef _MSC_VER+#define restrict+#define _Pragma __pragma+#endif++/* --- Common useful things --- */+#define MIN(a, b) ((a) > (b)) ? (b) : (a)++static inline void write32_be(uint32_t n, uint8_t out[4])+{+#if defined(__GNUC__) && __GNUC__ >= 4 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__+ *(uint32_t *)(out) = __builtin_bswap32(n);+#else+ out[0] = (n >> 24) & 0xff;+ out[1] = (n >> 16) & 0xff;+ out[2] = (n >> 8) & 0xff;+ out[3] = n & 0xff;+#endif+}++static inline void write64_be(uint64_t n, uint8_t out[8])+{+#if defined(__GNUC__) && __GNUC__ >= 4 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__+ *(uint64_t *)(out) = __builtin_bswap64(n);+#else+ write32_be((n >> 32) & 0xffffffff, out);+ write32_be(n & 0xffffffff, out + 4);+#endif+}++/* --- Optional OpenMP parallelisation of consecutive blocks --- */+#ifdef WITH_OPENMP+# define OPENMP_PARALLEL_FOR _Pragma("omp parallel for")+#else+# define OPENMP_PARALLEL_FOR+#endif++/* Prepare block (of blocksz bytes) to contain md padding denoting a msg-size+ * message (in bytes). block has a prefix of used bytes.+ *+ * Message length is expressed in 32 bits (so suitable for sha1, sha256, sha512). */+static inline void md_pad(uint8_t *block, size_t blocksz, size_t used, size_t msg)+{+ memset(block + used, 0, blocksz - used - 4);+ block[used] = 0x80;+ block += blocksz - 4;+ write32_be((uint32_t) (msg * 8), block);+}++/* Internal function/type names for hash-specific things. */+#define HMAC_CTX(_name) HMAC_ ## _name ## _ctx+#define HMAC_INIT(_name) HMAC_ ## _name ## _init+#define HMAC_UPDATE(_name) HMAC_ ## _name ## _update+#define HMAC_FINAL(_name) HMAC_ ## _name ## _final++#define PBKDF2_F(_name) pbkdf2_f_ ## _name+#define PBKDF2(_name) pbkdf2_ ## _name++/* This macro expands to decls for the whole implementation for a given+ * hash function. Arguments are:+ *+ * _name like 'sha1', added to symbol names+ * _blocksz block size, in bytes+ * _hashsz digest output, in bytes+ * _ctx hash context type+ * _init hash context initialisation function+ * args: (_ctx *c)+ * _update hash context update function+ * args: (_ctx *c, const void *data, size_t ndata)+ * _final hash context finish function+ * args: (void *out, _ctx *c)+ * _xform hash context raw block update function+ * args: (_ctx *c, const void *data)+ * _xcpy hash context raw copy function (only need copy hash state)+ * args: (_ctx * restrict out, const _ctx *restrict in)+ * _xtract hash context state extraction+ * args: args (_ctx *restrict c, uint8_t *restrict out)+ * _xxor hash context xor function (only need xor hash state)+ * args: (_ctx *restrict out, const _ctx *restrict in)+ *+ * The resulting function is named PBKDF2(_name).+ */+#define DECL_PBKDF2(_name, _blocksz, _hashsz, _ctx, \+ _init, _update, _xform, _final, _xcpy, _xtract, _xxor) \+ typedef struct { \+ _ctx inner; \+ _ctx outer; \+ } HMAC_CTX(_name); \+ \+ static inline void HMAC_INIT(_name)(HMAC_CTX(_name) *ctx, \+ const uint8_t *key, size_t nkey) \+ { \+ /* Prepare key: */ \+ uint8_t k[_blocksz]; \+ \+ /* Shorten long keys. */ \+ if (nkey > _blocksz) \+ { \+ _init(&ctx->inner); \+ _update(&ctx->inner, key, nkey); \+ _final(k, &ctx->inner); \+ \+ key = k; \+ nkey = _hashsz; \+ } \+ \+ /* Standard doesn't cover case where blocksz < hashsz. */ \+ assert(nkey <= _blocksz); \+ \+ /* Right zero-pad short keys. */ \+ if (k != key) \+ memcpy(k, key, nkey); \+ if (_blocksz > nkey) \+ memset(k + nkey, 0, _blocksz - nkey); \+ \+ /* Start inner hash computation */ \+ uint8_t blk_inner[_blocksz]; \+ uint8_t blk_outer[_blocksz]; \+ \+ for (size_t i = 0; i < _blocksz; i++) \+ { \+ blk_inner[i] = 0x36 ^ k[i]; \+ blk_outer[i] = 0x5c ^ k[i]; \+ } \+ \+ _init(&ctx->inner); \+ _update(&ctx->inner, blk_inner, sizeof blk_inner); \+ \+ /* And outer. */ \+ _init(&ctx->outer); \+ _update(&ctx->outer, blk_outer, sizeof blk_outer); \+ } \+ \+ static inline void HMAC_UPDATE(_name)(HMAC_CTX(_name) *ctx, \+ const void *data, size_t ndata) \+ { \+ _update(&ctx->inner, data, ndata); \+ } \+ \+ static inline void HMAC_FINAL(_name)(HMAC_CTX(_name) *ctx, \+ uint8_t out[_hashsz]) \+ { \+ _final(out, &ctx->inner); \+ _update(&ctx->outer, out, _hashsz); \+ _final(out, &ctx->outer); \+ } \+ \+ \+ /* --- PBKDF2 --- */ \+ static inline void PBKDF2_F(_name)(const HMAC_CTX(_name) *startctx, \+ uint32_t counter, \+ const uint8_t *salt, size_t nsalt, \+ uint32_t iterations, \+ uint8_t *out) \+ { \+ uint8_t countbuf[4]; \+ write32_be(counter, countbuf); \+ \+ /* Prepare loop-invariant padding block. */ \+ uint8_t Ublock[_blocksz]; \+ md_pad(Ublock, _blocksz, _hashsz, _blocksz + _hashsz); \+ \+ /* First iteration: \+ * U_1 = PRF(P, S || INT_32_BE(i)) \+ */ \+ HMAC_CTX(_name) ctx = *startctx; \+ HMAC_UPDATE(_name)(&ctx, salt, nsalt); \+ HMAC_UPDATE(_name)(&ctx, countbuf, sizeof countbuf); \+ HMAC_FINAL(_name)(&ctx, Ublock); \+ _ctx result = ctx.outer; \+ \+ /* Subsequent iterations: \+ * U_c = PRF(P, U_{c-1}) \+ */ \+ for (uint32_t i = 1; i < iterations; i++) \+ { \+ /* Complete inner hash with previous U */ \+ _xcpy(&ctx.inner, &startctx->inner); \+ _xform(&ctx.inner, Ublock); \+ _xtract(&ctx.inner, Ublock); \+ /* Complete outer hash with inner output */ \+ _xcpy(&ctx.outer, &startctx->outer); \+ _xform(&ctx.outer, Ublock); \+ _xtract(&ctx.outer, Ublock); \+ _xxor(&result, &ctx.outer); \+ } \+ \+ /* Reform result into output buffer. */ \+ _xtract(&result, out); \+ } \+ \+ static inline void PBKDF2(_name)(const uint8_t *pw, size_t npw, \+ const uint8_t *salt, size_t nsalt, \+ uint32_t iterations, \+ uint8_t *out, size_t nout) \+ { \+ assert(iterations); \+ assert(out && nout); \+ \+ /* Starting point for inner loop. */ \+ HMAC_CTX(_name) ctx; \+ HMAC_INIT(_name)(&ctx, pw, npw); \+ \+ /* How many blocks do we need? */ \+ uint32_t blocks_needed = (uint32_t)(nout + _hashsz - 1) / _hashsz; \+ \+ OPENMP_PARALLEL_FOR \+ for (uint32_t counter = 1; counter <= blocks_needed; counter++) \+ { \+ uint8_t block[_hashsz]; \+ PBKDF2_F(_name)(&ctx, counter, salt, nsalt, iterations, block); \+ \+ size_t offset = (counter - 1) * _hashsz; \+ size_t taken = MIN(nout - offset, _hashsz); \+ memcpy(out + offset, block, taken); \+ } \+ }++static inline void sha1_extract(SHA_CTX *restrict ctx, uint8_t *restrict out)+{+ write32_be(ctx->h0, out);+ write32_be(ctx->h1, out + 4);+ write32_be(ctx->h2, out + 8);+ write32_be(ctx->h3, out + 12);+ write32_be(ctx->h4, out + 16);+}++static inline void sha1_cpy(SHA_CTX *restrict out, const SHA_CTX *restrict in)+{+ out->h0 = in->h0;+ out->h1 = in->h1;+ out->h2 = in->h2;+ out->h3 = in->h3;+ out->h4 = in->h4;+}++static inline void sha1_xor(SHA_CTX *restrict out, const SHA_CTX *restrict in)+{+ out->h0 ^= in->h0;+ out->h1 ^= in->h1;+ out->h2 ^= in->h2;+ out->h3 ^= in->h3;+ out->h4 ^= in->h4;+}++DECL_PBKDF2(sha1,+ SHA_CBLOCK,+ SHA_DIGEST_LENGTH,+ SHA_CTX,+ SHA1_Init,+ SHA1_Update,+ SHA1_Transform,+ SHA1_Final,+ sha1_cpy,+ sha1_extract,+ sha1_xor)++static inline void sha256_extract(SHA256_CTX *restrict ctx, uint8_t *restrict out)+{+ write32_be(ctx->h[0], out);+ write32_be(ctx->h[1], out + 4);+ write32_be(ctx->h[2], out + 8);+ write32_be(ctx->h[3], out + 12);+ write32_be(ctx->h[4], out + 16);+ write32_be(ctx->h[5], out + 20);+ write32_be(ctx->h[6], out + 24);+ write32_be(ctx->h[7], out + 28);+}++static inline void sha256_cpy(SHA256_CTX *restrict out, const SHA256_CTX *restrict in)+{+ out->h[0] = in->h[0];+ out->h[1] = in->h[1];+ out->h[2] = in->h[2];+ out->h[3] = in->h[3];+ out->h[4] = in->h[4];+ out->h[5] = in->h[5];+ out->h[6] = in->h[6];+ out->h[7] = in->h[7];+}++static inline void sha256_xor(SHA256_CTX *restrict out, const SHA256_CTX *restrict in)+{+ out->h[0] ^= in->h[0];+ out->h[1] ^= in->h[1];+ out->h[2] ^= in->h[2];+ out->h[3] ^= in->h[3];+ out->h[4] ^= in->h[4];+ out->h[5] ^= in->h[5];+ out->h[6] ^= in->h[6];+ out->h[7] ^= in->h[7];+}++DECL_PBKDF2(sha256,+ SHA256_CBLOCK,+ SHA256_DIGEST_LENGTH,+ SHA256_CTX,+ SHA256_Init,+ SHA256_Update,+ SHA256_Transform,+ SHA256_Final,+ sha256_cpy,+ sha256_extract,+ sha256_xor)++static inline void sha512_extract(SHA512_CTX *restrict ctx, uint8_t *restrict out)+{+ write64_be(ctx->h[0], out);+ write64_be(ctx->h[1], out + 8);+ write64_be(ctx->h[2], out + 16);+ write64_be(ctx->h[3], out + 24);+ write64_be(ctx->h[4], out + 32);+ write64_be(ctx->h[5], out + 40);+ write64_be(ctx->h[6], out + 48);+ write64_be(ctx->h[7], out + 56);+}++static inline void sha512_cpy(SHA512_CTX *restrict out, const SHA512_CTX *restrict in)+{+ out->h[0] = in->h[0];+ out->h[1] = in->h[1];+ out->h[2] = in->h[2];+ out->h[3] = in->h[3];+ out->h[4] = in->h[4];+ out->h[5] = in->h[5];+ out->h[6] = in->h[6];+ out->h[7] = in->h[7];+}++static inline void sha512_xor(SHA512_CTX *restrict out, const SHA512_CTX *restrict in)+{+ out->h[0] ^= in->h[0];+ out->h[1] ^= in->h[1];+ out->h[2] ^= in->h[2];+ out->h[3] ^= in->h[3];+ out->h[4] ^= in->h[4];+ out->h[5] ^= in->h[5];+ out->h[6] ^= in->h[6];+ out->h[7] ^= in->h[7];+}++DECL_PBKDF2(sha512,+ SHA512_CBLOCK,+ SHA512_DIGEST_LENGTH,+ SHA512_CTX,+ SHA512_Init,+ SHA512_Update,+ SHA512_Transform,+ SHA512_Final,+ sha512_cpy,+ sha512_extract,+ sha512_xor)++void fastpbkdf2_hmac_sha1(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout)+{+ PBKDF2(sha1)(pw, npw, salt, nsalt, iterations, out, nout);+}++void fastpbkdf2_hmac_sha256(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout)+{+ PBKDF2(sha256)(pw, npw, salt, nsalt, iterations, out, nout);+}++void fastpbkdf2_hmac_sha512(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout)+{+ PBKDF2(sha512)(pw, npw, salt, nsalt, iterations, out, nout);+}+
+ fastpbkdf2.cabal view
@@ -0,0 +1,68 @@+name: fastpbkdf2+version: 0.1.0.0+synopsis: Haskell bindings to the fastpbkdf2 C library+description: Please see README.md+homepage: https://github.com/adinapoli/fastpbkdf2-hs#readme+license: BSD3+license-file: LICENSE+author: Alfredo Di Napoli+maintainer: alfredo.dinapoli@gmail.com+copyright: 2016 Alfredo Di Napoli+category: Cryptography+build-type: Simple+cabal-version: >=1.10+extra-source-files: README.md+ include/fastpbkdf2.h++library+ hs-source-dirs: src+ c-sources: cbits/fastpbkdf2.c+ cc-options: -ffast-math -std=gnu99+ include-dirs: include+ exposed-modules: Crypto.KDF.PBKDF2+ build-depends: base >= 4.6 && < 5+ , bytestring >= 0.10.4.0+ default-language: Haskell2010+ if os(mingw32) || os(windows)+ extra-libraries: eay32, ssl32+ else+ if os(osx)+ include-dirs: /usr/local/opt/openssl/include+ extra-lib-dirs: /usr/local/opt/openssl/lib+ else+ if arch(x86_64)+ cpp-options: -D__x86_64__+ if arch(i386)+ cpp-options: -D__i386__+ extra-libraries: crypto++test-suite fastpbkdf2-hs-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: base+ , base16-bytestring+ , bytestring+ , fastpbkdf2 -any+ , tasty+ , tasty-hunit+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++benchmark fastpbkdf2-bench+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ hs-source-dirs: bench+ ghc-options: -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates -O2 -threaded -rtsopts -with-rtsopts=-N1 -with-rtsopts=-s -with-rtsopts=-qg+ build-depends:+ base >=4.6 && <5+ , bytestring >= 0.10.4.0+ , criterion+ , fastpbkdf2+ , cryptonite+ , pbkdf+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/adinapoli/fastpbkdf2-hs
+ include/fastpbkdf2.h view
@@ -0,0 +1,71 @@+/*+ * fastpbkdf2 - Faster PBKDF2-HMAC calculation+ * Written in 2015 by Joseph Birr-Pixton <jpixton@gmail.com>+ *+ * To the extent possible under law, the author(s) have dedicated all+ * copyright and related and neighboring rights to this software to the+ * public domain worldwide. This software is distributed without any+ * warranty.+ *+ * You should have received a copy of the CC0 Public Domain Dedication+ * along with this software. If not, see+ * <http://creativecommons.org/publicdomain/zero/1.0/>.+ */++#ifndef FASTPBKDF2_H+#define FASTPBKDF2_H++#include <stdlib.h>+#include <stdint.h>++#ifdef __cplusplus+extern "C" {+#endif++/** Calculates PBKDF2-HMAC-SHA1.+ *+ * @p npw bytes at @p pw are the password input.+ * @p nsalt bytes at @p salt are the salt input.+ * @p iterations is the PBKDF2 iteration count and must be non-zero.+ * @p nout bytes of output are written to @p out. @p nout must be non-zero.+ *+ * This function cannot fail; it does not report errors.+ */+void fastpbkdf2_hmac_sha1(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout);++/** Calculates PBKDF2-HMAC-SHA256.+ *+ * @p npw bytes at @p pw are the password input.+ * @p nsalt bytes at @p salt are the salt input.+ * @p iterations is the PBKDF2 iteration count and must be non-zero.+ * @p nout bytes of output are written to @p out. @p nout must be non-zero.+ *+ * This function cannot fail; it does not report errors.+ */+void fastpbkdf2_hmac_sha256(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout);++/** Calculates PBKDF2-HMAC-SHA512.+ *+ * @p npw bytes at @p pw are the password input.+ * @p nsalt bytes at @p salt are the salt input.+ * @p iterations is the PBKDF2 iteration count and must be non-zero.+ * @p nout bytes of output are written to @p out. @p nout must be non-zero.+ *+ * This function cannot fail; it does not report errors.+ */+void fastpbkdf2_hmac_sha512(const uint8_t *pw, size_t npw,+ const uint8_t *salt, size_t nsalt,+ uint32_t iterations,+ uint8_t *out, size_t nout);++#ifdef __cplusplus+}+#endif++#endif
+ src/Crypto/KDF/PBKDF2.hs view
@@ -0,0 +1,91 @@++module Crypto.KDF.PBKDF2 (+ fastpbkdf2_hmac_sha1+ , fastpbkdf2_hmac_sha256+ , fastpbkdf2_hmac_sha512+ ) where++import Data.ByteString+import Data.ByteString.Unsafe+import Data.Monoid+import Foreign.C.Types+import Foreign.ForeignPtr+import Foreign.Ptr+import System.IO.Unsafe++foreign import ccall "fastpbkdf2_hmac_sha1" c_fastpbkdf2_hmac_sha1 :: Signature++foreign import ccall "fastpbkdf2_hmac_sha256" c_fastpbkdf2_hmac_sha256 :: Signature++foreign import ccall "fastpbkdf2_hmac_sha512" c_fastpbkdf2_hmac_sha512 :: Signature++type Signature = Ptr CChar -> CSize -> Ptr CChar -> CSize -> CInt -> Ptr CChar -> CSize -> IO ()++fastpbkdf2_fn :: Signature+ -> ByteString+ -- ^ The user key (e.g. a password)+ -> ByteString+ -- ^ The salt+ -> Int+ -- ^ The iteration count+ -> Int+ -- ^ The length (in bytes) of the output+ -> ByteString+fastpbkdf2_fn fn password salt iterations keyLen = unsafeDupablePerformIO $ do+ withPositive (iterations, keyLen) $ do+ let outSize = CSize (fromIntegral keyLen)+ outForeignPtr <- mallocForeignPtrBytes keyLen+ withForeignPtr outForeignPtr $ \ptrOut -> do+ unsafeUseAsCStringLen password $ \(passwordPtr, passwordSizeInt) -> do+ unsafeUseAsCStringLen salt $ \(saltPtr, saltSizeInt) -> do+ let passwordSize = CSize (fromIntegral passwordSizeInt)+ let saltSize = CSize (fromIntegral saltSizeInt)+ let iters = CInt (fromIntegral iterations)+ fn passwordPtr passwordSize saltPtr saltSize iters ptrOut outSize+ unsafePackCStringLen (ptrOut, keyLen)+{-# NOINLINE fastpbkdf2_fn #-}++withPositive :: (Int, Int) -> IO a -> IO a+withPositive (iter, keyLen) action+ | iter <= 0 = error $ "fastpbkdf2: iteration count must be > 0 (it was " <> show iter <> ")"+ | keyLen <= 0 = error $ "fastpbkdf2: key length must be > 0 (it was " <> show keyLen <> ")"+ | otherwise = action++--------------------------------------------------------------------------------+fastpbkdf2_hmac_sha1 :: ByteString+ -- ^ The user key (e.g. a password)+ -> ByteString+ -- ^ The salt+ -> Int+ -- ^ The iteration count+ -> Int+ -- ^ The length (in bytes) of the output+ -> ByteString+fastpbkdf2_hmac_sha1 = fastpbkdf2_fn c_fastpbkdf2_hmac_sha1+{-# INLINE fastpbkdf2_hmac_sha1 #-}++--------------------------------------------------------------------------------+fastpbkdf2_hmac_sha256 :: ByteString+ -- ^ The user key (e.g. a password)+ -> ByteString+ -- ^ The salt+ -> Int+ -- ^ The iteration count+ -> Int+ -- ^ The length (in bytes) of the output+ -> ByteString+fastpbkdf2_hmac_sha256 = fastpbkdf2_fn c_fastpbkdf2_hmac_sha256+{-# INLINE fastpbkdf2_hmac_sha256 #-}++--------------------------------------------------------------------------------+fastpbkdf2_hmac_sha512 :: ByteString+ -- ^ The user key (e.g. a password)+ -> ByteString+ -- ^ The salt+ -> Int+ -- ^ The iteration count+ -> Int+ -- ^ The length (in bytes) of the output+ -> ByteString+fastpbkdf2_hmac_sha512 = fastpbkdf2_fn c_fastpbkdf2_hmac_sha512+{-# INLINE fastpbkdf2_hmac_sha512 #-}
+ test/Main.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Main where++import Control.Exception (evaluate, try, SomeException)+import Crypto.KDF.PBKDF2+import Data.ByteString as B+import Data.ByteString.Base16 (decode)+import Data.ByteString.Char8 as C8+import Data.Either (isLeft, isRight)+import Data.Monoid+import Test.Tasty+import Test.Tasty.HUnit++----------------------------------------------------------------------+unhex :: ByteString -> ByteString+unhex = fst . decode++----------------------------------------------------------------------+type CryptoFn = ByteString -> ByteString -> Int -> Int -> ByteString++----------------------------------------------------------------------+testVectors :: CryptoFn -> [(C8.ByteString, C8.ByteString,Int,ByteString)] -> Assertion+testVectors _ [] = return ()+testVectors fn ((input, salt, iter, expected_hex):xs) = do+ let actual = fn input salt iter (floor ((fromIntegral $ B.length expected_hex) / fromIntegral 2))+ actual @?= (unhex expected_hex)+ testVectors fn xs++----------------------------------------------------------------------+testInvalidIterationNumber :: Assertion+testInvalidIterationNumber = do+ (res :: Either SomeException ByteString) <- try $ evaluate (fastpbkdf2_hmac_sha1 "password" "salt" 0 32)+ isLeft res @?= True++----------------------------------------------------------------------+testInvalidKeyLen :: Assertion+testInvalidKeyLen = do+ (res :: Either SomeException ByteString) <- try $ evaluate (fastpbkdf2_hmac_sha1 "password" "salt" 1 (-1))+ isLeft res @?= True++----------------------------------------------------------------------+testEmptyPassword :: Assertion+testEmptyPassword = do+ (res :: Either SomeException ByteString) <- try $ evaluate (fastpbkdf2_hmac_sha1 "" "salt" 1 32)+ isRight res @?= True++----------------------------------------------------------------------+testEmptySalt :: Assertion+testEmptySalt = do+ (res :: Either SomeException ByteString) <- try $ evaluate (fastpbkdf2_hmac_sha1 "password" "" 1 32)+ isRight res @?= True++----------------------------------------------------------------------+main :: IO ()+main = do+ defaultMainWithIngredients defaultIngredients $ testGroup "all tests" [+ testGroup "Test Vectors" $ [+ testCase "RFC6070 SHA1 Test Vectors" (testVectors fastpbkdf2_hmac_sha1 test_vectors_sha1)+ , testCase "RFC6070 SHA256 Test Vectors" (testVectors fastpbkdf2_hmac_sha256 test_vectors_sha256)+ , testCase "(Partial) RFC6070 SHA512 Test Vectors" (testVectors fastpbkdf2_hmac_sha512 test_vectors_sha512)+ ]+ , testGroup "Edge cases" $ [+ testCase "Invalid iteration number" testInvalidIterationNumber+ , testCase "Invalid key length" testInvalidKeyLen+ , testCase "Empty password" testEmptyPassword+ , testCase "Empty salt" testEmptySalt+ ]+ ]++-- RFC6070 test vectors+test_vectors_sha1 :: [(C8.ByteString,C8.ByteString,Int,ByteString)]+test_vectors_sha1 = [ ("password", "salt", 1, "0c60c80f961f0e71f3a9b524af6012062fe037a6")+ , ("password", "salt", 2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957")+ , ("password", "salt", 4096, "4b007901b765489abead49d926f721d065a429c1")+ , ("password", "salt", 16777216, "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984")+ , ( "passwordPASSWORDpassword"+ , "saltSALTsaltSALTsaltSALTsaltSALTsalt"+ , 4096+ , "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038")+ , ("pass\0word", "sa\0lt", 4096, "56fa6aa75548099dcc37d7f03425e0c3")+ ]++test_vectors_sha256 :: [(C8.ByteString,C8.ByteString,Int,ByteString)]+test_vectors_sha256 = [ ("password", "salt", 1, "120fb6cffcf8b32c43e7225256c4f837a86548c9")+ , ("password", "salt", 2, "ae4d0c95af6b46d32d0adff928f06dd02a303f8e")+ , ("password", "salt", 4096, "c5e478d59288c841aa530db6845c4c8d962893a0")+ , ("password", "salt", 16777216, "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e8")+ , ( "passwordPASSWORDpassword"+ , "saltSALTsaltSALTsaltSALTsaltSALTsalt"+ , 4096+ , "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c")+ , ("pass\0word", "sa\0lt", 4096, "89b69d0516f829893c696226650a8687")+ ]++test_vectors_sha512 :: [(C8.ByteString,C8.ByteString,Int,ByteString)]+test_vectors_sha512 = [ ("password", "salt", 1, "867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252c02d470"+ <> "a285a0501bad999bfe943c08f050235d7d68b1da55e63f73b60a57fce"+ )+ , ("password", "salt", 2, "e1d9c16aa681708a45f5c7c4e215ceb66e011a2e9f0040713f18aefdb866d53cf76cab2868a39b9f"+ <> "7840edce4fef5a82be67335c77a6068e04112754f27ccf4e"+ )+ ]