botan-low
Copyright(c) 2023-2024 Apotheca Labs
(c) 2024-2025 Haskell Foundation
LicenseBSD-3-Clause
Maintainerjoris@well-typed.com, leo@apotheca.io
Stabilityexperimental
PortabilityPOSIX
Safe HaskellSafe-Inferred
LanguageGHC2021

Botan.Low.BlockCipher

Description

This module is based on the Block Ciphers section of the C Botan FFI documentation, and on the Block Ciphers section of the C++ API reference.

Synopsis

Introduction

A block cipher is a deterministic, cryptographic primitive suitable for encrypting or decrypting a single, fixed-size block of data at a time. Block ciphers are used as building blocks for more complex cryptographic operations. If you are looking to encrypt user data, you are probably looking for Botan.Low.Cipher instead.

Unless you need a specific block cipher, it is strongly recommended that you use the AES256 algorithm.

>>> :{
{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE OverloadedStrings #-}
import Botan.Low.BlockCipher
import Botan.Low.RNG
import Control.Exception
import Data.ByteString as BS
:}

For this example, we will use the AES256 algorithm. To use a block cipher, we first need a secret key of the appropriate length. Such keys should be generated securely, but to keep it simple in this example, we will just use a random number generator to generate a secret key.

>>> :{
generateKey :: BlockCipher -> IO ByteString
generateKey blockCipher = do
    rng <- rngInit "user"
    (_,keySize,_) <- blockCipherGetKeyspec blockCipher
    key <- rngGet rng keySize
    pure key
:}

We can now initialise a block cipher object and set its secret key.

>>> :{
initBlockCipher :: IO BlockCipher
initBlockCipher = do
    blockCipher <- blockCipherInit AES256
    key <- generateKey blockCipher
    blockCipherSetKey blockCipher key
    pure blockCipher
:}

Using the initialised block cipher, we can now encrypt a message. To encrypt a message, it must be a multiple of the block size. To decrypt a message, simply reverse the process. As a sanity check, we show that decryption after encryption yields the original message

>>> :{
roundtrip :: BlockCipher -> ByteString -> IO ()
roundtrip blockCipher message = do
    ciphertext <- blockCipherEncryptBlocks blockCipher message
    plaintext <- blockCipherDecryptBlocks blockCipher ciphertext
    print $ message == plaintext
:}

Now we tie together the program. We initialise the block cipher (and set its secret key). Then, we generate a message, and check that it roundtrips through the encryption and decryption functions.

>>> :{
main :: IO ()
main = do
    blockCipher <- initBlockCipher
    blockSize <- blockCipherBlockSize blockCipher
    -- AES256 block size is always 16 bytes
    let message = "0000DEADBEEF0000"
    assert (BS.length message == blockSize) $ pure ()
    roundtrip blockCipher message
:}

All that is left to do is run the program.

>>> main
True

newtype BlockCipher Source #

A mutable block cipher object

type BlockCipherName = ByteString Source #

Block cipher name type

type BlockCipherKey = ByteString Source #

A block cipher secret key

type BlockCipherCiphertext = ByteString Source #

A block cipher ciphertext

blockCipherInit Source #

Arguments

:: BlockCipherName

cipher_name

-> IO BlockCipher

bc

Initialize a block cipher object

blockCipherDestroy :: BlockCipher -> IO () Source #

Destroy a block cipher object immediately

blockCipherName Source #

Arguments

:: BlockCipher

bc

-> IO BlockCipherName

name

Get the name of a block cipher.

This function is not guaranteed to return the same exact value as used to initialize the context.

blockCipherBlockSize Source #

Arguments

:: BlockCipher

bc: The cipher object

-> IO Int 

Return the block size of a block cipher.

blockCipherGetKeyspec Source #

Arguments

:: BlockCipher

bc

-> IO (Int, Int, Int)

(min,max,mod)

Get the key specification of a block cipher

Returns the minimum, maximum, and modulo of valid keys.

blockCipherSetKey Source #

Arguments

:: BlockCipher

bc: The cipher object

-> BlockCipherKey

key[]: A cipher key

-> IO () 

Set the key for a block cipher

Throws an error if the key is not valid.

blockCipherEncryptBlocks Source #

Arguments

:: BlockCipher

bc: The cipher object

-> ByteString

in[]: The plaintext

-> IO BlockCipherCiphertext

out[]: The ciphertext

Encrypt one or more blocks with a block cipher

Precondition: the length of the plaintext should be a multiple of the block size, or this function throws an error.

blockCipherDecryptBlocks Source #

Arguments

:: BlockCipher

bc: The cipher object

-> BlockCipherCiphertext

in[]: The ciphertext

-> IO ByteString

out[]: The plaintext

Decrypt one or more blocks with a block cipher.

Precondition: the length of the ciphertext should be a multiple of the block size, or this function throws an error.

If an incorrect key was set, the content of the decrypted plaintext is unspecified.

blockCipherClear Source #

Arguments

:: BlockCipher

bc: The cipher object

-> IO () 

Reinitializes a block cipher

You must call blockCipherSetKey in order to use the block cipher again.

Available ciphers

A number of algorithms are available to be used as block ciphers. An algorithm is selected by passing the algorithm specification name of the algorithm as a string to the block cipher functions. In some cases an algorithm specification name is just the algorithm name, like "AES-128". In other cases an algorithm specification name is a combination of an algorithm name paired with some parameters, such as "Cascade(Serpent, AES-256)" where Cascade is the algorithm name followed by a 2-tuple of parameters. Pattern synonyms are included for each available algorithm name. Where necessary, such as in the Cascade case, these algorithm names should be manually extended to full algorithm specification names, or by using utility functions like cascade that this module also provides.

WARNING: 64-bit block ciphers should be avoided.

WARNING: the Kuznyechik block ciphier is not included because its security is disputed.

See the Available Ciphers section of the C++ API reference for more information about available ciphers.

Each algorithm expects a certain size of block to encrypt or decrypt. For the available algorithms, these block sizes in number of bits are listed below. For algorithms that require parameters, the block size may vary depending on the parameters. For the examples below, we pick some arbitrary parameters.

>>> :{
forM_ allBlockCiphers $ \name -> do
  let name' =
        if name == Cascade
        then cascade Serpent AES256
        else if name == Lion
        then lion SHA1 "RC4" (Just 64)
        else name
  blockCipher <- blockCipherInit name'
  sz <- blockCipherBlockSize blockCipher
  print (name, sz * 8)
:}
("AES-128",128)
("AES-192",128)
("AES-256",128)
("ARIA-128",128)
("ARIA-192",128)
("ARIA-256",128)
("Camellia-128",128)
("Camellia-192",128)
("Camellia-256",128)
("Noekeon",128)
("SEED",128)
("Serpent",128)
("SM4",128)
("Twofish",128)
("Blowfish",64)
("Cascade",128)
("CAST-128",64)
("DES",64)
("TripleDES",64)
("GOST-28147-89",64)
("IDEA",64)
("Lion",512)
("SHACAL2",256)
("Threefish-512",512)

cascade :: BlockCipherName -> BlockCipherName -> BlockCipherName Source #

>>> cascade Serpent AES256
"Cascade(Serpent,AES-256)"

lion :: HashName -> ByteString -> Maybe Word -> BlockCipherName Source #

>>> lion SHA1 "RC4" Nothing
"Lion(SHA-1,RC4)"
>>> lion SHA1 "RC4" (Just 64)
"Lion(SHA-1,RC4,64)"

Convenience