block_ciphers
The block_ciphers library provides portable block cipher
implementations. It defines a block_cipher_protocol protocol with
the predicates:
encrypt_block/3decrypt_block/3block_size/1key_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 keyaes192, accepting a 24-byte keyaes256, 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/4anddecrypt/4for block-aligned input without paddingencrypt_padded/4anddecrypt_padded/4using PKCS#7 padding
The cbc object provides:
encrypt/5anddecrypt/5for block-aligned input without paddingencrypt_padded/5anddecrypt_padded/5using 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