WringTwistree-0.0.1.0: src/Cryptography/Wring.hs
module Cryptography.Wring
( Wring
, mix3Parts -- reexported for use in Cryptanalysis.hs
, permut8 -- reexported for testing
, mul65537 -- "
, wringName -- For events in traces when profiling
, xorn -- Used for big hash test
, linearWring -- Only for cryptanalysis and testing
, keyedWring
, encrypt
, decrypt
, encryptFixed -- Only for cryptanalysis and testing
) where
{- This cipher is intended to be used with short random keys (32 bytes or less,
- no hard limit) or long human-readable keys (up to 96 bytes). keyedWring
- takes arbitrarily long keys, but do not use keys longer than 96 bytes as they
- make the cipher more vulnerable to related-key attacks.
-
- The Haskell implementation needs four times as much RAM as the message size,
- plus a constant overhead.
-}
import Cryptography.WringTwistree.Mix3
import Cryptography.WringTwistree.RotBitcount
import Cryptography.WringTwistree.Sboxes
import Text.Printf
import Data.Word
import Data.Bits
import Data.Foldable (foldl')
import qualified Data.ByteString as B
import qualified Data.Vector.Unboxed as V
import qualified Data.Vector.Unboxed.Mutable as MV
import Control.Monad.ST
import Control.Monad
data Wring = Wring
{ sbox :: SBox
, invSbox :: SBox
} deriving Show
-- | Generates a name from the first four bytes of the S-box.
-- Used to tag events in a profiling log.
wringName :: Wring -> String
wringName wring = printf "%02x-%02x-%02x-%02x"
((sbox wring) V.! 0)
((sbox wring) V.! 1)
((sbox wring) V.! 2)
((sbox wring) V.! 3)
nRounds :: Integral a => a -> a
nRounds len
| len < 3 = 3
| otherwise = (nRounds (len `div` 3)) +1
-- | Exclusive-ors all bytes in a nonnegative number. The only reason this
-- function is public is that it's used to generate a long `ByteString`
-- for a test.
xorn :: (Integral a,Bits a) => a -> Word8
xorn 0 = 0
xorn (-1) = error "xorn: negative"
xorn a = (fromIntegral a) `xor` (xorn (a .>>. 8))
xornArray :: Int -> V.Vector Word8
xornArray n = V.fromListN n (map xorn [0..(n-1)])
-- | A `Wring` with linear S-boxes. Used only for testing and cryptanalysis.
linearWring = Wring linearSbox linearInvSbox
-- | Creates a `Wring` with the given key.
-- To convert a `String` to a `ByteString`, put @- utf8-string@ in your
-- package.yaml dependencies, @import Data.ByteString.UTF8@, and use
-- `fromString`.
keyedWring :: B.ByteString -> Wring
keyedWring key = Wring sbox (invert sbox)
where
sbox = sboxes key
{- A round of encryption consists of four steps:
- mix3Parts
- sboxes
- rotBitcount
- add byte index xor round number
-}
-- Original purely functional version, modified to use vectors
roundEncryptFun ::
Int ->
SBox ->
V.Vector Word8 ->
V.Vector Word8 ->
Int ->
V.Vector Word8
roundEncryptFun rprime sbox xornary buf rond = i4 where
len = V.length buf
xornrond = xorn rond
i1 = mix3Parts buf rprime
i2 = V.fromListN len $ map (sbox V.!) $
zipWith sboxInx (drop rond cycle3) (V.toList i1)
i3 = rotBitcount i2 1
i4 = V.fromListN len $ zipWith (+) (V.toList i3)
(map (xor xornrond) (V.toList xornary))
roundDecryptFun ::
Int ->
SBox ->
V.Vector Word8 ->
V.Vector Word8 ->
Int ->
V.Vector Word8
roundDecryptFun rprime sbox xornary buf rond = i4 where
len = V.length buf
xornrond = xorn rond
i1 = V.fromListN len $ zipWith (-) (V.toList buf)
(map (xor xornrond) (V.toList xornary))
i2 = rotBitcount i1 (-1)
i3 = V.fromListN len $ map (sbox V.!) $
zipWith sboxInx (drop rond cycle3) (V.toList i2)
i4 = mix3Parts i3 rprime
encryptFun :: Wring -> V.Vector Word8 -> V.Vector Word8
encryptFun wring buf = foldl' (roundEncryptFun rprime (sbox wring) xornary)
buf rounds
where
len = V.length buf
xornary = xornArray len
rprime = fromIntegral $ findMaxOrder (fromIntegral $ len `div` 3)
rounds = [0 .. (nRounds len) -1]
decryptFun :: Wring -> V.Vector Word8 -> V.Vector Word8
decryptFun wring buf = foldl' (roundDecryptFun rprime (invSbox wring) xornary)
buf rounds
where
len = V.length buf
xornary = xornArray len
rprime = fromIntegral $ findMaxOrder (fromIntegral $ len `div` 3)
rounds = reverse [0 .. (nRounds len) -1]
-- ST monad version modifies memory in place
-- by int-e
{-# NOINLINE roundEncryptST #-}
roundEncryptST ::
Int ->
SBox ->
V.Vector Word8 ->
MV.MVector s Word8 ->
MV.MVector s Word8 ->
Int ->
ST s ()
roundEncryptST rprime sbox xornary buf tmp rond = do
let len = MV.length buf
xornrond = xorn rond
mix3Parts' buf rprime
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
MV.write tmp i (sbox V.! (sboxInx ((i + rond) `rem` 3) a))
rotBitcount' tmp 1 buf
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
let a' = a + (xornrond `xor` (xornary V.! i))
MV.write buf i a'
{-# NOINLINE roundEncryptFixedST #-}
roundEncryptFixedST ::
Int ->
SBox ->
V.Vector Word8 ->
MV.MVector s Word8 ->
MV.MVector s Word8 ->
Int ->
ST s ()
roundEncryptFixedST rprime sbox xornary buf tmp rond = do
let len = MV.length buf
xornrond = xorn rond
mix3Parts' buf rprime
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
MV.write tmp i (sbox V.! (sboxInx ((i + rond) `rem` 3) a))
rotFixed' tmp 1 buf
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
let a' = a + (xornrond `xor` (xornary V.! i))
MV.write buf i a'
{-# NOINLINE roundDecryptST #-}
roundDecryptST ::
Int ->
SBox ->
V.Vector Word8 ->
MV.MVector s Word8 ->
MV.MVector s Word8 ->
Int ->
ST s ()
roundDecryptST rprime sbox xornary buf tmp rond = do
let len = MV.length buf
xornrond = xorn rond
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
let a' = a - (xornrond `xor` (xornary V.! i))
MV.write tmp i a'
rotBitcount' tmp (-1) buf
forM_ [0..len-1] $ \i -> do
a <- MV.read buf i
MV.write buf i (sbox V.! (sboxInx ((i + rond) `rem` 3) a))
mix3Parts' buf rprime
encryptST :: Wring -> V.Vector Word8 -> V.Vector Word8
encryptST wring buf = V.create $ do
let len = V.length buf
xornary = xornArray len
rprime = fromIntegral $ findMaxOrder (fromIntegral $ len `div` 3)
rounds = [0 .. nRounds len - 1]
buf <- V.thaw buf
tmp <- MV.new len
forM_ [0..nRounds len - 1] $ \rond -> do
roundEncryptST rprime (sbox wring) xornary buf tmp rond
pure buf
-- Encrypts using a fixed rotation of the buffer, rather than rotating
-- by its population count. This removes a source of nonlinearity.
encryptFixedST :: Wring -> V.Vector Word8 -> V.Vector Word8
encryptFixedST wring buf = V.create $ do
let len = V.length buf
xornary = xornArray len
rprime = fromIntegral $ findMaxOrder (fromIntegral $ len `div` 3)
rounds = [0 .. nRounds len - 1]
buf <- V.thaw buf
tmp <- MV.new len
forM_ [0..nRounds len - 1] $ \rond -> do
roundEncryptFixedST rprime (sbox wring) xornary buf tmp rond
pure buf
decryptST :: Wring -> V.Vector Word8 -> V.Vector Word8
decryptST wring buf = V.create $ do
let len = V.length buf
xornary = xornArray len
rprime = fromIntegral $ findMaxOrder (fromIntegral $ len `div` 3)
nr = nRounds len - 1
rounds = [0 .. nr]
buf <- V.thaw buf
tmp <- MV.new len
forM_ [0..nRounds len - 1] $ \rond -> do
roundDecryptST rprime (invSbox wring) xornary buf tmp (nr - rond)
pure buf
-- Use either the ST version or the Fun version.
-- Fun takes 4.2 times as long as ST when encrypting or decrypting a file,
-- but doing six threads in parallel for cryptanalysis, it takes only
-- 1.58 times as long. Turning off threading makes encrypting 5.2 times
-- as fast.
-- | Encrypts a message.
encrypt
:: Wring -- ^ The `Wring` made with the key to encrypt with
-> V.Vector Word8 -- ^ The plaintext
-> V.Vector Word8 -- ^ The returned ciphertext
encrypt = encryptST
-- | Used only for cryptanalysis
encryptFixed = encryptFixedST
-- | Decrypts a message.
decrypt
:: Wring -- ^ The `Wring` made with the key to decrypt with
-> V.Vector Word8 -- ^ The ciphertext
-> V.Vector Word8 -- ^ The returned plaintext
decrypt = decryptST