<commit_before>-- Copyright © 2015 <NAME>

-- | Implementation of the "CipherSaber-2" RC4 encryption
-- format. Also provides raw RC4 keystream generators and
-- a CipherSaber-1 implementation.
--
-- This work is licensed under the "MIT License".  Please
-- see the file LICENSE in the source distribution of this
-- software for license terms.
module CipherSaber2 (rc4, rc4dropN, encryptDropN, decryptDropN,
                     encrypt1, decrypt1, encrypt, decrypt,
                     toBytes, fromBytes) where

import Control.Monad
import Control.Monad.ST.Safe
import Data.Array.ST
import Data.Bits
import Data.Char
import Data.Word

-- | Number of bytes of IV to use in CipherSaber encryption/decryption
-- below. The standard CipherSaber IV size is 10 bytes.
ivLength :: Int
ivLength = 10

-- | Generate an RC4 keystream. CipherSaber recommends
-- a key of less than 54 bytes for best mixing. At most,
-- the first 256 bytes of the key are even used.
--
-- This code takes a length of keystream to generate, which
-- is un-Haskellike but hard to avoid given the strictness
-- of 'STUArray'. An alternative would be to pass the
-- plaintext in and combine it here, but this interface was
-- chosen as "simpler". The performance implications of this
-- choice need to be explored.
rc4 :: Int -> [Word8] -> [Word8]
rc4 keystreamLength key =
    runST $ do
      -- Create and initialize the state.
      s <- newListArray (0, 255) [0..255] :: ST s (STUArray s Word8 Word8)
      -- One step of the key schedule
      let schedStep j i = do
            si <- readArray s i
            let keyByte = key !! (fromIntegral i `mod` length key)
            let j' = j + si + keyByte
            sj <- readArray s j'
            writeArray s i sj
            writeArray s j' si
            return j'
      -- Do the key scheduling.
      foldM_ schedStep 0 [0..255]
      -- Do the keystream generation.
      let keystream 0 _ _ = return []
          keystream n i j = do
            let i' = i + 1
            si <- readArray s i'
            let j' = j + si
            sj <- readArray s j'
            writeArray s i' sj
            writeArray s j' si
            sk <- readArray s (si + sj)
            ks <- keystream (n - 1) i' j'
            return $ sk : ks
      -- Get the keystream.
      keystream keystreamLength 0 0

-- | RC4 with the first n keystream elements
-- dropped. Dropping 20 or more elements provides increased
-- resistance against known attacks.
rc4dropN :: Int -> Int -> [Word8] -> [Word8]
rc4dropN nDrop keystreamLength key =
  drop nDrop $ rc4 (keystreamLength + 200) key

-- | Convert a 'String' to a list of bytes.
toBytes :: String -> [Word8]
toBytes s = map (fromIntegral . ord) s

-- | Convert a list of bytes to a 'String'.
fromBytes :: [Word8] -> String
fromBytes bs = map (chr . fromIntegral) bs

-- | CipherSaber requires using a 10-byte initial value (IV) to
-- protect against keystream recovery. Given the key and IV,
-- this code will turn a plaintext message into a sequence
-- of ciphertext bytes.
encryptDropN :: Int -> [Word8] -> [Word8] -> [Word8] -> [Word8]
encryptDropN nDrop key iv plaintext
    | length iv == ivLength =
        let keystream = rc4dropN nDrop (length plaintext) (key ++ iv) in
        iv ++ zipWith xor keystream plaintext
    | otherwise = error $ "expected IV length " ++ show ivLength

-- | CipherSaber recovers the IV from the start of the ciphertext.
-- Given the key, this code will turn a sequence of
-- ciphertext bytes into a plaintext 'String' message. (Yes,
-- the type is a little weird.)
decryptDropN :: Int -> [Word8] -> [Word8] -> [Word8]
decryptDropN nDrop key ciphertext0 =
    let (iv, ciphertext) = splitAt ivLength ciphertext0
        keystream = rc4dropN nDrop (length ciphertext) (key ++ iv) in
    zipWith xor keystream ciphertext

-- | CipherSaber-1 encryption has no drops.
encrypt1 :: [Word8] -> [Word8] -> [Word8] -> [Word8]
encrypt1 key iv ciphertext =
    encryptDropN 0 key iv ciphertext

-- | CipherSaber-1 decryption has no drops.
decrypt1 :: [Word8] -> [Word8] -> [Word8]
decrypt1 key ciphertext =
    decryptDropN 0 key ciphertext

-- | CipherSaber-2 encryption drops 20 bytes.
encrypt :: [Word8] -> [Word8] -> [Word8] -> [Word8]
encrypt key iv ciphertext =
    encryptDropN 20 key iv ciphertext

-- | CipherSaber-2 decryption drops 20 bytes.
decrypt :: [Word8] -> [Word8] -> [Word8]
decrypt key ciphertext =
    decryptDropN 20 key ciphertext<commit_msg>changed CipherSaber2 interface and implementation to correspond to actual CipherSaber-2<commit_after>-- Copyright © 2015 <NAME>

-- | Implementation of the "CipherSaber-2" RC4 encryption
-- format. Also provides a raw RC4 keystream generator.
--
-- This work is licensed under the "MIT License".  Please
-- see the file LICENSE in the source distribution of this
-- software for license terms.
module CipherSaber2 (rc4, encrypt, decrypt, toBytes, fromBytes) where

import Control.Monad
import Control.Monad.ST.Safe
import Data.Array.ST
import Data.Bits
import Data.Char
import Data.Word

-- | Number of bytes of IV to use in CipherSaber encryption/decryption
-- below. The standard CipherSaber IV size is 10 bytes.
ivLength :: Int
ivLength = 10

-- | Generate an RC4 keystream. CipherSaber recommends
-- a key of less than 54 bytes for best mixing. At most,
-- the first 256 bytes of the key are even used.
--
-- This function takes a parameter for the number of times
-- to repeat the key mixing loop ala "CipherSaber-2". It
-- should probably be set to at least 20.
-- 
-- This function takes a length of keystream to generate,
-- which is un-Haskellike but hard to avoid given the
-- strictness of 'STUArray'. An alternative would be to pass
-- the plaintext in and combine it here, but this interface
-- was chosen as "simpler". Another choice would be to leave
-- whole rc4 function in the 'ST' monad, but that seemed
-- obnoxious. The performance and usability implications of
-- these choices need to be explored.
rc4 :: Int -> Int -> [Word8] -> [Word8]
rc4 scheduleReps keystreamLength key =
    runST $ do
      -- Create and initialize the state.
      s <- newListArray (0, 255) [0..255] :: ST s (STUArray s Word8 Word8)
      -- One step of the key schedule
      let schedStep j i = do
            si <- readArray s i
            let keyByte = key !! (fromIntegral i `mod` length key)
            let j' = j + si + keyByte
            sj <- readArray s j'
            writeArray s i sj
            writeArray s j' si
            return j'
      -- Do the key scheduling.
      foldM_ schedStep 0 $ concat $ replicate scheduleReps [0..255]
      -- Do the keystream generation.
      let keystream 0 _ _ = return []
          keystream n i j = do
            let i' = i + 1
            si <- readArray s i'
            let j' = j + si
            sj <- readArray s j'
            writeArray s i' sj
            writeArray s j' si
            sk <- readArray s (si + sj)
            ks <- keystream (n - 1) i' j'
            return $ sk : ks
      -- Get the keystream.
      keystream keystreamLength 0 0

-- | Convert a 'String' to a list of bytes.
toBytes :: String -> [Word8]
toBytes s = map (fromIntegral . ord) s

-- | Convert a list of bytes to a 'String'.
fromBytes :: [Word8] -> String
fromBytes bs = map (chr . fromIntegral) bs

-- | CipherSaber requires using a 10-byte initial value (IV)
-- to protect against keystream recovery. Given the key and
-- IV, this code will turn a a sequence of plaintext message
-- bytes into a sequence of ciphertext bytes.
encrypt :: Int -> [Word8] -> [Word8] -> [Word8] -> [Word8]
encrypt scheduleReps key iv plaintext
    | length iv == ivLength =
        let keystream = rc4 scheduleReps (length plaintext) (key ++ iv) in
        iv ++ zipWith xor keystream plaintext
    | otherwise = error $ "expected IV length " ++ show ivLength

-- | CipherSaber recovers the 10-byte IV from the start of the
-- ciphertext.  Given the key, this code will turn a
-- sequence of ciphertext bytes into a sequence of plaintext
-- bytes.
decrypt :: Int -> [Word8] -> [Word8] -> [Word8]
decrypt scheduleReps key ciphertext0 =
    let (iv, ciphertext) = splitAt ivLength ciphertext0
        keystream = rc4 scheduleReps (length ciphertext) (key ++ iv) in
    zipWith xor keystream ciphertext
{'fraction_non_alphanumeric': 0.09208303161959933, 'fraction_numerical': 0.020637219406227373, 'mean_word_length': 2.5144189991518235, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7411584', 'n_tokens_mistral': 2620, 'n_tokens_neox': 2427, 'n_words': 1086}