From 91e070cb5e4d77e5d47033cb94aa2001a7464483 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:00:18 +0400 Subject: [PATCH] crypto: document 16-byte GCM IV in double ratchet (#1914) --- protocol/pqdr.md | 2 ++ src/Simplex/Messaging/Crypto.hs | 15 +++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/protocol/pqdr.md b/protocol/pqdr.md index 60990e5b8..1519a59c9 100644 --- a/protocol/pqdr.md +++ b/protocol/pqdr.md @@ -212,6 +212,8 @@ The outer envelope contains the encrypted header (used as associated data for bo The message body is encrypted with AES-256-GCM using the message key derived from the sending chain key (`KDF_CK`). The associated data for body encryption is the concatenation of the ratchet associated data and the encoded encrypted header. +`KDF_CK(CK)` is HKDF-SHA512 with empty salt, `CK` as input key material and info `"SimpleXChainRatchet"`, producing 96 bytes split into the next chain key (32 bytes), the message key (32 bytes), the message body IV (16 bytes, not transmitted) and `headerIV` (16 bytes). Both IVs are used as 16-byte AES-256-GCM IVs, not the 12-byte IVs recommended by NIST SP 800-38D, so the initial counter block is J0 = GHASH(IV || 0^64 || [128]_64) as defined there for non-96-bit IVs. WebCrypto and other conforming implementations compute it when given the full 16-byte IV; truncating the IV to 12 bytes produces different ciphertext. + ```abnf encRatchetMessage = versionedLength encMessageHeader msgAuthTag encMsgBody ; encMessageHeader is used as associated data for body decryption: AD = rcAD || encMessageHeader diff --git a/src/Simplex/Messaging/Crypto.hs b/src/Simplex/Messaging/Crypto.hs index 0bc5238c4..f73b61e17 100644 --- a/src/Simplex/Messaging/Crypto.hs +++ b/src/Simplex/Messaging/Crypto.hs @@ -1045,9 +1045,7 @@ md5Hash = BA.convert . (hash :: ByteString -> Digest MD5) -- | AEAD-GCM encryption with associated data. -- --- Used as part of double ratchet encryption. --- This function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here: --- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c +-- Used as part of double ratchet encryption, with a 16-byte IV (see @initAEAD@). encryptAEAD :: Key -> IV -> Int -> ByteString -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString) encryptAEAD aesKey ivBytes paddedLen ad msg = do aead <- initAEAD @AES256 aesKey ivBytes @@ -1067,10 +1065,7 @@ encryptAEADNoPad aesKey ivBytes ad msg = do -- | AEAD-GCM decryption with associated data. -- --- Used as part of double ratchet encryption. --- This function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here: --- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c --- To make it compatible with WebCrypto we will need to start using initAEADGCM. +-- Used as part of double ratchet encryption, with a 16-byte IV (see @initAEAD@). decryptAEAD :: Key -> IV -> ByteString -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString decryptAEAD aesKey ivBytes ad msg (AuthTag authTag) = do aead <- initAEAD @AES256 aesKey ivBytes @@ -1148,9 +1143,9 @@ maxLength :: forall i. KnownNat i => Int maxLength = fromIntegral (natVal $ Proxy @i) {-# INLINE maxLength #-} --- this function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here: --- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c --- This is used for double ratchet encryption, so to make it compatible with WebCrypto we will need to deprecate it and start using initAEADGCM +-- The 16-byte double ratchet IV is intentionally not the 96-bit IV recommended by NIST SP 800-38D, so GCM derives J0 = GHASH(IV || 0^64 || [128]_64), +-- as in crypton_aes_gcm_init: https://hackage.haskell.org/package/crypton-0.34/src/cbits/crypton_aes.c +-- WebCrypto and other SP 800-38D implementations interoperate only when given all 16 IV bytes. initAEAD :: forall c. AES.BlockCipher c => Key -> IV -> ExceptT CryptoError IO (AES.AEAD c) initAEAD (Key aesKey) (IV ivBytes) = do iv <- makeIV @c ivBytes