block_ciphers

The block_ciphers library provides portable block cipher implementations. It defines a block_cipher_protocol protocol with the predicates:

  • encrypt_block/3

  • decrypt_block/3

  • block_size/1

  • key_size/1

The library also defines block_cipher_prepared_key_protocol, which extends the basic protocol with predicates for preparing a key once and reusing its opaque representation for repeated block operations. Keys, plaintext, ciphertext, initialization vectors, and counters are represented by lists of bytes.

API documentation

Open the ../../apis/library_index.html#block-ciphers link in a web browser.

Loading

To load all entities in this library, load the loader.lgt file:

| ?- logtalk_load(block_ciphers(loader)).

Testing

To test this library predicates, load the tester.lgt file:

| ?- logtalk_load(block_ciphers(tester)).

Supported block ciphers

The library implements AES as specified in FIPS 197 using three objects:

  • aes128, accepting a 16-byte key

  • aes192, accepting a 24-byte key

  • aes256, accepting a 32-byte key

All three objects use 16-byte blocks. They extend ground instances of the shared parametric object aes_common(KeySize, Nk, Nr), which contains the key expansion and encryption and decryption round implementations.

Block cipher modes

The library provides generic ecb, cbc, and ctr mode objects. Their first argument is any cipher object implementing block_cipher_prepared_key_protocol. The key is prepared once per mode operation.

The ecb object provides:

  • encrypt/4 and decrypt/4 for block-aligned input without padding

  • encrypt_padded/4 and decrypt_padded/4 using PKCS#7 padding

The cbc object provides:

  • encrypt/5 and decrypt/5 for block-aligned input without padding

  • encrypt_padded/5 and decrypt_padded/5 using PKCS#7 padding

The third argument of the CBC predicates is an explicit initialization vector whose length is exactly one cipher block. The IV is neither generated by the library nor prefixed to the ciphertext.

The ctr object provides crypt/5 and crypt/6 for both encryption and decryption. Their third argument is an explicit initial counter whose length is exactly one cipher block. The counter is interpreted as a whole-block unsigned big-endian integer and incremented modulo the block width while processing the input. The crypt/6 predicate additionally returns the next unused counter, which equals the initial counter for empty input and is otherwise incremented once per processed input block. The object is stateless; clients must manage counters across calls and ensure that counter sequences do not overlap when using the same key. CTR accepts arbitrary input lengths, including a final partial block.

Raw ECB and CBC operations accept empty input but otherwise require a byte length divisible by the cipher block size. PKCS#7 encryption accepts any input length and always appends padding. Thus, empty or already block-aligned input receives a complete padding block. Padded decryption requires non-empty, block-aligned ciphertext and rejects malformed padding.

Security considerations

The block primitive and all three modes are unauthenticated. Prefer an authenticated-encryption construction for general-purpose encryption.

ECB reveals equality and patterns between plaintext blocks and is unsuitable for encrypting confidential structured data.

CBC requires an unpredictable fresh IV for each encryption under a key. It provides confidentiality but not integrity or authenticity and must be paired with a secure authentication construction. Applications must also avoid exposing distinguishable padding failures to attackers, which can create a padding oracle.

CTR requires that a key and counter sequence is never reused. Reuse reveals relationships between plaintexts. CTR ciphertext is malleable and must be authenticated separately.

The portable table-based implementation is not guaranteed to execute in constant time and makes no side-channel resistance claims.

CMAC compatibility

The protocols are designed to support a future generic CMAC library. CMAC uses the block-encryption primitive and does not depend on CBC mode, despite using a related chaining operation. It can query block_size/1 and key_size/1 and use either encrypt_block/3 or the prepared-key predicates without depending on AES-specific constants or implementation details.

Example

Encrypt the standard FIPS 197 AES-128 example block:

| ?- aes128::encrypt_block(
         [0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f],
         [0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff],
         Ciphertext
     ).
Ciphertext = [105,196,224,216,106,123,4,48,216,205,183,128,112,180,197,90]
yes

Encrypt and decrypt an arbitrary-length message using CBC with PKCS#7 padding and an explicit IV:

| ?- Key = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     IV = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],
     cbc::encrypt_padded(aes128, Key, IV, [1,2,3,4,5], Ciphertext),
     cbc::decrypt_padded(aes128, Key, IV, Ciphertext, Plaintext).
Plaintext = [1,2,3,4,5]
yes