Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename Sel.SecretKey.Cipher.Hash to Ciphertext #182

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 55 additions & 54 deletions sel/src/Sel/SecretKey/Cipher.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ module Sel.SecretKey.Cipher
, nonceFromHexByteString
, nonceToHexByteString

-- ** Hash
, Hash
, hashFromHexByteString
, hashToBinary
, hashToHexByteString
, hashToHexText
-- ** Ciphertext
, Ciphertext
, ciphertextFromHexByteString
, ciphertextToBinary
, ciphertextToHexByteString
, ciphertextToHexText
) where

import Control.Monad (void, when)
Expand Down Expand Up @@ -75,7 +75,7 @@ import Sel.Internal.Sodium (binaryToHex)
-- $introduction
-- "Authenticated Encryption" uses a secret key along with a single-use number
-- called a "nonce" to encrypt a message.
-- The resulting hash is accompanied by an authentication tag.
-- The resulting ciphertext is accompanied by an authentication tag.
--
-- Encryption is done with the XSalsa20 stream cipher and authentication is done with the
-- Poly1305 MAC hash.
Expand Down Expand Up @@ -255,16 +255,16 @@ nonceToHexByteString (Nonce nonceForeignPtr) =
-- | A ciphertext consisting of an encrypted message and an authentication tag.
--
-- @since 0.0.1.0
data Hash = Hash
data Ciphertext = Ciphertext
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we prefer Ciphertext or CipherText?

I noticed a little bit of inconsistency:

In Sel.PublicKey.Cipher and Sel.SecretKey.Stream, we have two types named CipherText. However, in Sel.SecretKey.Stream, there are functions where this capitalization is not consistent: ciphertextFromHexByteString, ciphertextToBinary, etc.

Just let me know which way you guys prefer and I can make the changes throughout the project, if you like.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've personally always used "ciphertext" as a single word but, of course, I'd like to comply with the API that you prefer.

{ messageLength :: CULLong
, hashForeignPtr :: ForeignPtr CUChar
, ciphertextForeignPtr :: ForeignPtr CUChar
}

-- |
--
-- @since 0.0.1.0
instance Eq Hash where
(Hash messageLength1 hk1) == (Hash messageLength2 hk2) =
instance Eq Ciphertext where
(Ciphertext messageLength1 hk1) == (Ciphertext messageLength2 hk2) =
let
messageLength = messageLength1 == messageLength2
content =
Expand All @@ -278,8 +278,8 @@ instance Eq Hash where
-- |
--
-- @since 0.0.1.0
instance Ord Hash where
compare (Hash messageLength1 hk1) (Hash messageLength2 hk2) =
instance Ord Ciphertext where
compare (Ciphertext messageLength1 hk1) (Ciphertext messageLength2 hk2) =
let
messageLength = compare messageLength1 messageLength2
content =
Expand All @@ -293,69 +293,70 @@ instance Ord Hash where
-- | ⚠️ Be prudent as to what you do with it!
--
-- @since 0.0.1.0
instance Display Hash where
displayBuilder = Builder.fromText . hashToHexText
instance Display Ciphertext where
displayBuilder = Builder.fromText . ciphertextToHexText

-- | ⚠️ Be prudent as to what you do with it!
--
-- @since 0.0.1.0
instance Show Hash where
show = BS.unpackChars . hashToHexByteString
instance Show Ciphertext where
show = BS.unpackChars . ciphertextToHexByteString

-- | Create a 'Hash' from a binary 'StrictByteString' that you have obtained on your own,
-- usually from the network or disk. It must be a valid hash built from the concatenation
-- of the encrypted message and the authentication tag.
-- | Create a 'Ciphertext' from a hexadecimal-encoded 'StrictByteString' that
-- you have obtained on your own, usually from the network or disk. It must be
-- a valid ciphertext built from the concatenation of the encrypted message and
-- the authentication tag.
Comment on lines -305 to +308
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea what line width we're generally targeting? I changed this to 80 chars when updating the docs, but I'm happy to comply with whatever you prefer.

--
-- The input hash must at least of length 'cryptoSecretboxMACBytes'
-- The input ciphertext must at least of length 'cryptoSecretboxMACBytes'.
--
-- @since 0.0.1.0
hashFromHexByteString :: StrictByteString -> Either Text Hash
hashFromHexByteString hexHash = unsafeDupablePerformIO $
case Base16.decodeBase16Untyped hexHash of
ciphertextFromHexByteString :: StrictByteString -> Either Text Ciphertext
ciphertextFromHexByteString hexCiphertext = unsafeDupablePerformIO $
case Base16.decodeBase16Untyped hexCiphertext of
Right bytestring ->
if BS.length bytestring >= fromIntegral cryptoSecretboxMACBytes
then BS.unsafeUseAsCStringLen bytestring $ \(outsideHashPtr, outsideHashLength) -> do
hashForeignPtr <- BS.mallocByteString @CChar outsideHashLength -- The foreign pointer that will receive the hash data.
Foreign.withForeignPtr hashForeignPtr $ \hashPtr ->
-- We copy bytes from 'outsideHashPtr' to 'hashPtr'.
Foreign.copyArray hashPtr outsideHashPtr outsideHashLength
then BS.unsafeUseAsCStringLen bytestring $ \(outsideCiphertextPtr, outsideCiphertextLength) -> do
ciphertextForeignPtr <- BS.mallocByteString @CChar outsideCiphertextLength -- The foreign pointer that will receive the ciphertext data.
Foreign.withForeignPtr ciphertextForeignPtr $ \ciphertextPtr ->
-- We copy bytes from 'outsideCiphertextPtr' to 'ciphertextPtr'.
Foreign.copyArray ciphertextPtr outsideCiphertextPtr outsideCiphertextLength
pure $
Right $
Hash
(fromIntegral @Int @CULLong outsideHashLength - fromIntegral @CSize @CULLong cryptoSecretboxMACBytes)
(Foreign.castForeignPtr @CChar @CUChar hashForeignPtr)
else pure $ Left $ Text.pack "Hash is too short"
Ciphertext
(fromIntegral @Int @CULLong outsideCiphertextLength - fromIntegral @CSize @CULLong cryptoSecretboxMACBytes)
(Foreign.castForeignPtr @CChar @CUChar ciphertextForeignPtr)
else pure $ Left $ Text.pack "Ciphertext is too short"
Left msg -> pure $ Left msg

-- | Convert a 'Hash' to a hexadecimal-encoded 'Text'.
-- | Convert a 'Ciphertext' to a hexadecimal-encoded 'Text'.
--
-- ⚠️ Be prudent as to where you store it!
--
-- @since 0.0.1.0
hashToHexText :: Hash -> Text
hashToHexText = Base16.extractBase16 . Base16.encodeBase16 . hashToBinary
ciphertextToHexText :: Ciphertext -> Text
ciphertextToHexText = Base16.extractBase16 . Base16.encodeBase16 . ciphertextToBinary

-- | Convert a 'Hash' to a hexadecimal-encoded 'StrictByteString' in constant time.
-- | Convert a 'Ciphertext' to a hexadecimal-encoded 'StrictByteString' in constant time.
--
-- ⚠️ Be prudent as to where you store it!
--
-- @since 0.0.1.0
hashToHexByteString :: Hash -> StrictByteString
hashToHexByteString (Hash messageLength fPtr) =
ciphertextToHexByteString :: Ciphertext -> StrictByteString
ciphertextToHexByteString (Ciphertext messageLength fPtr) =
binaryToHex fPtr (cryptoSecretboxMACBytes + fromIntegral messageLength)

-- | Convert a 'Hash' to a binary 'StrictByteString'.
-- | Convert a 'Ciphertext' to a binary 'StrictByteString'.
--
-- ⚠️ Be prudent as to where you store it!
--
-- @since 0.0.1.0
hashToBinary :: Hash -> StrictByteString
hashToBinary (Hash messageLength fPtr) =
ciphertextToBinary :: Ciphertext -> StrictByteString
ciphertextToBinary (Ciphertext messageLength fPtr) =
BS.fromForeignPtr0
(Foreign.castForeignPtr fPtr)
(fromIntegral messageLength + fromIntegral cryptoSecretboxMACBytes)

-- | Create an authenticated hash from a message, a secret key,
-- | Create an authenticated ciphertext from a message, a secret key,
-- and a one-time cryptographic nonce that must never be re-used with the same
-- secret key to encrypt another message.
--
Expand All @@ -365,46 +366,46 @@ encrypt
-- ^ Message to encrypt.
-> SecretKey
-- ^ Secret key generated with 'newSecretKey'.
-> IO (Nonce, Hash)
-> IO (Nonce, Ciphertext)
encrypt message (SecretKey secretKeyForeignPtr) =
BS.unsafeUseAsCStringLen message $ \(cString, cStringLen) -> do
(Nonce nonceForeignPtr) <- newNonce
hashForeignPtr <-
ciphertextForeignPtr <-
Foreign.mallocForeignPtrBytes
(cStringLen + fromIntegral cryptoSecretboxMACBytes)
Foreign.withForeignPtr hashForeignPtr $ \hashPtr ->
Foreign.withForeignPtr ciphertextForeignPtr $ \ciphertextPtr ->
Foreign.withForeignPtr secretKeyForeignPtr $ \secretKeyPtr ->
Foreign.withForeignPtr nonceForeignPtr $ \noncePtr -> do
void $
cryptoSecretboxEasy
hashPtr
ciphertextPtr
(Foreign.castPtr @CChar @CUChar cString)
(fromIntegral @Int @CULLong cStringLen)
noncePtr
secretKeyPtr
let hash = Hash (fromIntegral @Int @CULLong cStringLen) hashForeignPtr
pure (Nonce nonceForeignPtr, hash)
let ciphertext = Ciphertext (fromIntegral @Int @CULLong cStringLen) ciphertextForeignPtr
pure (Nonce nonceForeignPtr, ciphertext)

-- | Decrypt a hashed and authenticated message with the shared secret key and the one-time cryptographic nonce.
-- | Decrypt an encrypted and authenticated message with the shared secret key and the one-time cryptographic nonce.
--
-- @since 0.0.1.0
decrypt
:: Hash
:: Ciphertext
-- ^ Encrypted message you want to decrypt.
-> SecretKey
-- ^ Secret key used for encrypting the original message.
-> Nonce
-- ^ Nonce used for encrypting the original message.
-> Maybe StrictByteString
decrypt Hash{messageLength, hashForeignPtr} (SecretKey secretKeyForeignPtr) (Nonce nonceForeignPtr) = unsafeDupablePerformIO $ do
decrypt Ciphertext{messageLength, ciphertextForeignPtr} (SecretKey secretKeyForeignPtr) (Nonce nonceForeignPtr) = unsafeDupablePerformIO $ do
messagePtr <- Foreign.mallocBytes (fromIntegral @CULLong @Int messageLength)
Foreign.withForeignPtr hashForeignPtr $ \hashPtr ->
Foreign.withForeignPtr ciphertextForeignPtr $ \ciphertextPtr ->
Foreign.withForeignPtr secretKeyForeignPtr $ \secretKeyPtr ->
Foreign.withForeignPtr nonceForeignPtr $ \noncePtr -> do
result <-
cryptoSecretboxOpenEasy
messagePtr
hashPtr
ciphertextPtr
(messageLength + fromIntegral cryptoSecretboxMACBytes)
noncePtr
secretKeyPtr
Expand Down
12 changes: 6 additions & 6 deletions sel/test/Test/SecretKey/Cipher.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec =
[ testCase "Encrypt a message with a secret key and a nonce" testEncryptMessage
, testCase "Round-trip nonce serialisation" testNonceSerdeRoundtrip
, testCase "Round-trip secret key serialisation" testSecretKeySerdeRoundtrip
, testCase "Round-trip hash serialisation" testHashSerdeRoundtrip
, testCase "Round-trip ciphertext serialisation" testCiphertextSerdeRoundtrip
]

testEncryptMessage :: Assertion
Expand All @@ -41,9 +41,9 @@ testSecretKeySerdeRoundtrip = do
secretKey2 <- assertRight $ secretKeyFromHexByteString . unsafeSecretKeyToHexByteString $ secretKey
assertEqual "Roundtripping secret key" secretKey secretKey2

testHashSerdeRoundtrip :: Assertion
testHashSerdeRoundtrip = do
testCiphertextSerdeRoundtrip :: Assertion
testCiphertextSerdeRoundtrip = do
secretKey <- newSecretKey
(_, hash) <- encrypt "" secretKey
hash2 <- assertRight $ hashFromHexByteString . hashToHexByteString $ hash
assertEqual "Roundtripping hash" hash hash2
(_, ciphertext) <- encrypt "" secretKey
ciphertext2 <- assertRight $ ciphertextFromHexByteString . ciphertextToHexByteString $ ciphertext
assertEqual "Roundtripping ciphertext" ciphertext ciphertext2