<commit_before>{-# LANGUAGE DeriveGeneric #-}

module Web.ZeroBin.SJCL (
  Content(..),
  encrypt
) where

import Crypto.Cipher.AES (AES256)
import Crypto.Cipher.Types (ivAdd, blockSize, cipherInit, ecbEncrypt, ctrCombine, makeIV)
import Crypto.Error (throwCryptoErrorIO)
import Crypto.Hash.Algorithms (SHA256(..))
import Crypto.KDF.PBKDF2 (prfHMAC)
import Crypto.Number.Serialize (i2ospOf_)
import Crypto.Random.Entropy (getEntropy)
import Data.ByteString (ByteString)
import Data.Maybe (fromJust)
import Data.Word (Word8)
import GHC.Generics (Generic)
import Web.ZeroBin.Utils (toWeb)
import qualified Crypto.KDF.PBKDF2 as PBKDF2
import qualified Data.Aeson as JSON
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C

data Content = Content {
    iv   :: String
  , salt :: String
  , ct   :: String
  } deriving (Generic, Show)

-- FIXME: http://stackoverflow.com/questions/33045350/unexpected-haskell-aeson-warning-no-explicit-implementation-for-tojson
instance JSON.ToJSON Content where
  toJSON = JSON.genericToJSON JSON.defaultOptions

makeCipher :: ByteString -> IO AES256
makeCipher = throwCryptoErrorIO . cipherInit

-- SJCL uses PBKDF2-HMAC-SHA256 with 1000 iterations, 32 bytes length,
-- but the output is truncated down to 16 bytes.
-- https://github.com/bitwiseshiftleft/sjcl/blob/master/core/pbkdf2.js
-- TODO: this is default, we can specify it explicitly
--       for forward compatibility
makeKey :: ByteString -> ByteString -> ByteString
makeKey pwd slt = BS.take 16 $ PBKDF2.generate (prfHMAC SHA256)
  PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 32}
  pwd slt


chunks :: Int -> ByteString -> [ByteString]
chunks sz = split
  where split b | cl <= sz = [b'] -- padded
                | otherwise = b1 : split b2
                where cl = BS.length b
                      (b1, b2) = BS.splitAt sz b
                      b' = BS.take sz $ BS.append b (BS.replicate sz 0)

lengthOf :: Int -> Word8
lengthOf = ceiling . (logBase 256 :: Float -> Float) . fromIntegral

-- Ref. https://tools.ietf.org/html/rfc3610
-- SJCL uses 64-bit tag (8 bytes)
encrypt :: String -> ByteString -> IO Content
encrypt password plaintext = do
  ivd    <- getEntropy 16 -- XXX it is truncated to get the nonce below
  slt    <- getEntropy 13 -- arbitrary length
  cipher <- makeCipher $ makeKey (C.pack password) slt
  let tlen = 8 :: Word8<commit_msg>Simply use PBKDF2.outputLength = 16<commit_after>{-# LANGUAGE DeriveGeneric #-}

module Web.ZeroBin.SJCL (
  Content(..),
  encrypt
) where

import Crypto.Cipher.AES (AES256)
import Crypto.Cipher.Types (ivAdd, blockSize, cipherInit, ecbEncrypt, ctrCombine, makeIV)
import Crypto.Error (throwCryptoErrorIO)
import Crypto.Hash.Algorithms (SHA256(..))
import Crypto.KDF.PBKDF2 (prfHMAC)
import Crypto.Number.Serialize (i2ospOf_)
import Crypto.Random.Entropy (getEntropy)
import Data.ByteString (ByteString)
import Data.Maybe (fromJust)
import Data.Word (Word8)
import GHC.Generics (Generic)
import Web.ZeroBin.Utils (toWeb)
import qualified Crypto.KDF.PBKDF2 as PBKDF2
import qualified Data.Aeson as JSON
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C

data Content = Content {
    iv   :: String
  , salt :: String
  , ct   :: String
  } deriving (Generic, Show)

-- FIXME: http://stackoverflow.com/questions/33045350/unexpected-haskell-aeson-warning-no-explicit-implementation-for-tojson
instance JSON.ToJSON Content where
  toJSON = JSON.genericToJSON JSON.defaultOptions

makeCipher :: ByteString -> IO AES256
makeCipher = throwCryptoErrorIO . cipherInit

-- https://github.com/bitwiseshiftleft/sjcl/blob/master/core/pbkdf2.js
-- TODO: this is default, we can specify it explicitly for forward compatibility
makeKey :: ByteString -> ByteString -> ByteString
makeKey = PBKDF2.generate (prfHMAC SHA256)
  PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 16}

chunks :: Int -> ByteString -> [ByteString]
chunks sz = split
  where split b | cl <= sz = [b'] -- padded
                | otherwise = b1 : split b2
                where cl = BS.length b
                      (b1, b2) = BS.splitAt sz b
                      b' = BS.take sz $ BS.append b (BS.replicate sz 0)

lengthOf :: Int -> Word8
lengthOf = ceiling . (logBase 256 :: Float -> Float) . fromIntegral

-- Ref. https://tools.ietf.org/html/rfc3610
-- SJCL uses 64-bit tag (8 bytes)
encrypt :: String -> ByteString -> IO Content
encrypt password plaintext = do
  ivd    <- getEntropy 16 -- XXX it is truncated to get the nonce below
  slt    <- getEntropy 13 -- arbitrary length
  cipher <- makeCipher $ makeKey (C.pack password) slt
  let tlen = 8 :: Word8
{'fraction_non_alphanumeric': 0.1049863244266779, 'fraction_numerical': 0.028403113822848727, 'mean_word_length': 3.430568499534017, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 21, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5360482', 'n_tokens_mistral': 1677, 'n_tokens_neox': 1545, 'n_words': 460}