module Unbreak.Crypto
( getRandomBytes
, scrypt
, encrypt
, decrypt
, encryptFileName
, decryptFileName
, module Crypto.Error
) where
import Prelude hiding ((++))
import System.IO
import Data.ByteString (ByteString, hGet)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.ByteString.Builder as B
import Data.Serialize.Get
import Data.ByteArray (convert)
import Crypto.Error
import Crypto.KDF.Scrypt
import qualified Crypto.Cipher.ChaChaPoly1305 as C
(++) :: Monoid m => m -> m -> m
(++) = mappend
getRandomBytes :: Int -> IO ByteString
getRandomBytes n = withFile "/dev/urandom" ReadMode $ \ h -> hGet h n
scrypt
:: ByteString
-> ByteString
-> ByteString
scrypt = generate (Parameters 16384 8 1 32)
encrypt
:: ByteString
-> ByteString
-> ByteString
-> CryptoFailable ByteString
encrypt nonce key plaintext = (nonce ++) <$> encrypt' nonce key "" plaintext
encrypt'
:: ByteString
-> ByteString
-> ByteString
-> ByteString
-> CryptoFailable ByteString
encrypt' nonce key header plaintext = do
st1 <- C.nonce12 nonce >>= C.initialize key
let
st2 = C.finalizeAAD $ C.appendAAD header st1
(out, st3) = C.encrypt plaintext st2
auth = C.finalize st3
return $ out ++ Data.ByteArray.convert auth
encryptFileName
:: ByteString
-> ByteString
-> ByteString
encryptFileName key fileName = nonce ++ encryptNoAuth nonce key
(mconcat [word32LEencode oLen, fileName, padding])
where
nonce = B.take 12 $ B.drop 10 $ scrypt fileName key
oLen = B.length fileName
goalLen = (((oLen + 15) `div` 48) + 1) * 48
padding = B.pack $ replicate (goalLen 16 oLen) '\0'
encryptNoAuth
:: ByteString
-> ByteString
-> ByteString
-> ByteString
encryptNoAuth nonce key plaintext = throwCryptoError $ do
st1 <- C.nonce12 nonce >>= C.initialize key
let
st2 = C.finalizeAAD st1
(out, _) = C.encrypt plaintext st2
return out
decrypt
:: ByteString
-> ByteString
-> CryptoFailable ByteString
decrypt key input = decrypt' nonce key "" ciphertextWithTag
where
(nonce, ciphertextWithTag) = B.splitAt 12 input
decrypt'
:: ByteString
-> ByteString
-> ByteString
-> ByteString
-> CryptoFailable ByteString
decrypt' nonce key header input
| B.length input < 16 =
CryptoFailed CryptoError_AuthenticationTagSizeInvalid
| otherwise = case decryptionAttempt of
CryptoPassed (decrypted, auth)
| Data.ByteArray.convert auth == tag -> return decrypted
| otherwise -> CryptoFailed CryptoError_MacKeyInvalid
CryptoFailed x -> CryptoFailed x
where
(ciphertext, tag) = B.splitAt (B.length input 16) input
decryptionAttempt = do
st1 <- C.nonce12 nonce >>= C.initialize key
let
st2 = C.finalizeAAD $ C.appendAAD header st1
(out, st3) = C.decrypt ciphertext st2
auth = C.finalize st3
return (out, auth)
decryptFileName
:: ByteString
-> ByteString
-> ByteString
decryptFileName key encrypted = B.take oLen $ B.drop 4 decrypted
where
(nonce, ciphertext) = B.splitAt 12 encrypted
decrypted = decryptNoAuth nonce key ciphertext
oLen = word32LEdecode decrypted
decryptNoAuth
:: ByteString
-> ByteString
-> ByteString
-> ByteString
decryptNoAuth nonce key ciphertext = throwCryptoError $ do
st1 <- C.nonce12 nonce >>= C.initialize key
let
st2 = C.finalizeAAD st1
(out, _) = C.decrypt ciphertext st2
return out
word32LEencode :: Int -> ByteString
word32LEencode = LB.toStrict . B.toLazyByteString . B.word32LE . fromIntegral
word32LEdecode :: ByteString -> Int
word32LEdecode = fromIntegral .
either (const $ error "word32LE decode fail") id .
runGet getWord32le