packages feed

g3p-hash-1.0.0.0: lib/Crypto/G3P/BCrypt.hsc

{-# LANGUAGE CApiFFI, ViewPatterns #-}

-- |  A very minimal binding to the core of the bcrypt algorithm, adapted from
--    OpenBSD's implementation. The Global Password Prehash Protocol version
--    G3Pb1 cannot be implemented in terms of standard bcrypt interfaces for
--    several reasons:
--
--    1.  Standard bcrypt hashes are truncated to 23 bytes.  The G3P depends
--        on all 24 output bytes.
--
--    2.  Standard bcrypt must specify a number of rounds that is a power of
--        two. The G3P allows any number of rounds between 1 and 2^32 inclusive.
--
--    3.  the G3P needs unimpeded access to the full 72 byte password input.
--        This is not doable with all bcrypt variants.
--
--    4.  Standard bcrypt limits salt length to 16 bytes.  The G3P depends on
--        72 byte salt parameters.
--
--    For this reason, this binding completely removes the code for handling
--    unix-style bcrypt hashes, which has repeatedly proven problematic. One
--    of the major design motifs of the G3P is to replace this cruft with PHKDF,
--    which is intended to be bulletproof.
--
--    Similarly, this binding cannot be directly used to process unix-style
--    bcrypt hashes, which does make testing a bit of a challenge.  However,
--    the core algorithm is unmodified, so implementing unix-style hash
--    handling in terms of this binding is very much possible.
--
--    This will be done in the test suite for this library.  Hopefully that
--    implementation will eventually migrate here, once it's production-ready,
--    so that this binding might also be used to handle standard bcrypt hashes
--    directly.

module Crypto.G3P.BCrypt
  ( bcryptRaw
  , bcryptRaw_maxInputLength
  , bcryptRaw_outputLength
  ) where

#include "bcrypt_raw.h"

import           Data.ByteString(ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as B
import           Data.Word

import           Foreign.C.String
import           System.IO.Unsafe

foreign import capi "bcrypt_raw.h bcrypt_raw" c_bcrypt_raw :: CString -> Word32 -> CString -> Word32 -> CString -> Word32 -> IO ()

-- | Any input longer than 72 bytes will be truncated.

bcryptRaw_maxInputLength :: Int
bcryptRaw_maxInputLength = (#const BCRYPT_RAW_MAX_INPUT_LENGTH)

-- | Any output hash from 'bcryptRaw' will be exactly 24 bytes long.

bcryptRaw_outputLength :: Int
bcryptRaw_outputLength = (#const BCRYPT_RAW_OUTPUT_LENGTH)

-- | @bcryptRaw key salt rounds@ Be aware that keys and salts that are longer
--   than 72 bytes do get truncated to exactly 72 bytes. This binding will
--   return a hash that is exactly 24 bytes long.
--
--   Note the rounds parameter is one less than the number of rounds to be
--   computed. Thus if you want something equivalent to the traditional bcrypt
--   cost parameter of 12, you need to specify 4095 rounds.  This is because
--   @2^12 - 1 = 4095@.

bcryptRaw :: ByteString -> ByteString -> Word32 -> ByteString
bcryptRaw (f -> key) (f -> salt) rounds
  = unsafePerformIO $ do
      B.unsafeUseAsCString key $ \keyPtr -> do
        B.unsafeUseAsCString salt $ \saltPtr -> do
          -- using a superfluous `seq` to try to ensure that this allocates a new
          -- unique bytestring.   FIXME: there's almost certainly a better, more
          -- proper, more idiomatic solution here
          let output = B.replicate bcryptRaw_outputLength (saltPtr `seq` 0)
          B.unsafeUseAsCString output $ \outPtr -> do
            c_bcrypt_raw keyPtr (len key) saltPtr (len salt) outPtr rounds
            return output
  where
    len x = fromIntegral (min bcryptRaw_maxInputLength (B.length x))

f :: ByteString -> ByteString
f key = if B.null key then B.replicate bcryptRaw_maxInputLength 0 else key