Прочитав книгу Курта, решил реализовать AES на Haskell в качестве первой практики. Сначала была эйфория от красоты конструкций и языка, потом агония, когда картинку несколько мегабайт программа шифровала целую минуту, хоть и сделала все правильно и хеши при расшифровке совпали. На больших файлах запускать страшно, боюсь не выдержит, хотя в конечном итоге хочу оптимизировать именно для них.
import Data.Binary
import Data.Bits(xor)
import qualified Data.ByteString.Lazy as BSL
import Data.Int(Int64)
import System.Environment
mask :: BSL.ByteString
mask = BSL.concat $ map encode numbers
where numbers = [1..] :: [Int64]
ctrMode :: BSL.ByteString -> BSL.ByteString
ctrMode = BSL.packZipWith xor mask
main :: IO ()
main = do
(inputFile:_) <- getArgs
let outputFile = "ctr_" ++ inputFile
inputContent <- BSL.readFile inputFile
let newContent = ctrMode inputContent
BSL.writeFile outputFile newContent
Данный код реализует Counter Mode для обработки открытого текста перед шифрованием, чтобы на одинаковых блоках зашифрованный текст был разный.
$ ghc -O3 ctr.hs
$ dd if=/dev/zero of=./zeros bs=1M count=1536
$ time ./ctr zeros
./ctr zeros 86,75s user 7,54s system 100% cpu 1:34,14 total
$ time openssl enc -aes-128-cbc -in zeros -out enc_zeros
openssl enc -aes-128-cbc -in zeros -out enc_zeros 1,31s user 3,50s system 66% cpu 7,187 total
Файл 1.5 гигабайта обрабатывается за 90 секунд, и делает лишь xor, в то время как openssl тот же файл за пару секунд полностью шифрует. Программа собрана с -O3. Как можно оптимизировать?
Я уверен, что проблема в маленьких чанках при чтении файла. Как их можно увеличить? hGetContent работает с дефолтным чанком. hGetContentN, которая принимает размер чанка, не экспортируется (https://hackage.haskell.org/package/bytestring-0.11.1.0/docs/src/Data.ByteString.Lazy.html#hGetContents). Вручную писать считывание с большими чанками? Как-то некрасиво, да и не знаю как, и это похоже на что-то базовое, что должно быть в стандартной библиотеке, но я не могу найти.