packages feed

crypton 2.1.0 → 2.1.1

raw patch · 22 files changed

+1021/−30 lines, 22 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Crypto.Cipher.AES.GCM: decryptWithTag :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba) => Context -> nonce -> aad -> ba -> Int -> (ba, AuthTag)
+ Crypto.Cipher.ChaCha.Poly1305: data Context
+ Crypto.Cipher.ChaCha.Poly1305: decrypt :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba) => Context -> nonce -> aad -> ba -> Int -> Maybe ba
+ Crypto.Cipher.ChaCha.Poly1305: decryptWithTag :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba) => Context -> nonce -> aad -> ba -> Int -> CryptoFailable (ba, AuthTag)
+ Crypto.Cipher.ChaCha.Poly1305: encrypt :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArrayAccess ba, ByteArray output) => Context -> nonce -> aad -> ba -> Int -> CryptoFailable output
+ Crypto.Cipher.ChaCha.Poly1305: instance Control.DeepSeq.NFData Crypto.Cipher.ChaCha.Poly1305.Context
+ Crypto.Cipher.ChaCha.Poly1305: newContext :: ByteArrayAccess key => key -> CryptoFailable Context
+ Crypto.PubKey.ECDSA: deterministicNonce :: (EllipticCurveECDSA curve, HashAlgorithm hashDRG, HashAlgorithm hashDigest) => proxy curve -> hashDRG -> PrivateKey curve -> Digest hashDigest -> (Scalar curve -> Maybe a) -> a
+ Crypto.PubKey.ECDSA: signDeterministic :: (EllipticCurveECDSA curve, HashAlgorithm hashDRG, HashAlgorithm hash, ByteArrayAccess msg) => proxy curve -> hashDRG -> PrivateKey curve -> hash -> msg -> Signature curve
+ Crypto.PubKey.ECDSA: signDigestDeterministic :: (EllipticCurveECDSA curve, HashAlgorithm hashDRG, HashAlgorithm hashDigest) => proxy curve -> hashDRG -> PrivateKey curve -> Digest hashDigest -> Signature curve

Files

CHANGELOG.md view
@@ -1,5 +1,40 @@ # CHANGELOG for crypton +## 2.1.1++* docs(rsa): the haddock says what the optional blinder covers -- that the+  private exponent is not what is at risk, `expSafe` keeping its value out of+  the work, and that what a blinder covers is the input, which without one is+  the ciphertext as it arrived and so a number an attacker may have chosen.+  The eight places taking a `Maybe Blinder` point at t'Blinder' rather than+  repeating half of it; the four in `Crypto.PubKey.RSA.PSS` said nothing at+  all before++* feat(chachapoly): `Crypto.Cipher.ChaCha.Poly1305`, which does a whole+  ChaCha20-Poly1305 message in one call, the shape `Crypto.Cipher.AES.GCM`+  has.  `Crypto.Cipher.ChaChaPoly1305` takes a message in steps, which is+  right when it arrives in pieces and is eight foreign calls and the+  allocations between them when it was already whole.  Measured on an Apple+  M4 through the Haskell interface, a 100-byte message goes 0.97 -> 0.415+  microseconds and a 1400-byte one 2.89 -> 2.36.  The nonce is the twelve+  bytes RFC 8439 defines; eight is the other ChaCha construction and is+  refused rather than quietly encrypted under a scheme nobody asked for++* feat(gcm): `Crypto.Cipher.AES.GCM.decryptWithTag`, which decrypts and hands+  back the tag it computed rather than comparing it.  For a protocol that+  carries the tag apart from the ciphertext, where `decrypt` -- which wants+  the two together -- does not fit.  It returns an `AuthTag`, whose `Eq` is a+  constant-time comparison, so the safe way to use it is also the obvious one++* feat(ecdsa): `Crypto.PubKey.ECDSA` gains the deterministic nonce of RFC+  6979, which `Crypto.PubKey.ECC.ECDSA` already had.  The fast module was the+  one without it, so moving to it for the speed meant giving up the one+  protection against the mistake that hands over an ECDSA private key.  Three+  new names: `deterministicNonce`, and `signDeterministic` and+  `signDigestDeterministic` over it.  Held to the implementation in+  `Crypto.PubKey.ECC.ECDSA`, which is itself held to the vectors in the RFC,+  on P-256, P-384 and P-521 with SHA-1 through SHA-512+ ## 2.1.0  * perf(p256): a signed five-bit window for the variable-point scalar
Crypto/Cipher/AES/GCM.hs view
@@ -40,6 +40,7 @@     newContext,     encrypt,     decrypt,+    decryptWithTag,      -- * Header protection     HeaderKey,@@ -51,11 +52,13 @@     AES,     AESGCMKey,     gcmFullDecrypt,+    gcmFullDecryptTag,     gcmFullEncrypt,     gcmFullEncryptMask,     gcmKeyInit,     initAES,  )+import Crypto.Cipher.Types (AuthTag) import Crypto.Error import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess) import qualified Crypto.Internal.ByteArray as B@@ -119,6 +122,30 @@     | otherwise = gcmFullDecrypt aes gk nonce aad body tag   where     (body, tag) = B.splitAt (B.length input - taglen) input++-- | Decrypt one message, the tag kept apart, and hand back the tag this end+-- computed.+--+-- For a caller whose protocol hands it the tag separately from the+-- ciphertext, so that 'decrypt' -- which wants the two together and compares+-- them itself -- does not fit.  Compare the two tags with '=='; the 'Eq'+-- instance of t'AuthTag' is a constant-time comparison, and taking them apart+-- to compare the bytes is how this goes wrong.+--+-- Nothing here says whether the message is authentic.  Until the comparison+-- is made and has come out equal, what this returns is not plaintext, it is+-- what the ciphertext turns into, and a caller must not act on it.+{-# INLINABLE decryptWithTag #-}+decryptWithTag+    :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba)+    => Context+    -> nonce+    -> aad+    -> ba+    -> Int+    -> (ba, AuthTag)+decryptWithTag (Context aes gk) nonce aad input taglen =+    gcmFullDecryptTag aes gk nonce aad input taglen  ---------------------------------------------------------------- 
Crypto/Cipher/AES/Primitive.hs view
@@ -45,6 +45,7 @@     gcmFullEncrypt,     gcmFullEncryptMask,     gcmFullDecrypt,+    gcmFullDecryptTag,     gcmAeadInit,      -- * Incremental OCB@@ -582,6 +583,50 @@         | B.length input <= shortMessage = c_aes_gcm_full_decrypt_unsafe         | otherwise = c_aes_gcm_full_decrypt +-- | Decrypt one message and hand back the tag that was computed over it,+-- rather than comparing it here.+--+-- For a caller that holds the expected tag in a form of its own and will+-- compare it itself.  Compare the two t'AuthTag's with '==', whose instance+-- for that type is a constant-time comparison; taking them apart and+-- comparing the bytes is how this goes wrong.+--+-- Where the tag simply arrives after the ciphertext, 'gcmFullDecrypt' is the+-- one to use: it compares in C and never puts a tag in the caller's hands.+{-# INLINABLE gcmFullDecryptTag #-}+gcmFullDecryptTag+    :: ( ByteArrayAccess iv+       , ByteArrayAccess aad+       , ByteArrayAccess ba+       , ByteArray output+       )+    => AES -> AESGCMKey -> iv -> aad -> ba -> Int -> (output, AuthTag)+gcmFullDecryptTag ctx (AESGCMKey gk) iv aad input taglen = unsafeDoIO $ do+    (tagbs, out) <- B.allocRet (B.length input) $ \outp ->+        B.alloc taglen $ \tagp ->+            B.withByteArray gk $ \gkp ->+                keyToPtr ctx $ \k ->+                    B.withByteArray iv $ \ivp ->+                        B.withByteArray aad $ \aadp ->+                            B.withByteArray input $ \inp ->+                                call+                                    outp+                                    tagp+                                    (castPtr gkp)+                                    k+                                    ivp+                                    (fromIntegral $ B.length iv)+                                    aadp+                                    (fromIntegral $ B.length aad)+                                    inp+                                    (fromIntegral $ B.length input)+                                    (fromIntegral taglen)+    return (out, AuthTag $ B.convert (tagbs :: B.Bytes))+  where+    call+        | B.length input <= shortMessage = c_aes_gcm_full_decrypt_tag_unsafe+        | otherwise = c_aes_gcm_full_decrypt_tag+ -- | append data which is only going to be authenticated to the GCM context. -- -- needs to happen after initialization and before appending encryption/decryption data.@@ -869,6 +914,36 @@         -> Ptr Word8         -> CUInt         -> IO CInt++foreign import ccall "crypton_aes.h crypton_aes_gcm_full_decrypt_tag"+    c_aes_gcm_full_decrypt_tag+        :: Ptr Word8+        -> Ptr Word8+        -> Ptr AESGCM+        -> Ptr AES+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> CUInt+        -> IO ()++foreign import ccall unsafe "crypton_aes.h crypton_aes_gcm_full_decrypt_tag"+    c_aes_gcm_full_decrypt_tag_unsafe+        :: Ptr Word8+        -> Ptr Word8+        -> Ptr AESGCM+        -> Ptr AES+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> CUInt+        -> IO ()  foreign import ccall unsafe "crypton_aes.h crypton_aes_gcm_full_encrypt"     c_aes_gcm_full_encrypt_unsafe
+ Crypto/Cipher/ChaCha/Poly1305.hs view
@@ -0,0 +1,324 @@+-- |+-- Module      : Crypto.Cipher.ChaCha.Poly1305+-- License     : BSD-style+-- Maintainer  : Kazu Yamamoto <kazu@iij.ad.jp>+-- Stability   : experimental+-- Portability : Good+--+-- ChaCha20-Poly1305 (RFC 8439) a message at a time.+--+-- "Crypto.Cipher.ChaChaPoly1305" takes a message in pieces: a state is+-- started, the additional data appended, the body encrypted and the tag+-- taken, each a step of its own.  That is what a protocol wants when the+-- message arrives in pieces, and it is eight foreign calls and the+-- allocations between them when the message was already whole.+--+-- Here the whole message goes in one call.+--+-- The functions are the same shape as "Crypto.Cipher.AES.GCM", so a protocol+-- that offers both ciphers can hold them the same way.+module Crypto.Cipher.ChaCha.Poly1305 (+    Context,+    newContext,+    encrypt,+    decrypt,+    decryptWithTag,+) where++import Crypto.Cipher.Types (AuthTag (..))+import Crypto.Error+import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)+import qualified Crypto.Internal.ByteArray as B+import Crypto.Internal.Compat (unsafeDoIO)+import Crypto.Internal.Imports+import Data.Word (Word8)+import Foreign.C.Types (CInt (..), CUInt (..))+import Foreign.Ptr (Ptr, plusPtr)++-- | A key, checked once.+--+-- ChaCha20-Poly1305 has nothing to precompute from a key: the one-time+-- Poly1305 key comes from the nonce, so it differs for every message.  This+-- holds the thirty-two bytes and the knowledge that they are thirty-two, and+-- exists so that the interface is the one "Crypto.Cipher.AES.GCM" has.+newtype Context = Context B.ScrubbedBytes++instance NFData Context where+    rnf (Context k) = k `seq` ()++-- | Take a key of 32 bytes.  Any other length is reported as+-- 'CryptoError_KeySizeInvalid'.+newContext :: ByteArrayAccess key => key -> CryptoFailable Context+newContext k+    | B.length k /= 32 = CryptoFailed CryptoError_KeySizeInvalid+    | otherwise = CryptoPassed $ Context (B.convert k)+{-# INLINABLE newContext #-}++-- | Encrypt one message.  The result is the ciphertext with the tag after it,+-- which is the shape 'decrypt' expects.+--+-- The nonce is the twelve bytes RFC 8439 defines; any other length gives+-- 'CryptoError_IvSizeInvalid'.  The tag is at most 16 bytes.+{-# INLINABLE encrypt #-}+encrypt+    :: ( ByteArrayAccess nonce+       , ByteArrayAccess aad+       , ByteArrayAccess ba+       , ByteArray output+       )+    => Context+    -> nonce+    -> aad+    -> ba+    -> Int+    -> CryptoFailable output+encrypt (Context k) nonce aad input taglen+    | not (validNonce nonce) = CryptoFailed CryptoError_IvSizeInvalid+    | badTag taglen = CryptoFailed CryptoError_AuthenticationTagSizeInvalid+    | otherwise =+        CryptoPassed $+            unsafeDoIO $+                B.alloc (B.length input + taglen) $ \out ->+                    B.withByteArray k $ \kp ->+                        B.withByteArray nonce $ \np ->+                            B.withByteArray aad $ \ap ->+                                B.withByteArray input $ \ip ->+                                    (callE (B.length input))+                                        out+                                        (out `plusPtr` B.length input)+                                        (fromIntegral taglen)+                                        kp+                                        np+                                        (fromIntegral $ B.length nonce)+                                        ap+                                        (fromIntegral $ B.length aad)+                                        ip+                                        (fromIntegral $ B.length input)++-- | Decrypt one message, in the shape 'encrypt' produced: the ciphertext with+-- its tag after it.  The tag is compared here, every byte of it whatever the+-- answer, and a message whose tag does not match gives 'Nothing' rather than+-- the plaintext.+--+-- 'Nothing' also comes back when the input is shorter than the tag, or the+-- nonce is not twelve bytes.+{-# INLINABLE decrypt #-}+decrypt+    :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba)+    => Context+    -> nonce+    -> aad+    -> ba+    -> Int+    -> Maybe ba+decrypt (Context k) nonce aad input taglen+    | not (validNonce nonce) = Nothing+    | badTag taglen || B.length input < taglen = Nothing+    | otherwise = unsafeDoIO $ do+        (r, out) <- B.allocRet bodylen $ \outp ->+            B.withByteArray k $ \kp ->+                B.withByteArray nonce $ \np ->+                    B.withByteArray aad $ \ap ->+                        B.withByteArray body $ \ip ->+                            B.withByteArray tag $ \tp ->+                                (callD bodylen)+                                    outp+                                    tp+                                    (fromIntegral taglen)+                                    kp+                                    np+                                    (fromIntegral $ B.length nonce)+                                    ap+                                    (fromIntegral $ B.length aad)+                                    ip+                                    (fromIntegral bodylen)+        return $ if r /= 0 then Just out else Nothing+  where+    bodylen = B.length input - taglen+    (body, tag) = B.splitAt bodylen input++-- | Decrypt one message, the tag kept apart, and hand back the tag this end+-- computed.+--+-- For a caller whose protocol carries the tag separately from the ciphertext,+-- so that 'decrypt' -- which wants the two together and compares them itself+-- -- does not fit.  Compare the two tags with '=='; the 'Eq' instance of+-- t'AuthTag' is a constant-time comparison, and taking them apart to compare+-- the bytes is how this goes wrong.+--+-- Nothing here says whether the message is authentic.  Until the comparison+-- is made and has come out equal, what this returns is not plaintext, it is+-- what the ciphertext turns into, and a caller must not act on it.+{-# INLINABLE decryptWithTag #-}+decryptWithTag+    :: (ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba)+    => Context+    -> nonce+    -> aad+    -> ba+    -> Int+    -> CryptoFailable (ba, AuthTag)+decryptWithTag (Context k) nonce aad input taglen+    | not (validNonce nonce) = CryptoFailed CryptoError_IvSizeInvalid+    | badTag taglen = CryptoFailed CryptoError_AuthenticationTagSizeInvalid+    | otherwise = CryptoPassed $ unsafeDoIO $ do+        (tagbs, out) <- B.allocRet (B.length input) $ \outp ->+            B.alloc taglen $ \tagp ->+                B.withByteArray k $ \kp ->+                    B.withByteArray nonce $ \np ->+                        B.withByteArray aad $ \ap ->+                            B.withByteArray input $ \ip ->+                                (callT (B.length input))+                                    outp+                                    tagp+                                    (fromIntegral taglen)+                                    kp+                                    np+                                    (fromIntegral $ B.length nonce)+                                    ap+                                    (fromIntegral $ B.length aad)+                                    ip+                                    (fromIntegral $ B.length input)+        return (out, AuthTag $ B.convert (tagbs :: B.Bytes))++-- RFC 8439 is the twelve-byte nonce.  ChaCha20 will take eight, but that is+-- the other construction, with a 64-bit block counter, and it is not what+-- this AEAD is defined over -- so it is refused here rather than quietly+-- encrypting under a scheme nobody asked for.+validNonce :: ByteArrayAccess nonce => nonce -> Bool+validNonce n = B.length n == 12++badTag :: Int -> Bool+badTag t = t < 0 || t > 16++-- | An unsafe call keeps a capability for as long as it runs, so it is only+-- for a message short enough that the run is short.  Four kibibytes is what+-- the AES side uses, and it takes in a datagram of any size a network will+-- carry.+shortMessage :: Int+shortMessage = 4096++callE :: Int -> CEncrypt+callE n+    | n <= shortMessage = c_chachapoly_encrypt_unsafe+    | otherwise = c_chachapoly_encrypt++callD :: Int -> CDecrypt+callD n+    | n <= shortMessage = c_chachapoly_decrypt_unsafe+    | otherwise = c_chachapoly_decrypt++callT :: Int -> CEncrypt+callT n+    | n <= shortMessage = c_chachapoly_decrypt_tag_unsafe+    | otherwise = c_chachapoly_decrypt_tag++type CEncrypt =+    Ptr Word8+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> CUInt+    -> IO ()++type CDecrypt =+    Ptr Word8+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> CUInt+    -> Ptr Word8+    -> CUInt+    -> IO CInt++foreign import ccall "crypton_chachapoly.h crypton_chachapoly_encrypt"+    c_chachapoly_encrypt+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO ()++foreign import ccall unsafe "crypton_chachapoly.h crypton_chachapoly_encrypt"+    c_chachapoly_encrypt_unsafe+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO ()++foreign import ccall "crypton_chachapoly.h crypton_chachapoly_decrypt"+    c_chachapoly_decrypt+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO CInt++foreign import ccall unsafe "crypton_chachapoly.h crypton_chachapoly_decrypt"+    c_chachapoly_decrypt_unsafe+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO CInt++foreign import ccall "crypton_chachapoly.h crypton_chachapoly_decrypt_tag"+    c_chachapoly_decrypt_tag+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO ()++foreign import ccall unsafe "crypton_chachapoly.h crypton_chachapoly_decrypt_tag"+    c_chachapoly_decrypt_tag_unsafe+        :: Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> Ptr Word8+        -> CUInt+        -> IO ()
Crypto/PubKey/ECDSA.hs view
@@ -48,6 +48,11 @@     signDigest,     verify,     verifyDigest,++    -- * Deterministic nonces+    deterministicNonce,+    signDeterministic,+    signDigestDeterministic, ) where  import Control.Monad@@ -58,8 +63,10 @@ import Crypto.Hash import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess) import Crypto.Internal.Imports+import Crypto.Number.Generate (generatePrefix) import Crypto.Number.ModArithmetic (inverseFermat) import qualified Crypto.PubKey.ECC.P256 as P256+import Crypto.Random.HmacDRG (initial, update) import Crypto.Random.Types  import Data.Bits@@ -257,6 +264,75 @@ verify prx hashAlg q sig msg = verifyDigest prx q sig (hashWith hashAlg msg)  -- | Truncate a digest based on curve order size.+-- | Deterministic nonce generation according to RFC 6979.+--+-- The nonce is derived from the private key and the message alone, so a+-- signature made this way needs no random number generator and cannot be the+-- one that repeats a nonce -- which, for ECDSA, hands over the private key.+--+-- The hash used to seed the generator is given separately from the one the+-- message was digested with, as RFC 6979 allows.+--+-- The last argument is what to do with a candidate nonce.  It may answer+-- 'Nothing', in which case another candidate is drawn, which is what+-- 'signDigestDeterministic' does for the r or s that comes out zero:+--+-- > deterministicNonce prx SHA256 priv digest (\k -> signDigestWith prx k priv digest)+deterministicNonce+    :: (EllipticCurveECDSA curve, HashAlgorithm hashDRG, HashAlgorithm hashDigest)+    => proxy curve+    -> hashDRG+    -> PrivateKey curve+    -> Digest hashDigest+    -> (Scalar curve -> Maybe a)+    -> a+deterministicNonce prx alg d digest go = fst $ withDRG state run+  where+    state = update seed $ initial alg+    -- RFC 6979 section 3.2 step d: int2octets(x) || bits2octets(h1).  The+    -- second is the truncated digest taken modulo the order, which is what+    -- scalarAdd with zero does, its contract being to reduce there.+    seed =+        B.append (encodeScalar prx d) (encodeScalar prx z)+            :: B.ScrubbedBytes+    z = scalarAdd prx (tHashDigest prx digest) zeroScalar+    zeroScalar = throwCryptoError $ scalarFromInteger prx 0+    run = do+        k <- generatePrefix (curveOrderBits prx)+        case scalarFromInteger prx k of+            CryptoPassed s+                | scalarIsValid prx s -> maybe run pure (go s)+            _ -> run++-- | Sign a digest with a nonce derived from the private key and the digest,+-- as RFC 6979 says, rather than from a random number generator.+signDigestDeterministic+    :: (EllipticCurveECDSA curve, HashAlgorithm hashDRG, HashAlgorithm hashDigest)+    => proxy curve+    -> hashDRG+    -> PrivateKey curve+    -> Digest hashDigest+    -> Signature curve+signDigestDeterministic prx alg d digest =+    deterministicNonce prx alg d digest $ \k -> signDigestWith prx k d digest++-- | Sign a message with a nonce derived from the private key and the message,+-- as RFC 6979 says, rather than from a random number generator.+signDeterministic+    :: ( EllipticCurveECDSA curve+       , HashAlgorithm hashDRG+       , HashAlgorithm hash+       , ByteArrayAccess msg+       )+    => proxy curve+    -> hashDRG+    -> PrivateKey curve+    -> hash+    -> msg+    -> Signature curve+signDeterministic prx alg d hashAlg msg =+    signDigestDeterministic prx alg d (hashWith hashAlg msg)+ tHashDigest     :: (EllipticCurveECDSA curve, HashAlgorithm hash)     => proxy curve -> Digest hash -> Scalar curve
Crypto/PubKey/RSA/OAEP.hs view
@@ -181,10 +181,9 @@  -- | Decrypt a ciphertext using OAEP ----- When the signature is not in a context where an attacker could gain--- information from the timing of the operation, the blinder can be set to None.------ If unsure always set a blinder or use decryptSafer+-- The blinder is optional and 'Nothing' is accepted, but see t'Blinder' for+-- what it covers and when leaving it out is a decision rather than a default.+-- 'decryptSafer' generates one for you. -- -- Following RFC 8017, the ciphertext is rejected unless it is exactly as long -- as the modulus (section 7.1.2, step 1) and its integer representative is
Crypto/PubKey/RSA/PKCS15.hs view
@@ -453,10 +453,9 @@  -- | decrypt message using the private key. ----- When the decryption is not in a context where an attacker could gain--- information from the timing of the operation, the blinder can be set to None.------ If unsure always set a blinder or use decryptSafer+-- The blinder is optional and 'Nothing' is accepted, but see t'Blinder' for+-- what it covers and when leaving it out is a decision rather than a default.+-- 'decryptSafer' generates one for you. -- -- The message is returned un-padded. --@@ -508,10 +507,9 @@  -- | sign message using private key, a hash and its ASN1 description ----- When the signature is not in a context where an attacker could gain--- information from the timing of the operation, the blinder can be set to None.------ If unsure always set a blinder or use signSafer+-- The blinder is optional and 'Nothing' is accepted, but see t'Blinder' for+-- what it covers and when leaving it out is a decision rather than a default.+-- 'signSafer' generates one for you. sign     :: HashAlgorithmASN1 hashAlg     => Maybe Blinder
Crypto/PubKey/RSA/PSS.hs view
@@ -69,6 +69,9 @@ -- | Sign using the PSS parameters and the salt explicitely passed as parameters. -- -- the function ignore SaltLength from the PSS Parameters+--+-- See t'Blinder' for what the optional blinder covers and when leaving it out+-- is a decision rather than a default.  'signSafer' generates one for you. signDigestWithSalt     :: HashAlgorithm hash     => ByteString@@ -103,6 +106,9 @@ -- | Sign using the PSS parameters and the salt explicitely passed as parameters. -- -- the function ignore SaltLength from the PSS Parameters+--+-- See t'Blinder' for what the optional blinder covers and when leaving it out+-- is a decision rather than a default.  'signSafer' generates one for you. signWithSalt     :: HashAlgorithm hash     => ByteString@@ -121,6 +127,9 @@     mHash = hashWith (pssHash params) m  -- | Sign using the PSS Parameters+--+-- See t'Blinder' for what the optional blinder covers and when leaving it out+-- is a decision rather than a default.  'signSafer' generates one for you. sign     :: (HashAlgorithm hash, MonadRandom m)     => Maybe Blinder@@ -137,6 +146,9 @@     return (signWithSalt salt blinder params pk m)  -- | Sign using the PSS Parameters+--+-- See t'Blinder' for what the optional blinder covers and when leaving it out+-- is a decision rather than a default.  'signSafer' generates one for you. signDigest     :: (HashAlgorithm hash, MonadRandom m)     => Maybe Blinder
Crypto/PubKey/RSA/Types.hs view
@@ -27,8 +27,32 @@  import GHC.Generics --- | Blinder which is used to obfuscate the timing--- of the decryption primitive (used by decryption and signing).+-- | A blinder, which keeps the timing of the private key operation from+-- saying anything about the number it was given.+--+-- The private exponent is not what is at risk.  'Crypto.Number.ModArithmetic.expSafe',+-- which the exponentiation goes through, keeps the /value/ of an exponent out+-- of the work it does.+--+-- What a blinder covers is the other side.  Without one, the operation runs+-- on the ciphertext as it arrived, so how long it takes depends on a number+-- an attacker may have chosen and can vary -- which is what a remote timing+-- attack on RSA needs.  With one, the input is multiplied by a random value+-- first and that value divided out afterwards, so the timing carries nothing+-- an attacker can steer.+--+-- Every private key operation here takes a @'Maybe' t'Blinder'@.  The+-- @Safer@ form of each -- 'Crypto.PubKey.RSA.PKCS15.decryptSafer',+-- 'Crypto.PubKey.RSA.PKCS15.signSafer' and their kind -- generates one and is+-- the one to reach for.  Pass 'Nothing' only where the input is not attacker+-- controlled and you have decided that it is not.+--+-- A blinder costs one more exponentiation, by the public exponent, which is+-- the cheap direction: measured on an Apple M4, PKCS#1 v1.5 signing goes from+-- about 601 to about 620 microseconds.+--+-- Use a blinder once.  'Crypto.PubKey.RSA.generateBlinder' makes a fresh one;+-- carrying one across operations is not what it is for. data Blinder = Blinder !Integer !Integer     deriving (Show, Eq) 
README.md view
@@ -43,6 +43,32 @@     ghci> processorOptions     [AESNI,PCLMUL] +RSA is the other place to know about, and there the choice is the caller's.+The private key operations in `Crypto.PubKey.RSA.PKCS15`, `.OAEP` and `.PSS`+take a `Maybe Blinder`, and `Nothing` is no harder to write than the safe+form:++    decrypt     :: Maybe Blinder -> PrivateKey -> ByteString -> ...+    decryptSafer :: MonadRandom m => PrivateKey -> ByteString -> m ...++The exponent itself is not what is at risk.  `expSafe` keeps the *value* of an+exponent out of the work it does, so the private exponent does not leak+through the exponentiation.  What a blinder covers is the other side: without+one, the operation runs on the ciphertext the caller was handed, so how long+it takes depends on a number an attacker may have chosen and can vary.  That+is what a remote timing attack on RSA needs.  With a blinder the input is+multiplied by a random value first and the result divided out afterwards, so+the timing carries nothing an attacker can steer.++`decryptSafer` and `signSafer` generate the blinder themselves and are the+ones to reach for.  Pass `Nothing` only where the input is not attacker+controlled and you have decided that it is not.++The RSA rows in the tables below are the unblinded path.  A blinder costs one+more exponentiation, by the public exponent, which is the cheap direction:+measured on the M4, signing goes from about 601 to about 620 microseconds,+three per cent.+ Performance ----------- 
cbits/aes/armv8.c view
@@ -434,20 +434,21 @@                                     aes_key *key, const uint8_t *nonce,                                     const uint8_t *aad, uint32_t aadlen,                                     const uint8_t *in, uint32_t inlen,-                                    const uint8_t *tag, uint32_t taglen)+                                    const uint8_t *tag, uint32_t taglen,+                                    uint8_t *outtag) { 	switch (key->strength) { 	case 0: 		return crypton_aes_armv8_gcm_fused_dec128(out, ht, key, nonce, 		                                          aad, aadlen, in, inlen,-		                                          tag, taglen);+		                                          tag, taglen, outtag); 	case 1: 		return crypton_aes_armv8_gcm_fused_dec192(out, ht, key, nonce, 		                                          aad, aadlen, in, inlen,-		                                          tag, taglen);+		                                          tag, taglen, outtag); 	default: 		return crypton_aes_armv8_gcm_fused_dec256(out, ht, key, nonce, 		                                          aad, aadlen, in, inlen,-		                                          tag, taglen);+		                                          tag, taglen, outtag); 	} }
cbits/aes/armv8_impl.c view
@@ -664,7 +664,8 @@                                            aes_key *key, const uint8_t *nonce,                                            const uint8_t *aad, uint32_t aadlen,                                            const uint8_t *in, uint32_t inlen,-                                           const uint8_t *tagp, uint32_t taglen)+                                           const uint8_t *tagp, uint32_t taglen,+                                           uint8_t *outtag) { 	const uint8_t *rk = FWD(key); 	uint8x16_t s[WAY];@@ -733,6 +734,11 @@ 	FG_ABSORB(vld1q_u8(lenb));  	vst1q_u8(want, veorq_u8(tag, ek0));+	if (outtag) {+		/* The caller holds the expected tag and will compare it itself. */+		memcpy(outtag, want, taglen);+		return 1;+	} 	for (i = 0; i < taglen; i++) 		diff |= (uint8_t) (want[i] ^ tagp[i]); 	return diff == 0;
cbits/aes/gcm_fused_x86.c view
@@ -843,7 +843,8 @@                                   const aes_key *key, const uint8_t *nonce,                                   const uint8_t *aad, size_t aadlen,                                   const uint8_t *in, size_t inlen,-                                  const uint8_t *tag, size_t taglen)+                                  const uint8_t *tag, size_t taglen,+                                  uint8_t *outtag) {     const uint8_t *rk = key->data;     const int rounds = key->nbr;@@ -979,6 +980,11 @@     {         uint8_t got[16];         _mm_storeu_si128((__m128i *) got, want);+        if (outtag) {+            /* The caller holds the expected tag and will compare it itself. */+            memcpy(outtag, got, taglen);+            return 1;+        }         for (i = 0; i < taglen; i++)             diff |= (uint8_t) (got[i] ^ tag[i]);     }
cbits/aes/gcm_fused_x86.h view
@@ -49,6 +49,7 @@                               const aes_key *key, const uint8_t *nonce,                               const uint8_t *aad, size_t aadlen,                               const uint8_t *in, size_t inlen,-                              const uint8_t *tag, size_t taglen);+                              const uint8_t *tag, size_t taglen,+                              uint8_t *outtag);  #endif
cbits/crypton_aes.c view
@@ -65,7 +65,8 @@                                     aes_key *key, const uint8_t *nonce,                                     const uint8_t *aad, uint32_t aadlen,                                     const uint8_t *in, uint32_t inlen,-                                    const uint8_t *tag, uint32_t taglen);+                                    const uint8_t *tag, uint32_t taglen,+                                    uint8_t *outtag); void crypton_aes_armv8_gcm_fused(uint8_t *out, const block128 *ht,                                  aes_key *key, const uint8_t *nonce,                                  const uint8_t *aad, uint32_t aadlen,@@ -752,11 +753,19 @@  * caller: returns 1 when it matches and 0 when it does not, comparing every  * byte either way.  The plaintext is written whatever the answer, so a caller  * that gets 0 must not use it. */-int crypton_aes_gcm_full_decrypt(uint8_t *output, const aes_gcm_key *gcmkey, aes_key *key,-                                 uint8_t *iv, uint32_t ivlen,-                                 uint8_t *aad, uint32_t aadlen,-                                 uint8_t *input, uint32_t length,-                                 const uint8_t *tag, uint32_t taglen)+/* Decrypt, and either compare the tag or hand it back.+ *+ * With outtag NULL this is the verifying form: the tag is compared here, a+ * byte at a time over its whole length whichever way the answer goes, and the+ * answer is the return value.  With outtag not NULL the computed tag is+ * written there instead and the return value is 1 -- for a caller that holds+ * the expected tag in a form of its own and will compare it itself. */+static int gcm_full_decrypt(uint8_t *output, const aes_gcm_key *gcmkey, aes_key *key,+                            uint8_t *iv, uint32_t ivlen,+                            uint8_t *aad, uint32_t aadlen,+                            uint8_t *input, uint32_t length,+                            const uint8_t *tag, uint32_t taglen,+                            uint8_t *outtag) { 	aes_gcm gcm; 	uint8_t expected[16];@@ -773,7 +782,7 @@ 	    && crypton_aes_cpu_options[CPU_PCLMUL]) 		return crypton_gcm_fused_decrypt(output, &gcmkey->fused, key, 		                                 iv, aad, aadlen, input,-		                                 length, tag, taglen);+		                                 length, tag, taglen, outtag); #endif #ifdef WITH_ARMV8_CRYPTO 	if (ivlen == 12@@ -781,7 +790,8 @@ 	    && crypton_aes_cpu_options[CPU_PCLMUL]) 		return crypton_aes_armv8_gcm_fused_dec(output, gcmkey->gcm.htable, 		                                       key, iv, aad, aadlen,-		                                       input, length, tag, taglen);+		                                       input, length, tag, taglen,+		                                       outtag); #endif 	memcpy(gcm.htable, gcmkey->gcm.htable, sizeof(gcm.htable)); 	gcm_message_init(&gcm, iv, ivlen);@@ -791,9 +801,34 @@ 		crypton_aes_gcm_decrypt(output, &gcm, key, input, length); 	crypton_aes_gcm_finish(expected, &gcm, key); +	if (outtag) {+		memcpy(outtag, expected, taglen);+		return 1;+	} 	for (i = 0; i < taglen; i++) 		diff |= (uint8_t) (expected[i] ^ tag[i]); 	return diff == 0;+}++int crypton_aes_gcm_full_decrypt(uint8_t *output, const aes_gcm_key *gcmkey, aes_key *key,+                                 uint8_t *iv, uint32_t ivlen,+                                 uint8_t *aad, uint32_t aadlen,+                                 uint8_t *input, uint32_t length,+                                 const uint8_t *tag, uint32_t taglen)+{+	return gcm_full_decrypt(output, gcmkey, key, iv, ivlen, aad, aadlen,+	                        input, length, tag, taglen, NULL);+}++void crypton_aes_gcm_full_decrypt_tag(uint8_t *output, uint8_t *outtag,+                                      const aes_gcm_key *gcmkey, aes_key *key,+                                      uint8_t *iv, uint32_t ivlen,+                                      uint8_t *aad, uint32_t aadlen,+                                      uint8_t *input, uint32_t length,+                                      uint32_t taglen)+{+	(void) gcm_full_decrypt(output, gcmkey, key, iv, ivlen, aad, aadlen,+	                        input, length, NULL, taglen, outtag); }  static inline uint8_t ccm_b0_flags(uint32_t has_adata, uint32_t m, uint32_t l)
cbits/crypton_aes.h view
@@ -168,6 +168,12 @@                                  uint8_t *aad, uint32_t aadlen,                                  uint8_t *input, uint32_t length,                                  const uint8_t *tag, uint32_t taglen);+void crypton_aes_gcm_full_decrypt_tag(uint8_t *output, uint8_t *outtag,+                                      const aes_gcm_key *gcmkey, aes_key *key,+                                      uint8_t *iv, uint32_t ivlen,+                                      uint8_t *aad, uint32_t aadlen,+                                      uint8_t *input, uint32_t length,+                                      uint32_t taglen); void crypton_aes_gcm_aad(aes_gcm *gcm, uint8_t *input, uint32_t length); void crypton_aes_gcm_encrypt(uint8_t *output, aes_gcm *gcm, aes_key *key, uint8_t *input, uint32_t length); void crypton_aes_gcm_decrypt(uint8_t *output, aes_gcm *gcm, aes_key *key, uint8_t *input, uint32_t length);
+ cbits/crypton_chachapoly.c view
@@ -0,0 +1,156 @@+/*+ * Copyright (c) 2026 Kazu Yamamoto+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ *    notice, this list of conditions and the following disclaimer.+ * 2. 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.+ *+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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.+ */++#include <stdint.h>+#include <string.h>++#include "crypton_chacha.h"+#include "crypton_chachapoly.h"+#include "crypton_poly1305.h"++/* RFC 8439.  The one-time Poly1305 key is the first 32 bytes of the ChaCha20+ * keystream at counter 0; a whole 64-byte block is generated so the counter+ * lands on 1, which is where the message starts. */+static void chachapoly_start(crypton_chacha_context *cctx, poly1305_ctx *pctx,+                             const uint8_t *key,+                             const uint8_t *nonce, uint32_t noncelen)+{+	uint8_t block[64];++	crypton_chacha_init(cctx, 20, 32, key, noncelen, nonce);+	crypton_chacha_generate(block, cctx, sizeof(block));+	crypton_poly1305_init(pctx, (poly1305_key *) block);+	memset(block, 0, sizeof(block));+}++/* Poly1305 over an associated or encrypted part, then zeros up to the next+ * multiple of sixteen. */+static void absorb_padded(poly1305_ctx *pctx, const uint8_t *p, uint32_t len)+{+	static const uint8_t zeros[16] = {0};+	uint32_t rem;++	if (len)+		crypton_poly1305_update(pctx, (uint8_t *) p, len);+	rem = len % 16;+	if (rem)+		crypton_poly1305_update(pctx, (uint8_t *) zeros, 16 - rem);+}++/* The two lengths, little endian, eight bytes each, which is what the tag+ * ends on. */+static void absorb_lengths(poly1305_ctx *pctx, uint32_t aadlen, uint32_t inlen)+{+	uint8_t lens[16];+	int i;++	for (i = 0; i < 8; i++)+		lens[i] = (uint8_t) (((uint64_t) aadlen) >> (8 * i));+	for (i = 0; i < 8; i++)+		lens[8 + i] = (uint8_t) (((uint64_t) inlen) >> (8 * i));+	crypton_poly1305_update(pctx, lens, sizeof(lens));+}++void crypton_chachapoly_encrypt(uint8_t *out, uint8_t *tag, uint32_t taglen,+                                const uint8_t *key,+                                const uint8_t *nonce, uint32_t noncelen,+                                const uint8_t *aad, uint32_t aadlen,+                                const uint8_t *input, uint32_t inlen)+{+	crypton_chacha_context cctx;+	poly1305_ctx pctx;+	poly1305_mac mac;++	chachapoly_start(&cctx, &pctx, key, nonce, noncelen);+	absorb_padded(&pctx, aad, aadlen);+	if (inlen)+		crypton_chacha_combine(out, &cctx, input, inlen);+	/* what the tag covers is the ciphertext, which is now in out */+	absorb_padded(&pctx, out, inlen);+	absorb_lengths(&pctx, aadlen, inlen);+	crypton_poly1305_finalize(mac, &pctx);+	memcpy(tag, mac, taglen);++	memset(&cctx, 0, sizeof(cctx));+	memset(&pctx, 0, sizeof(pctx));+}++/* Shared by the two decrypting entry points: with outtag NULL the tag is+ * compared here and the answer returned, otherwise it is written there. */+static int chachapoly_decrypt(uint8_t *out, const uint8_t *tag, uint32_t taglen,+                              uint8_t *outtag, const uint8_t *key,+                              const uint8_t *nonce, uint32_t noncelen,+                              const uint8_t *aad, uint32_t aadlen,+                              const uint8_t *input, uint32_t inlen)+{+	crypton_chacha_context cctx;+	poly1305_ctx pctx;+	poly1305_mac mac;+	uint8_t diff = 0;+	uint32_t i;++	chachapoly_start(&cctx, &pctx, key, nonce, noncelen);+	absorb_padded(&pctx, aad, aadlen);+	/* here the ciphertext is the input, so the tag can be taken before the+	 * plaintext is written and out may alias input */+	absorb_padded(&pctx, input, inlen);+	absorb_lengths(&pctx, aadlen, inlen);+	crypton_poly1305_finalize(mac, &pctx);++	if (inlen)+		crypton_chacha_combine(out, &cctx, input, inlen);++	memset(&cctx, 0, sizeof(cctx));+	memset(&pctx, 0, sizeof(pctx));++	if (outtag) {+		memcpy(outtag, mac, taglen);+		return 1;+	}+	for (i = 0; i < taglen; i++)+		diff |= (uint8_t) (mac[i] ^ tag[i]);+	return diff == 0;+}++int crypton_chachapoly_decrypt(uint8_t *out,+                               const uint8_t *tag, uint32_t taglen,+                               const uint8_t *key,+                               const uint8_t *nonce, uint32_t noncelen,+                               const uint8_t *aad, uint32_t aadlen,+                               const uint8_t *input, uint32_t inlen)+{+	return chachapoly_decrypt(out, tag, taglen, NULL, key, nonce, noncelen,+	                          aad, aadlen, input, inlen);+}++void crypton_chachapoly_decrypt_tag(uint8_t *out, uint8_t *outtag,+                                    uint32_t taglen, const uint8_t *key,+                                    const uint8_t *nonce, uint32_t noncelen,+                                    const uint8_t *aad, uint32_t aadlen,+                                    const uint8_t *input, uint32_t inlen)+{+	(void) chachapoly_decrypt(out, NULL, taglen, outtag, key, nonce,+	                          noncelen, aad, aadlen, input, inlen);+}
+ cbits/crypton_chachapoly.h view
@@ -0,0 +1,62 @@+/*+ * Copyright (c) 2026 Kazu Yamamoto+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ *    notice, this list of conditions and the following disclaimer.+ * 2. 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.+ *+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS 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 AUTHORS 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.+ */++#ifndef CRYPTON_CHACHAPOLY_H+#define CRYPTON_CHACHAPOLY_H++#include <stdint.h>++/* ChaCha20-Poly1305 (RFC 8439) as one call.+ *+ * The pieces are the ChaCha20 and Poly1305 already here; what these do is+ * hold them together, which the Haskell above used to do at the cost of eight+ * foreign calls and the allocations between them.+ *+ * The nonce is the twelve bytes RFC 8439 defines.  taglen is at most 16.+ */++void crypton_chachapoly_encrypt(uint8_t *out, uint8_t *tag, uint32_t taglen,+                                const uint8_t *key,+                                const uint8_t *nonce, uint32_t noncelen,+                                const uint8_t *aad, uint32_t aadlen,+                                const uint8_t *input, uint32_t inlen);++/* Decrypt and compare, a byte at a time over the whole tag whichever way the+ * answer goes.  Returns non-zero when the tag matched. */+int crypton_chachapoly_decrypt(uint8_t *out,+                               const uint8_t *tag, uint32_t taglen,+                               const uint8_t *key,+                               const uint8_t *nonce, uint32_t noncelen,+                               const uint8_t *aad, uint32_t aadlen,+                               const uint8_t *input, uint32_t inlen);++/* Decrypt and hand the computed tag back rather than comparing it. */+void crypton_chachapoly_decrypt_tag(uint8_t *out, uint8_t *outtag,+                                    uint32_t taglen, const uint8_t *key,+                                    const uint8_t *nonce, uint32_t noncelen,+                                    const uint8_t *aad, uint32_t aadlen,+                                    const uint8_t *input, uint32_t inlen);++#endif
crypton.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               crypton-version:            2.1.0+version:            2.1.1 license:            BSD-3-Clause license-file:       LICENSE copyright:          Vincent Hanquez <vincent@snarc.org>@@ -149,6 +149,7 @@         Crypto.Cipher.Camellia         Crypto.Cipher.CAST5         Crypto.Cipher.ChaCha+        Crypto.Cipher.ChaCha.Poly1305         Crypto.Cipher.ChaChaPoly1305         Crypto.Cipher.DES         Crypto.Cipher.RC4@@ -245,6 +246,7 @@         cbits/crypton_blowfish.c         cbits/crypton_camellia.c         cbits/crypton_chacha.c+        cbits/crypton_chachapoly.c         cbits/crypton_cpu.c         cbits/crypton_des.c         cbits/crypton_ecc.c
tests/BlockCipher/AESSpec.hs view
@@ -237,6 +237,19 @@         run "AES-128" KATGCM.vectors_aes128_enc         run "AES-192" KATGCM.vectors_aes192_enc         run "AES-256" KATGCM.vectors_aes256_enc+    describe "decryptWithTag hands back the tag encrypt made" $ do+        runTag "AES-128" KATGCM.vectors_aes128_enc+        runTag "AES-192" KATGCM.vectors_aes192_enc+        runTag "AES-256" KATGCM.vectors_aes256_enc+    it "decryptWithTag gives a different tag for a tampered ciphertext" $+        let ctx = ctx16+            sealed = GCM.encrypt ctx iv16 B.empty message 16 :: B.ByteString+            body = B.take (B.length sealed - 16) sealed+            tag = AuthTag (BA.convert (B.drop (B.length sealed - 16) sealed))+            (_, tag') =+                GCM.decryptWithTag ctx iv16 B.empty (flipFirst body) 16+                    :: (B.ByteString, AuthTag)+         in tag' `shouldSatisfy` (/= tag)     describe "refuses a message that was interfered with" $ do         it "a flipped bit in the tag" $ tamper (\(c, t) -> (c, flipFirst t))         it "a flipped bit in the ciphertext" $ tamper (\(c, t) -> (flipFirst c, t))@@ -260,6 +273,19 @@             , let sealed = GCM.encrypt ctx iv aad input taglen :: B.ByteString             , sealed /= out `B.append` tag                 || GCM.decrypt ctx iv aad sealed taglen /= Just input+            ]+                `shouldBe` []+    -- The tag decryptWithTag computes has to be the one encrypt appended, and+    -- the body it returns the one decrypt returns, over the same vectors.+    runTag name vs =+        it name $+            [ (key, iv)+            | (key, iv, aad, input, out, taglen, tag) <- vs+            , let ctx = throwCryptoError (GCM.newContext key)+            , let (body, tag') =+                    GCM.decryptWithTag ctx iv aad out taglen+                        :: (B.ByteString, AuthTag)+            , body /= input || tag' /= AuthTag (BA.convert tag)             ]                 `shouldBe` []     ctx16 = throwCryptoError (GCM.newContext (B.replicate 16 0x2b))
tests/ECDSASpec.hs view
@@ -79,8 +79,42 @@             ECC.signExtendedDigestWith k key digest >>= \s -> pure $ ECC.sign_s (ECC.signature s) <= n `div` 2     pure $ propertyHold [eqTest "normalized" (Just True) check] +-- | The deterministic nonce of RFC 6979, against the implementation in+-- Crypto.PubKey.ECC.ECDSA, which is itself held to the vectors in the RFC by+-- tests/PubKey/ECDSASpec.hs.  Agreeing with it is agreeing with those.+propertyDeterministic+    :: HashAlgorithm hash => hash -> Curve -> ArbitraryBS0_2901 -> Gen Bool+propertyDeterministic hashAlg (Curve c curve _) (ArbitraryBS0_2901 msg) = do+    d <- arbitraryScalar curve+    let prx = Just c -- using Maybe as Proxy+        privECC = ECC.PrivateKey curve d+        privECDSA = throwCryptoError $ ECDSA.scalarFromInteger prx d+        pubECDSA = ECDSA.toPublic prx privECDSA+        digest = hashWith hashAlg msg+        kECC = ECC.deterministicNonce hashAlg privECC digest Just+        kECDSA =+            ECDSA.deterministicNonce prx hashAlg privECDSA digest Just+        sigECDSA = ECDSA.signDeterministic prx hashAlg privECDSA hashAlg msg+        sigWithK = fromJust $ ECDSA.signWith prx kECDSA privECDSA hashAlg msg+    pure $+        propertyHold+            [ eqTest "nonce" kECC (ECDSA.scalarToInteger prx kECDSA)+            , eqTest "signature matches signWith" sigWithK sigECDSA+            , eqTest+                "signature verifies"+                True+                (ECDSA.verify prx hashAlg pubECDSA sigECDSA msg)+            ]+ spec :: Spec spec = do+    modifyMaxSuccess (const 5) $+        describe "RFC 6979 deterministic nonce" $ do+            prop "SHA1" $ propertyDeterministic SHA1+            prop "SHA224" $ propertyDeterministic SHA224+            prop "SHA256" $ propertyDeterministic SHA256+            prop "SHA384" $ propertyDeterministic SHA384+            prop "SHA512" $ propertyDeterministic SHA512     modifyMaxSuccess (const 5) $         describe "verification" $ do             prop "SHA1" $ propertyECDSA SHA1
tests/StreamCipher/ChaChaPoly1305Spec.hs view
@@ -2,12 +2,14 @@  module StreamCipher.ChaChaPoly1305Spec where +import qualified Crypto.Cipher.ChaCha.Poly1305 as One import qualified Crypto.Cipher.ChaChaPoly1305 as CP import Crypto.Cipher.Types import Crypto.Error import Imports import MAC.Poly1305Spec () +import Data.Bits (xor) import qualified Data.ByteArray as B (convert) import qualified Data.ByteString as B @@ -129,6 +131,7 @@     it "nonce increment" runNonceInc     it "RFC8439 A5 enc" rfc8439encrypt     it "RFC8439 A5 dec" rfc8439decrypt+    oneShotTests   where     runEncrypt =         let ini =@@ -202,3 +205,60 @@                     B.convert . CP.incrementNonce $                         n10                 ]++-- | Crypto.Cipher.ChaCha.Poly1305 does a whole message in one call where+-- Crypto.Cipher.ChaChaPoly1305 does it in steps.  It has to answer exactly+-- what the steps answer, and what RFC 8439 prints.+oneShotTests :: Spec+oneShotTests = describe "Crypto.Cipher.ChaCha.Poly1305" $ do+    it "RFC 8439 2.8.2, twelve-byte nonce" $+        propertyHoldCase+            [ eqTest "ciphertext" ciphertext (B.take (B.length ciphertext) sealed)+            , eqTest "tag" tag (B.drop (B.length ciphertext) sealed)+            ]+    it "decrypt undoes encrypt" $+        One.decrypt ctx nonce12 aad sealed 16 `shouldBe` Just plaintext+    it "refuses a flipped bit in the ciphertext" $+        One.decrypt ctx nonce12 aad (flipHead sealed) 16+            `shouldBe` (Nothing :: Maybe B.ByteString)+    it "refuses a flipped bit in the tag" $+        One.decrypt ctx nonce12 aad (flipLast sealed) 16+            `shouldBe` (Nothing :: Maybe B.ByteString)+    it "refuses input shorter than the tag" $+        One.decrypt ctx nonce12 aad (B.replicate 8 0) 16+            `shouldBe` (Nothing :: Maybe B.ByteString)+    it "refuses a nonce that is not twelve bytes" $ do+        One.decrypt ctx (B.replicate 10 0) aad sealed 16+            `shouldBe` (Nothing :: Maybe B.ByteString)+        -- eight is the other ChaCha construction, not this AEAD+        One.decrypt ctx (B.replicate 8 0) aad sealed 16+            `shouldBe` (Nothing :: Maybe B.ByteString)+    it "decryptWithTag hands back the tag encrypt made" $+        case One.decryptWithTag ctx nonce12 aad (B.take (B.length ciphertext) sealed) 16 of+            CryptoFailed e -> expectationFailure (show e)+            CryptoPassed (body, t) ->+                propertyHoldCase+                    [ eqTest "plaintext" plaintext body+                    , eqTest "tag" (AuthTag (B.convert tag)) t+                    ]+    it "agrees with the step-at-a-time interface over a different message" $+        let msg = "another message, of a length that is not a multiple of 16" :: B.ByteString+            ad = "\x01\x02\x03" :: B.ByteString+            ini = CP.initialize (throwCryptoError $ CP.key key)+                                (throwCryptoError $ CP.nonce12 nonce12)+            afterAAD = CP.finalizeAAD (CP.appendAAD ad ini)+            (out, afterEnc) = CP.encrypt msg afterAAD+            t = CP.finalize afterEnc+            one = throwCryptoError (One.encrypt ctx nonce12 ad msg 16) :: B.ByteString+         in propertyHoldCase+                [ eqTest "ciphertext" out (B.take (B.length out) one)+                , eqTest "tag" (B.convert t :: B.ByteString) (B.drop (B.length out) one)+                ]+  where+    ctx = throwCryptoError (One.newContext key)+    -- the same nonce the step interface builds from constant and iv+    nonce12 = constant `B.append` iv+    sealed = throwCryptoError (One.encrypt ctx nonce12 aad plaintext 16) :: B.ByteString+    flipHead bs = B.cons (B.head bs `xor` 1) (B.tail bs)+    flipLast bs =+        B.snoc (B.init bs) (B.last bs `xor` 1)