Add support for CFB and CFB8 modes support. CFB uses segment size = block size, CFB8 uses 8-bit segments.
Changes since V1:
- Add CFB8 support - Minor corrections in documentation.
Dmitry Eremin-Solenikov (2): Add CFB block mode support Add CFB8 - Cipher Feedback 8-bit block cipher mode
Makefile.in | 4 +- cfb.c | 257 +++++++++++++++++++++++++++++ cfb.h | 122 ++++++++++++++ nettle.texinfo | 188 ++++++++++++++++++++-- testsuite/.gitignore | 1 + testsuite/.test-rules.make | 3 + testsuite/Makefile.in | 2 +- testsuite/cfb-test.c | 390 +++++++++++++++++++++++++++++++++++++++++++++ testsuite/testutils.c | 357 +++++++++++++++++++++++++++++++++++++++++ testsuite/testutils.h | 14 ++ 10 files changed, 1326 insertions(+), 12 deletions(-) create mode 100644 cfb.c create mode 100644 cfb.h create mode 100644 testsuite/cfb-test.c
Signed-off-by: Dmitry Eremin-Solenikov dbaryshkov@gmail.com --- Makefile.in | 4 +- cfb.c | 257 ++++++++++++++++++++++++++++++++++++++++ cfb.h | 87 ++++++++++++++ nettle.texinfo | 106 +++++++++++++++-- testsuite/.gitignore | 1 + testsuite/.test-rules.make | 3 + testsuite/Makefile.in | 2 +- testsuite/cfb-test.c | 287 +++++++++++++++++++++++++++++++++++++++++++++ testsuite/testutils.c | 179 ++++++++++++++++++++++++++++ testsuite/testutils.h | 7 ++ 10 files changed, 921 insertions(+), 12 deletions(-) create mode 100644 cfb.c create mode 100644 cfb.h create mode 100644 testsuite/cfb-test.c
diff --git a/Makefile.in b/Makefile.in index 1aa2b29db702..920145132864 100644 --- a/Makefile.in +++ b/Makefile.in @@ -91,7 +91,7 @@ nettle_SOURCES = aes-decrypt-internal.c aes-decrypt.c \ camellia256-set-decrypt-key.c \ camellia256-meta.c \ cast128.c cast128-meta.c cbc.c \ - ccm.c ccm-aes128.c ccm-aes192.c ccm-aes256.c \ + ccm.c ccm-aes128.c ccm-aes192.c ccm-aes256.c cfb.c \ chacha-crypt.c chacha-core-internal.c \ chacha-poly1305.c chacha-poly1305-meta.c \ chacha-set-key.c chacha-set-nonce.c \ @@ -188,7 +188,7 @@ OPT_SOURCES = fat-x86_64.c fat-arm.c mini-gmp.c
HEADERS = aes.h arcfour.h arctwo.h asn1.h blowfish.h \ base16.h base64.h bignum.h buffer.h camellia.h cast128.h \ - cbc.h ccm.h chacha.h chacha-poly1305.h ctr.h \ + cbc.h ccm.h cfb.h chacha.h chacha-poly1305.h ctr.h \ curve25519.h des.h des-compat.h dsa.h dsa-compat.h eax.h \ ecc-curve.h ecc.h ecdsa.h eddsa.h \ gcm.h gosthash94.h hmac.h \ diff --git a/cfb.c b/cfb.c new file mode 100644 index 000000000000..97b99f3e269b --- /dev/null +++ b/cfb.c @@ -0,0 +1,257 @@ +/* cfb.c + + Cipher feedback mode. + + Copyright (C) 2015 Dmitry Eremin-Solenikov + Copyright (C) 2001, 2011 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "cfb.h" + +#include "memxor.h" +#include "nettle-internal.h" + +void +cfb_encrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src) +{ + if (src != dst) + { + uint8_t *p; + + for (p = iv; length >= block_size; p = dst, dst += block_size, src += block_size, length -= block_size) + { + f(ctx, block_size, dst, p); + memxor(dst, src, block_size); + } + + if (p != iv) + memcpy(iv, p, block_size); + + if (length) + { + TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE); + TMP_ALLOC(buffer, block_size); + + f(ctx, block_size, buffer, iv); + memxor3(dst, buffer, src, length); + memcpy(iv, dst, length); + } + } + else + { + TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE); + TMP_ALLOC(buffer, block_size); + uint8_t *p; + + for (p = iv; length >= block_size; p = dst, dst += block_size, length -= block_size) + { + f(ctx, block_size, buffer, p); + memxor(dst, buffer, block_size); + } + + if (p != iv) + memcpy(iv, p, block_size); + + if (length) + { + f(ctx, block_size, buffer, iv); + memxor(dst, buffer, length); + memcpy(iv, dst, length); + } + } +} + +/* Don't allocate any more space than this on the stack */ +#define CFB_BUFFER_LIMIT 512 + +void +cfb_decrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src) +{ + if (src != dst) + { + size_t left = length % block_size; + + length -= left; + if (length > 0) + { + /* Decrypt in ECB mode */ + f(ctx, block_size, dst, iv); + f(ctx, length - block_size, dst + block_size, src); + memcpy(iv, src + length - block_size, block_size); + memxor(dst, src, length); + } + + if (left > 0) + { + TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE); + TMP_ALLOC(buffer, block_size); + + f(ctx, block_size, buffer, iv); + memxor3(dst + length, src + length, buffer, left); + memcpy(iv, src + length, left); + } + } + else + { + /* For in-place CFB, we decrypt into a temporary buffer of size + * at most CFB_BUFFER_LIMIT, and process that amount of data at + * a time. */ + + /* NOTE: We assume that block_size <= CFB_BUFFER_LIMIT */ + + TMP_DECL(buffer, uint8_t, CFB_BUFFER_LIMIT); + TMP_DECL(initial_iv, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE); + + size_t buffer_size; + size_t left; + + buffer_size = CFB_BUFFER_LIMIT - (CFB_BUFFER_LIMIT % block_size); + + TMP_ALLOC(buffer, buffer_size); + TMP_ALLOC(initial_iv, block_size); + + left = length % block_size; + length -= left; + + while (length > 0) + { + uint32_t part = length > buffer_size ? buffer_size : length; + + /* length is greater that zero and is divided by block_size, so it is + * not less than block_size. So does part */ + + f(ctx, block_size, buffer, iv); + f(ctx, part - block_size, buffer + block_size, src); + memcpy(iv, src + part - block_size, block_size); + memxor(dst, buffer, part); + + length -= part; + src += part; + dst += part; + } + + if (left > 0) + { + f(ctx, block_size, buffer, iv); + memxor(dst, buffer, left); + memcpy(iv, src, block_size); + } + } +} + +void +cfb8_encrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src) +{ + TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE * 2); + TMP_DECL(outbuf, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE); + TMP_ALLOC(buffer, block_size * 2); + TMP_ALLOC(outbuf, block_size); + uint8_t pos; + + memcpy(buffer, iv, block_size); + pos = 0; + while (length) + { + uint8_t t; + + if (pos == block_size) + { + memcpy(buffer, buffer + block_size, block_size); + pos = 0; + } + + f(ctx, block_size, outbuf, buffer + pos); + t = *(dst++) = *(src++) ^ outbuf[0]; + buffer[pos + block_size] = t; + length--; + pos ++; + } + memcpy(iv, buffer + pos, block_size); +} + +void +cfb8_decrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src) +{ + TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE * 2); + TMP_DECL(outbuf, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE * 2); + TMP_ALLOC(buffer, block_size * 2); + TMP_ALLOC(outbuf, block_size * 2); + + memcpy(buffer, iv, block_size); + memcpy(buffer + block_size, src, + length < block_size ? length : block_size); + while (length) + { + uint8_t i; + + for (i = 0; i < length && i < block_size; i++) + f(ctx, block_size, outbuf + i, buffer + i); + + if (src != dst) + memxor3(dst, src, outbuf, i); + else + memxor(dst, outbuf, i); + + length -= i; + src += i; + dst += i; + + if (length != 0) + { + memcpy(buffer, buffer + block_size, block_size); + memcpy(buffer + block_size, + src, length < block_size ? length : block_size); + memset(outbuf, 0, 2 * block_size); + } + else + { + memcpy(iv, buffer + i, block_size); + } + } +} diff --git a/cfb.h b/cfb.h new file mode 100644 index 000000000000..76134eeb41a0 --- /dev/null +++ b/cfb.h @@ -0,0 +1,87 @@ +/* cfb.h + + Cipher feedback mode. + + Copyright (C) 2015 Dmitry Eremin-Solenikov + Copyright (C) 2001 Niels Möller + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_CFB_H_INCLUDED +#define NETTLE_CFB_H_INCLUDED + +#include "nettle-types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Name mangling */ +#define cfb_encrypt nettle_cfb_encrypt +#define cfb_decrypt nettle_cfb_decrypt + +void +cfb_encrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src); + +void +cfb_decrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src); + +#define CFB_CTX(type, size) \ +{ type ctx; uint8_t iv[size]; } + +#define CFB_SET_IV(ctx, data) \ +memcpy((ctx)->iv, (data), sizeof((ctx)->iv)) + +/* NOTE: Avoid using NULL, as we don't include anything defining it. */ +#define CFB_ENCRYPT(self, f, length, dst, src) \ + (0 ? ((f)(&(self)->ctx, ~(size_t) 0, \ + (uint8_t *) 0, (const uint8_t *) 0)) \ + : cfb_encrypt((void *) &(self)->ctx, \ + (nettle_cipher_func *) (f), \ + sizeof((self)->iv), (self)->iv, \ + (length), (dst), (src))) + +#define CFB_DECRYPT(self, f, length, dst, src) \ + (0 ? ((f)(&(self)->ctx, ~(size_t) 0, \ + (uint8_t *) 0, (const uint8_t *) 0)) \ + : cfb_decrypt((void *) &(self)->ctx, \ + (nettle_cipher_func *) (f), \ + sizeof((self)->iv), (self)->iv, \ + (length), (dst), (src))) + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_CFB_H_INCLUDED */ diff --git a/nettle.texinfo b/nettle.texinfo index 6eada3db11ba..23eed3357d06 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -93,6 +93,7 @@ Cipher modes
* CBC:: * CTR:: +* CFB:: * GCM:: * CCM::
@@ -1884,21 +1885,23 @@ a message that is larger than the cipher's block size. As explained in processing them independently with the block cipher (Electronic Code Book mode, @acronym{ECB}), leaks information.
-Besides @acronym{ECB}, Nettle provides a two other modes of operation: -Cipher Block Chaining (@acronym{CBC}), Counter mode (@acronym{CTR}), and -a couple of @acronym{AEAD} modes (@pxref{Authenticated encryption}). -@acronym{CBC} is widely used, but there are a few subtle issues of -information leakage, see, e.g., +Besides @acronym{ECB}, Nettle provides several other modes of operation: +Cipher Block Chaining (@acronym{CBC}), Counter mode (@acronym{CTR}), Cipher +Feedback (@acronym{CFB}) and a couple of @acronym{AEAD} modes +(@pxref{Authenticated encryption}). @acronym{CBC} is widely used, but +there are a few subtle issues of information leakage, see, e.g., @uref{http://www.kb.cert.org/vuls/id/958563, @acronym{SSH} @acronym{CBC} vulnerability}. Today, @acronym{CTR} is usually preferred over @acronym{CBC}.
-Modes like @acronym{CBC} and @acronym{CTR} provide @emph{no} message -authentication, and should always be used together with a @acronym{MAC} -(@pxref{Keyed hash functions}) or signature to authenticate the message. +Modes like @acronym{CBC}, @acronym{CTR} and @acronym{CFB} provide @emph{no} +message authentication, and should always be used together with a +@acronym{MAC} (@pxref{Keyed hash functions}) or signature to authenticate +the message.
@menu * CBC:: * CTR:: +* CFB:: @end menu
@node CBC, CTR, Cipher modes, Cipher modes @@ -1994,7 +1997,7 @@ These macros use some tricks to make the compiler display a warning if the types of @var{f} and @var{ctx} don't match, e.g. if you try to use an @code{struct aes_ctx} context with the @code{des_encrypt} function.
-@node CTR, , CBC, Cipher modes +@node CTR, CFB, CBC, Cipher modes @comment node-name, next, previous, up @subsection Counter mode
@@ -2070,6 +2073,91 @@ last three arguments define the source and destination area for the operation. @end deffn
+@node CFB, , CTR, Cipher modes +@comment node-name, next, previous, up +@subsection Cipher Feedback mode + +@cindex Cipher Feedback Mode +@cindex CFB Mode + +Cipher Feedback mode (@acronym{CFB}) being a close relative to both +@acronym{CBC} mode and @acronym{CTR} mode borrows some characteristics +from stream ciphers. + +The message is divided into @code{n} blocks @code{M_1},@dots{} +@code{M_n}, where @code{M_n} is of size @code{m} which may be smaller +than the block size. Except for the last block, all the message blocks +must be of size equal to the cipher's block size. + +If @code{E_k} is the encryption function of a block cipher, @code{IV} is +the initialization vector, then the @code{n} plaintext blocks are +transformed into @code{n} ciphertext blocks @code{C_1},@dots{} +@code{C_n} as follows: + +@example +C_1 = E_k(IV) XOR M_1 +C_2 = E_k(C_1) XOR M_2 + +@dots{} + +C_(n-1) = E_k(C_(n - 2)) XOR M_(n-1) +C_n = E_k(C_(n - 1)) [1..m] XOR M_n +@end example + +Nettle's includes two functions for applying a block cipher in Cipher +Feedback (@acronym{CFB}) mode, one for encryption and one for +decryption. These functions uses @code{void *} to pass cipher contexts +around. + +@deftypefun {void} cfb_encrypt (const void *@var{ctx}, nettle_cipher_func *@var{f}, size_t @var{block_size}, uint8_t *@var{iv}, size_t @var{length}, uint8_t *@var{dst}, const uint8_t *@var{src}) +@deftypefunx {void} cfb_decrypt (const void *@var{ctx}, nettle_cipher_func *@var{f}, size_t @var{block_size}, uint8_t *@var{iv}, size_t @var{length}, uint8_t *@var{dst}, const uint8_t *@var{src}) + +Applies the encryption or decryption function @var{f} in @acronym{CFB} +mode. The final ciphertext block processed is copied into @var{iv} +before returning, so that a large message can be processed by a sequence +of calls to @code{cfb_encrypt}. Note that for @acronym{CFB} mode +internally uses encryption only function and hence @var{f} should always +be the encryption function for the underlying block cipher. + +When a message is encrypted using a sequence of calls to +@code{cfb_encrypt}, all but the last call @emph{must} use a length that +is a multiple of the block size. +@end deftypefun + +Like for @acronym{CBC}, there are also a couple of helper macros. + +@deffn Macro CFB_CTX (@var{context_type}, @var{block_size}) +Expands to +@example +@{ + context_type ctx; + uint8_t iv[block_size]; +@} +@end example +@end deffn + +@deffn Macro CFB_SET_IV(@var{ctx}, @var{iv}) +First argument is a pointer to a context struct as defined by +@code{CFB_CTX}, and the second is a pointer to an initialization vector +that is copied into that context. +@end deffn + +@deffn Macro CFB_ENCRYPT (@var{ctx}, @var{f}, @var{length}, @var{dst}, @var{src}) +A simpler way to invoke @code{cfb_encrypt}. The first argument is a +pointer to a context struct as defined by @code{CFB_CTX}, and the second +argument is an encryption function following Nettle's conventions. The +last three arguments define the source and destination area for the +operation. +@end deffn + +@deffn Macro CFB_DECRYPT (@var{ctx}, @var{f}, @var{length}, @var{dst}, @var{src}) +A simpler way to invoke @code{cfb_decrypt}. The first argument is a +pointer to a context struct as defined by @code{CFB_CTX}, and the second +argument is an encryption function following Nettle's conventions. The +last three arguments define the source and destination area for the +operation. +@end deffn + @node Authenticated encryption, Keyed hash functions, Cipher modes, Reference @comment node-name, next, previous, up
diff --git a/testsuite/.gitignore b/testsuite/.gitignore index a50bc44c8fcd..8558b7f3b02f 100644 --- a/testsuite/.gitignore +++ b/testsuite/.gitignore @@ -12,6 +12,7 @@ /cast128-test /cbc-test /ccm-test +/cfb-test /chacha-poly1305-test /chacha-test /ctr-test diff --git a/testsuite/.test-rules.make b/testsuite/.test-rules.make index a9732694a5f0..1f7803103104 100644 --- a/testsuite/.test-rules.make +++ b/testsuite/.test-rules.make @@ -115,6 +115,9 @@ knuth-lfib-test$(EXEEXT): knuth-lfib-test.$(OBJEXT) cbc-test$(EXEEXT): cbc-test.$(OBJEXT) $(LINK) cbc-test.$(OBJEXT) $(TEST_OBJS) -o cbc-test$(EXEEXT)
+cfb-test$(EXEEXT): cfb-test.$(OBJEXT) + $(LINK) cfb-test.$(OBJEXT) $(TEST_OBJS) -o cfb-test$(EXEEXT) + ctr-test$(EXEEXT): ctr-test.$(OBJEXT) $(LINK) ctr-test.$(OBJEXT) $(TEST_OBJS) -o ctr-test$(EXEEXT)
diff --git a/testsuite/Makefile.in b/testsuite/Makefile.in index 6276043060f3..80233e83fc20 100644 --- a/testsuite/Makefile.in +++ b/testsuite/Makefile.in @@ -25,7 +25,7 @@ TS_NETTLE_SOURCES = aes-test.c arcfour-test.c arctwo-test.c \ sha3-384-test.c sha3-512-test.c \ serpent-test.c twofish-test.c version-test.c \ knuth-lfib-test.c \ - cbc-test.c ctr-test.c gcm-test.c eax-test.c ccm-test.c \ + cbc-test.c cfb-test.c ctr-test.c gcm-test.c eax-test.c ccm-test.c \ poly1305-test.c chacha-poly1305-test.c \ hmac-test.c umac-test.c \ meta-hash-test.c meta-cipher-test.c\ diff --git a/testsuite/cfb-test.c b/testsuite/cfb-test.c new file mode 100644 index 000000000000..aafc96d2f029 --- /dev/null +++ b/testsuite/cfb-test.c @@ -0,0 +1,287 @@ +#include "testutils.h" +#include "aes.h" +#include "cfb.h" +#include "knuth-lfib.h" + +/* Test with more data and inplace decryption, to check that the + * cfb_decrypt buffering works. */ +#define CFB_BULK_DATA 0x2710 /* 10000 */ + +static void +test_cfb_bulk(void) +{ + struct knuth_lfib_ctx random; + + uint8_t clear[CFB_BULK_DATA]; + + uint8_t cipher[CFB_BULK_DATA + 1]; + + const uint8_t *key = H("966c7bf00bebe6dc 8abd37912384958a" + "743008105a08657d dcaad4128eee38b3"); + + const uint8_t *start_iv = H("11adbff119749103 207619cfa0e8d13a"); + const uint8_t *end_iv = H("1fd0a9189b8480b7 b06a2b36ef5943ba"); + + struct CFB_CTX(struct aes_ctx, AES_BLOCK_SIZE) aes; + + knuth_lfib_init(&random, CFB_BULK_DATA); + knuth_lfib_random(&random, CFB_BULK_DATA, clear); + + /* Byte that should not be overwritten */ + cipher[CFB_BULK_DATA] = 17; + + aes_set_encrypt_key(&aes.ctx, 32, key); + CFB_SET_IV(&aes, start_iv); + + CFB_ENCRYPT(&aes, aes_encrypt, CFB_BULK_DATA, cipher, clear); + + ASSERT(cipher[CFB_BULK_DATA] == 17); + + if (verbose) + { + printf("IV after bulk encryption: "); + print_hex(AES_BLOCK_SIZE, aes.iv); + printf("\n"); + } + + ASSERT(MEMEQ(AES_BLOCK_SIZE, aes.iv, end_iv)); + + /* Decrypt, in place */ + aes_set_encrypt_key(&aes.ctx, 32, key); + CFB_SET_IV(&aes, start_iv); + CFB_DECRYPT(&aes, aes_encrypt, CFB_BULK_DATA, cipher, cipher); + + ASSERT(cipher[CFB_BULK_DATA] == 17); + + if (verbose) + { + printf("IV after bulk decryption: "); + print_hex(AES_BLOCK_SIZE, aes.iv); + printf("\n"); + } + + ASSERT (MEMEQ(AES_BLOCK_SIZE, aes.iv, end_iv)); + ASSERT (MEMEQ(CFB_BULK_DATA, clear, cipher)); +} + +void +test_main(void) +{ + /* From NIST spec 800-38a on AES modes. + * + * F.3 CFB Example Vectors + * F.3.13 CFB128-AES128.Encrypt + */ + + /* Intermediate values, blocks input to AES: + * + * 000102030405060708090a0b0c0d0e0f + * 3b3fd92eb72dad20333449f8e83cfb4a + * c8a64537a0b3a93fcde3cdad9f1ce58b + * 26751f67a3cbb140b1808cf187a4f4df + */ + test_cipher_cfb(&nettle_aes128, + SHEX("2b7e151628aed2a6abf7158809cf4f3c"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710"), + SHEX("3b3fd92eb72dad20333449f8e83cfb4a" + "c8a64537a0b3a93fcde3cdad9f1ce58b" + "26751f67a3cbb140b1808cf187a4f4df" + "c04b05357c5d1c0eeac4c66f9ff7f2e6"), + SHEX("000102030405060708090a0b0c0d0e0f")); + + /* F.3.15 CFB128-AES192.Encrypt */ + + /* Intermediate values, blocks input to AES: + * + * 000102030405060708090a0b0c0d0e0f + * cdc80d6fddf18cab34c25909c99a4174 + * 67ce7f7f81173621961a2b70171d3d7a + * 2e1e8a1dd59b88b1c8e60fed1efac4c9 + */ + + test_cipher_cfb(&nettle_aes192, + SHEX("8e73b0f7da0e6452c810f32b809079e5" + "62f8ead2522c6b7b"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710"), + SHEX("cdc80d6fddf18cab34c25909c99a4174" + "67ce7f7f81173621961a2b70171d3d7a" + "2e1e8a1dd59b88b1c8e60fed1efac4c9" + "c05f9f9ca9834fa042ae8fba584b09ff"), + SHEX("000102030405060708090a0b0c0d0e0f")); + + /* F.3.17 CFB128-AES256.Encrypt */ + + /* Intermediate values, blcoks input to AES: + * + * 000102030405060708090a0b0c0d0e0f + * dc7e84bfda79164b7ecd8486985d3860 + * 39ffed143b28b1c832113c6331e5407b + * df10132415e54b92a13ed0a8267ae2f9 + */ + + test_cipher_cfb(&nettle_aes256, + SHEX("603deb1015ca71be2b73aef0857d7781" + "1f352c073b6108d72d9810a30914dff4"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d8a571e03ac9c9eb76fac45af8e51" + "30c81c46a35ce411e5fbc1191a0a52ef" + "f69f2445df4f9b17ad2b417be66c3710"), + SHEX("dc7e84bfda79164b7ecd8486985d3860" + "39ffed143b28b1c832113c6331e5407b" + "df10132415e54b92a13ed0a8267ae2f9" + "75a385741ab9cef82031623d55b1e471"), + SHEX("000102030405060708090a0b0c0d0e0f")); + + test_cfb_bulk(); +} + +/* +F.3.13 CFB128-AES128.Encrypt +Key 2b7e151628aed2a6abf7158809cf4f3c +IV 000102030405060708090a0b0c0d0e0f +Segment #1 +Input Block 000102030405060708090a0b0c0d0e0f +Output Block 50fe67cc996d32b6da0937e99bafec60 +Plaintext 6bc1bee22e409f96e93d7e117393172a +Ciphertext 3b3fd92eb72dad20333449f8e83cfb4a +Segment #2 +Input Block 3b3fd92eb72dad20333449f8e83cfb4a +Output Block 668bcf60beb005a35354a201dab36bda +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Ciphertext c8a64537a0b3a93fcde3cdad9f1ce58b +Segment #3 +Input Block c8a64537a0b3a93fcde3cdad9f1ce58b +Output Block 16bd032100975551547b4de89daea630 +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Ciphertext 26751f67a3cbb140b1808cf187a4f4df +Segment #4 +Input Block 26751f67a3cbb140b1808cf187a4f4df +Output Block 36d42170a312871947ef8714799bc5f6 +Plaintext f69f2445df4f9b17ad2b417be66c3710 +Ciphertext c04b05357c5d1c0eeac4c66f9ff7f2e6 +F.3.14 CFB128-AES128.Decrypt +Key 2b7e151628aed2a6abf7158809cf4f3c +IV 000102030405060708090a0b0c0d0e0f +Segment #1 +Input Block 000102030405060708090a0b0c0d0e0f +Output Block 50fe67cc996d32b6da0937e99bafec60 +Ciphertext 3b3fd92eb72dad20333449f8e83cfb4a +Plaintext 6bc1bee22e409f96e93d7e117393172a +Segment #2 +Input Block 3b3fd92eb72dad20333449f8e83cfb4a +Output Block 668bcf60beb005a35354a201dab36bda +Ciphertext c8a64537a0b3a93fcde3cdad9f1ce58b +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Segment #3 +Input Block c8a64537a0b3a93fcde3cdad9f1ce58b +Output Block 16bd032100975551547b4de89daea630 +Ciphertext 26751f67a3cbb140b1808cf187a4f4df +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Segment #4 +Input Block 26751f67a3cbb140b1808cf187a4f4df +Output Block 36d42170a312871947ef8714799bc5f6 +Ciphertext c04b05357c5d1c0eeac4c66f9ff7f2e6 +Plaintext f69f2445df4f9b17ad2b417be66c3710 +F.3.15 CFB128-AES192.Encrypt +Key 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b +000102030405060708090a0b0c0d0e0f +Segment #1 +50 +IV +Input Block 000102030405060708090a0b0c0d0e0f +Output Block a609b38df3b1133dddff2718ba09565e +Plaintext 6bc1bee22e409f96e93d7e117393172a +Ciphertext cdc80d6fddf18cab34c25909c99a4174 +Segment #2 +Input Block cdc80d6fddf18cab34c25909c99a4174 +Output Block c9e3f5289f149abd08ad44dc52b2b32b +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Ciphertext 67ce7f7f81173621961a2b70171d3d7a +Segment #3 +Input Block 67ce7f7f81173621961a2b70171d3d7a +Output Block 1ed6965b76c76ca02d1dcef404f09626 +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Ciphertext 2e1e8a1dd59b88b1c8e60fed1efac4c9 +Segment #4 +Input Block 2e1e8a1dd59b88b1c8e60fed1efac4c9 +Output Block 36c0bbd976ccd4b7ef85cec1be273eef +Plaintext f69f2445df4f9b17ad2b417be66c3710 +Ciphertext c05f9f9ca9834fa042ae8fba584b09ff +F.3.16 CFB128-AES192.Decrypt +Key 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b +IV 000102030405060708090a0b0c0d0e0f +Segment #1 +Input Block 000102030405060708090a0b0c0d0e0f +Output Block a609b38df3b1133dddff2718ba09565e +Ciphertext cdc80d6fddf18cab34c25909c99a4174 +Plaintext 6bc1bee22e409f96e93d7e117393172a +Segment #2 +Input Block cdc80d6fddf18cab34c25909c99a4174 +Output Block c9e3f5289f149abd08ad44dc52b2b32b +Ciphertext 67ce7f7f81173621961a2b70171d3d7a +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Segment #3 +Input Block 67ce7f7f81173621961a2b70171d3d7a +Output Block 1ed6965b76c76ca02d1dcef404f09626 +Ciphertext 2e1e8a1dd59b88b1c8e60fed1efac4c9 +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Segment #4 +Input Block 2e1e8a1dd59b88b1c8e60fed1efac4c9 +Output Block 36c0bbd976ccd4b7ef85cec1be273eef +Ciphertext c05f9f9ca9834fa042ae8fba584b09ff +Plaintext f69f2445df4f9b17ad2b417be66c3710 +F.3.17 CFB128-AES256.Encrypt +Key 603deb1015ca71be2b73aef0857d7781 +1f352c073b6108d72d9810a30914dff4 +IV 000102030405060708090a0b0c0d0e0f +Segment #1 +Input Block 000102030405060708090a0b0c0d0e0f +Output Block b7bf3a5df43989dd97f0fa97ebce2f4a +Plaintext 6bc1bee22e409f96e93d7e117393172a +Ciphertext dc7e84bfda79164b7ecd8486985d3860 +Segment #2 +Input Block dc7e84bfda79164b7ecd8486985d3860 +Output Block 97d26743252b1d54aca653cf744ace2a +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Ciphertext 39ffed143b28b1c832113c6331e5407b +Segment #3 +Input Block 39ffed143b28b1c832113c6331e5407b +Output Block efd80f62b6b9af8344c511b13c70b016 +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Ciphertext df10132415e54b92a13ed0a8267ae2f9 +Segment #4 +Input Block df10132415e54b92a13ed0a8267ae2f9 +Output Block 833ca131c5f655ef8d1a2346b3ddd361 +Plaintext f69f2445df4f9b17ad2b417be66c3710 +Ciphertext 75a385741ab9cef82031623d55b1e471 +F.3.18 CFB128-AES256.Decrypt +Key 603deb1015ca71be2b73aef0857d7781 +1f352c073b6108d72d9810a30914dff4 +IV 000102030405060708090a0b0c0d0e0f +Segment #1 +Input Block 000102030405060708090a0b0c0d0e0f +Output Block b7bf3a5df43989dd97f0fa97ebce2f4a +Ciphertext dc7e84bfda79164b7ecd8486985d3860 +Plaintext 6bc1bee22e409f96e93d7e117393172a +Segment #2 +Input Block dc7e84bfda79164b7ecd8486985d3860 +Output Block 97d26743252b1d54aca653cf744ace2a +Ciphertext 39ffed143b28b1c832113c6331e5407b +Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 +Segment #3 +Input Block 39ffed143b28b1c832113c6331e5407b +Output Block efd80f62b6b9af8344c511b13c70b016 +Ciphertext df10132415e54b92a13ed0a8267ae2f9 +Plaintext 30c81c46a35ce411e5fbc1191a0a52ef +Segment #4 +Input Block df10132415e54b92a13ed0a8267ae2f9 +Output Block 833ca131c5f655ef8d1a2346b3ddd361 +Ciphertext 75a385741ab9cef82031623d55b1e471 +Plaintext f69f2445df4f9b17ad2b417be66c3710 +*/ diff --git a/testsuite/testutils.c b/testsuite/testutils.c index 05130c19a8aa..6ce13c4e59f1 100644 --- a/testsuite/testutils.c +++ b/testsuite/testutils.c @@ -4,6 +4,7 @@
#include "base16.h" #include "cbc.h" +#include "cfb.h" #include "ctr.h" #include "knuth-lfib.h" #include "macros.h" @@ -244,6 +245,184 @@ test_cipher_cbc(const struct nettle_cipher *cipher, free(iv); }
+void +test_cipher_cfb(const struct nettle_cipher *cipher, + const struct tstring *key, + const struct tstring *cleartext, + const struct tstring *ciphertext, + const struct tstring *iiv) +{ + void *ctx = xalloc(cipher->context_size); + uint8_t *data, *data2; + uint8_t *iv = xalloc(cipher->block_size); + size_t length; + + ASSERT (cleartext->length == ciphertext->length); + length = cleartext->length; + + ASSERT (key->length == cipher->key_size); + ASSERT (iiv->length == cipher->block_size); + + data = xalloc(length); + data2 = xalloc(length); + + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, cleartext->data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB encrypt failed:\nInput:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data2, data); + + if (!MEMEQ(length, data2, cleartext->data)) + { + fprintf(stderr, "CFB decrypt failed:\nInput:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data2); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + memcpy(data, cleartext->data, length); + + cfb_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB inplace encrypt failed:\nInput:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, cleartext->data)) + { + fprintf(stderr, "CFB inplace decrypt failed:\nInput:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\n"); + FAIL(); + } + + /* Repeat all tests with incomplete last block */ + length -= 1; + + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, cleartext->data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB encrypt failed:\nInput:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data2, data); + + if (!MEMEQ(length, data2, cleartext->data)) + { + fprintf(stderr, "CFB decrypt failed:\nInput:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data2); + fprintf(stderr, "\nExpected:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + memcpy(data, cleartext->data, length); + + cfb_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB inplace encrypt failed:\nInput:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, cleartext->data)) + { + fprintf(stderr, "CFB inplace decrypt failed:\nInput:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\n"); + FAIL(); + } + + free(ctx); + free(data); + free(data2); + free(iv); +} + void test_cipher_ctr(const struct nettle_cipher *cipher, const struct tstring *key, diff --git a/testsuite/testutils.h b/testsuite/testutils.h index eb520453c8ca..fbbba7b9fab5 100644 --- a/testsuite/testutils.h +++ b/testsuite/testutils.h @@ -122,6 +122,13 @@ test_cipher_cbc(const struct nettle_cipher *cipher, const struct tstring *ciphertext, const struct tstring *iv);
+void +test_cipher_cfb(const struct nettle_cipher *cipher, + const struct tstring *key, + const struct tstring *cleartext, + const struct tstring *ciphertext, + const struct tstring *iv); + void test_cipher_ctr(const struct nettle_cipher *cipher, const struct tstring *key,
I also have a few comments on the code.
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
+void +cfb_encrypt(const void *ctx, nettle_cipher_func *f,
size_t block_size, uint8_t *iv,
size_t length, uint8_t *dst,
const uint8_t *src)
+{
- if (src != dst)
- {
uint8_t *p;
for (p = iv; length >= block_size; p = dst, dst += block_size, src += block_size, length -= block_size)
- {
f(ctx, block_size, dst, p);
memxor(dst, src, block_size);
- }
if (p != iv)
- memcpy(iv, p, block_size);
if (length)
- {
TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE);
TMP_ALLOC(buffer, block_size);
f(ctx, block_size, buffer, iv);
memxor3(dst, buffer, src, length);
memcpy(iv, dst, length);
- }
- }
- else
- {
TMP_DECL(buffer, uint8_t, NETTLE_MAX_CIPHER_BLOCK_SIZE);
TMP_ALLOC(buffer, block_size);
uint8_t *p;
for (p = iv; length >= block_size; p = dst, dst += block_size, length -= block_size)
- {
f(ctx, block_size, buffer, p);
memxor(dst, buffer, block_size);
- }
if (p != iv)
- memcpy(iv, p, block_size);
if (length)
- {
f(ctx, block_size, buffer, iv);
memxor(dst, buffer, length);
memcpy(iv, dst, length);
- }
- }
+}
It may be nice to reduce the code duplication for the two cases; only the block loop really needs to be different. I think it's fine to move allocation of buffer up front even if it's unnecessary in the case src != dst and there's no left-over; it's only an alloca so it's pretty check. Also unconditionally using memxor3 rather than memxor for the left-over is a small overhead. Do you think those operations are performance critical?
Also for cfb_decrypt it would be nice to take common parts out of the src != dst condition, but not quite obvious how to do it.
+/* Don't allocate any more space than this on the stack */ +#define CFB_BUFFER_LIMIT 512
+void +cfb_decrypt(const void *ctx, nettle_cipher_func *f,
size_t block_size, uint8_t *iv,
size_t length, uint8_t *dst,
const uint8_t *src)
+{
- if (src != dst)
- {
size_t left = length % block_size;
Avoiding % would be nice, division by non-constants is expensive (might apply to the old cbc code too). But perhaps tricky, since for the final part we need to call f with a length which is rounded down to an integral number of blocks.
while (length > 0)
- {
uint32_t part = length > buffer_size ? buffer_size : length;
Should be size_t, not uint32_t.
+void +cfb8_encrypt(const void *ctx, nettle_cipher_func *f,
size_t block_size, uint8_t *iv,
size_t length, uint8_t *dst,
const uint8_t *src)
+{
As I wrote in my other mail, I don't think we need a separate function for this. My understanding is that the main use case of CFB8 is when we get one octet at a time to encrypt and transmit, i.e., tis function called with length == 1. If that's right, there's little use to optimize it's performance for larger length.
Regards, /Niels
Add CFB variant with 8-bit segment size.
Signed-off-by: Dmitry Eremin-Solenikov dbaryshkov@gmail.com --- cfb.h | 35 ++++++++++ nettle.texinfo | 96 +++++++++++++++++++++++++-- testsuite/cfb-test.c | 103 +++++++++++++++++++++++++++++ testsuite/testutils.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ testsuite/testutils.h | 7 ++ 5 files changed, 412 insertions(+), 7 deletions(-)
diff --git a/cfb.h b/cfb.h index 76134eeb41a0..b383b628d0a0 100644 --- a/cfb.h +++ b/cfb.h @@ -45,6 +45,9 @@ extern "C" { #define cfb_encrypt nettle_cfb_encrypt #define cfb_decrypt nettle_cfb_decrypt
+#define cfb8_encrypt nettle_cfb8_encrypt +#define cfb8_decrypt nettle_cfb8_decrypt + void cfb_encrypt(const void *ctx, nettle_cipher_func *f, size_t block_size, uint8_t *iv, @@ -57,12 +60,28 @@ cfb_decrypt(const void *ctx, nettle_cipher_func *f, size_t length, uint8_t *dst, const uint8_t *src);
+void +cfb8_encrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src); + +void +cfb8_decrypt(const void *ctx, nettle_cipher_func *f, + size_t block_size, uint8_t *iv, + size_t length, uint8_t *dst, + const uint8_t *src); + + #define CFB_CTX(type, size) \ { type ctx; uint8_t iv[size]; }
#define CFB_SET_IV(ctx, data) \ memcpy((ctx)->iv, (data), sizeof((ctx)->iv))
+#define CFB8_CTX CFB_CTX +#define CFB8_SET_IV CFB_SET_IV + /* NOTE: Avoid using NULL, as we don't include anything defining it. */ #define CFB_ENCRYPT(self, f, length, dst, src) \ (0 ? ((f)(&(self)->ctx, ~(size_t) 0, \ @@ -80,6 +99,22 @@ memcpy((ctx)->iv, (data), sizeof((ctx)->iv)) sizeof((self)->iv), (self)->iv, \ (length), (dst), (src)))
+#define CFB8_ENCRYPT(self, f, length, dst, src) \ + (0 ? ((f)(&(self)->ctx, ~(size_t) 0, \ + (uint8_t *) 0, (const uint8_t *) 0)) \ + : cfb8_encrypt((void *) &(self)->ctx, \ + (nettle_cipher_func *) (f), \ + sizeof((self)->iv), (self)->iv, \ + (length), (dst), (src))) + +#define CFB8_DECRYPT(self, f, length, dst, src) \ + (0 ? ((f)(&(self)->ctx, ~(size_t) 0, \ + (uint8_t *) 0, (const uint8_t *) 0)) \ + : cfb8_decrypt((void *) &(self)->ctx, \ + (nettle_cipher_func *) (f), \ + sizeof((self)->iv), (self)->iv, \ + (length), (dst), (src))) + #ifdef __cplusplus } #endif diff --git a/nettle.texinfo b/nettle.texinfo index 23eed3357d06..8c05dda6d7fc 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -94,6 +94,7 @@ Cipher modes * CBC:: * CTR:: * CFB:: +* CFB8:: * GCM:: * CCM::
@@ -1887,21 +1888,22 @@ Book mode, @acronym{ECB}), leaks information.
Besides @acronym{ECB}, Nettle provides several other modes of operation: Cipher Block Chaining (@acronym{CBC}), Counter mode (@acronym{CTR}), Cipher -Feedback (@acronym{CFB}) and a couple of @acronym{AEAD} modes -(@pxref{Authenticated encryption}). @acronym{CBC} is widely used, but +Feedback (@acronym{CFB} and @acronym{CFB8}) and a couple of @acronym{AEAD} +modes (@pxref{Authenticated encryption}). @acronym{CBC} is widely used, but there are a few subtle issues of information leakage, see, e.g., @uref{http://www.kb.cert.org/vuls/id/958563, @acronym{SSH} @acronym{CBC} vulnerability}. Today, @acronym{CTR} is usually preferred over @acronym{CBC}.
-Modes like @acronym{CBC}, @acronym{CTR} and @acronym{CFB} provide @emph{no} -message authentication, and should always be used together with a -@acronym{MAC} (@pxref{Keyed hash functions}) or signature to authenticate -the message. +Modes like @acronym{CBC}, @acronym{CTR}, @acronym{CFB} and @acronym{CFB8} +provide @emph{no} message authentication, and should always be used together +with a @acronym{MAC} (@pxref{Keyed hash functions}) or signature to +authenticate the message.
@menu * CBC:: * CTR:: * CFB:: +* CFB8:: @end menu
@node CBC, CTR, Cipher modes, Cipher modes @@ -2073,7 +2075,7 @@ last three arguments define the source and destination area for the operation. @end deffn
-@node CFB, , CTR, Cipher modes +@node CFB, CFB8, CTR, Cipher modes @comment node-name, next, previous, up @subsection Cipher Feedback mode
@@ -2158,6 +2160,86 @@ last three arguments define the source and destination area for the operation. @end deffn
+@node CFB8, , CFB, Cipher modes +@comment node-name, next, previous, up +@subsection Cipher Feedback 8-bit mode + +@cindex Cipher Feedback 8-bit Mode +@cindex CFB8 Mode + +Cipher Feedback 8-bit mode (@acronym{CFB8}) transforms block cipher into a stream +cipher. The message is encrypted byte after byte, not requiring any padding. + +If @code{E_k} is the encryption function of a block cipher, @code{b} is +@code{E_k} block size, @code{IV} is the initialization vector, then the +@code{n} plaintext bytes are transformed into @code{n} ciphertext bytes +@code{C_1},@dots{} @code{C_n} as follows: + +@example +I_1 = IV +C_1 = E_k(I_1) [1..8] XOR M_1 +I_2 = I_1 [9..b] << 8 | C_1 +C_2 = E_k(I_2) [1..8] XOR M_2 + +@dots{} + +I_(n-1) = I_(n-2) [9..b] << 8 | C_(n-2) +C_(n-1) = E_k(I_(n-1)) [1..8] XOR M_(n-1) +I_n = I_(n-1) [9..b] << 8 | C_(n-1) +C_n = E_k(I_n) [1..8] XOR M_n +@end example + +Nettle's includes two functions for applying a block cipher in Cipher +Feedback 8-bit (@acronym{CFB8}) mode, one for encryption and one for +decryption. These functions uses @code{void *} to pass cipher contexts +around. + +@deftypefun {void} cfb8_encrypt (const void *@var{ctx}, nettle_cipher_func *@var{f}, size_t @var{block_size}, uint8_t *@var{iv}, size_t @var{length}, uint8_t *@var{dst}, const uint8_t *@var{src}) +@deftypefunx {void} cfb8_decrypt (const void *@var{ctx}, nettle_cipher_func *@var{f}, size_t @var{block_size}, uint8_t *@var{iv}, size_t @var{length}, uint8_t *@var{dst}, const uint8_t *@var{src}) + +Applies the encryption or decryption function @var{f} in @acronym{CFB8} +mode. The final IV block processed is copied into @var{iv} +before returning, so that a large message can be processed by a sequence of +calls to @code{cfb8_encrypt}. Note that for @acronym{CFB8} mode internally +uses encryption only function and hence @var{f} should always be the +encryption function for the underlying block cipher. + +@end deftypefun + +Like for @acronym{CBC}, there are also a couple of helper macros. + +@deffn Macro CFB8_CTX (@var{context_type}, @var{block_size}) +Expands to +@example +@{ + context_type ctx; + uint8_t iv[block_size]; +@} +@end example +@end deffn + +@deffn Macro CFB8_SET_IV(@var{ctx}, @var{iv}) +First argument is a pointer to a context struct as defined by +@code{CFB8_CTX}, and the second is a pointer to an initialization vector +that is copied into that context. +@end deffn + +@deffn Macro CFB8_ENCRYPT (@var{ctx}, @var{f}, @var{length}, @var{dst}, @var{src}) +A simpler way to invoke @code{cfb8_encrypt}. The first argument is a +pointer to a context struct as defined by @code{CFB8_CTX}, and the +second argument is an encryption function following Nettle's +conventions. The last three arguments define the source and destination +area for the operation. +@end deffn + +@deffn Macro CFB8_DECRYPT (@var{ctx}, @var{f}, @var{length}, @var{dst}, @var{src}) +A simpler way to invoke @code{cfb8_decrypt}. The first argument is a +pointer to a context struct as defined by @code{CFB8_CTX}, and the +second argument is an encryption function following Nettle's +conventions. The last three arguments define the source and destination +area for the operation. +@end deffn + @node Authenticated encryption, Keyed hash functions, Cipher modes, Reference @comment node-name, next, previous, up
diff --git a/testsuite/cfb-test.c b/testsuite/cfb-test.c index aafc96d2f029..c527f9f8f3d2 100644 --- a/testsuite/cfb-test.c +++ b/testsuite/cfb-test.c @@ -6,6 +6,7 @@ /* Test with more data and inplace decryption, to check that the * cfb_decrypt buffering works. */ #define CFB_BULK_DATA 0x2710 /* 10000 */ +#define CFB8_BULK_DATA CFB_BULK_DATA
static void test_cfb_bulk(void) @@ -64,9 +65,110 @@ test_cfb_bulk(void) ASSERT (MEMEQ(CFB_BULK_DATA, clear, cipher)); }
+static void +test_cfb8_bulk(void) +{ + struct knuth_lfib_ctx random; + + uint8_t clear[CFB8_BULK_DATA]; + + uint8_t cipher[CFB8_BULK_DATA + 1]; + + const uint8_t *key = H("966c7bf00bebe6dc 8abd37912384958a" + "743008105a08657d dcaad4128eee38b3"); + + const uint8_t *start_iv = H("11adbff119749103 207619cfa0e8d13a"); + const uint8_t *end_iv = H("f84bfd48206f5803 6ef86f4e69e9aec0"); + + struct CFB8_CTX(struct aes_ctx, AES_BLOCK_SIZE) aes; + + knuth_lfib_init(&random, CFB8_BULK_DATA); + knuth_lfib_random(&random, CFB8_BULK_DATA, clear); + + /* Byte that should not be overwritten */ + cipher[CFB8_BULK_DATA] = 17; + + aes_set_encrypt_key(&aes.ctx, 32, key); + CFB8_SET_IV(&aes, start_iv); + + CFB8_ENCRYPT(&aes, aes_encrypt, CFB8_BULK_DATA, cipher, clear); + + ASSERT(cipher[CFB8_BULK_DATA] == 17); + + if (verbose) + { + printf("IV after bulk encryption: "); + print_hex(AES_BLOCK_SIZE, aes.iv); + printf("\n"); + } + + ASSERT(MEMEQ(AES_BLOCK_SIZE, aes.iv, end_iv)); + + /* Decrypt, in place */ + aes_set_encrypt_key(&aes.ctx, 32, key); + CFB8_SET_IV(&aes, start_iv); + CFB8_DECRYPT(&aes, aes_encrypt, CFB8_BULK_DATA, cipher, cipher); + + ASSERT(cipher[CFB8_BULK_DATA] == 17); + + if (verbose) + { + printf("IV after bulk decryption: "); + print_hex(AES_BLOCK_SIZE, aes.iv); + printf("\n"); + } + + ASSERT (MEMEQ(AES_BLOCK_SIZE, aes.iv, end_iv)); + ASSERT (MEMEQ(CFB8_BULK_DATA, clear, cipher)); +} + void test_main(void) { + /* From NIST spec 800-38a on AES modes. + * + * F.3 CFB Example Vectors + * F.3.7 CFB8-AES128.Encrypt + */ + + test_cipher_cfb8(&nettle_aes128, + SHEX("2b7e151628aed2a6abf7158809cf4f3c"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d"), + SHEX("3b79424c9c0dd436bace9e0ed4586a4f" + "32b9"), + SHEX("000102030405060708090a0b0c0d0e0f")); + + /* From NIST spec 800-38a on AES modes. + * + * F.3 CFB Example Vectors + * F.3.9 CFB8-AES192.Encrypt + */ + + test_cipher_cfb8(&nettle_aes192, + SHEX("8e73b0f7da0e6452c810f32b809079e5" + "62f8ead2522c6b7b"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d"), + SHEX("cda2521ef0a905ca44cd057cbf0d47a0" + "678a"), + SHEX("000102030405060708090a0b0c0d0e0f")); + + /* From NIST spec 800-38a on AES modes. + * + * F.3 CFB Example Vectors + * F.3.11 CFB8-AES256.Encrypt + */ + + test_cipher_cfb8(&nettle_aes256, + SHEX("603deb1015ca71be2b73aef0857d7781" + "1f352c073b6108d72d9810a30914dff4"), + SHEX("6bc1bee22e409f96e93d7e117393172a" + "ae2d"), + SHEX("dc1f1a8520a64db55fcc8ac554844e88" + "9700"), + SHEX("000102030405060708090a0b0c0d0e0f")); + /* From NIST spec 800-38a on AES modes. * * F.3 CFB Example Vectors @@ -139,6 +241,7 @@ test_main(void) SHEX("000102030405060708090a0b0c0d0e0f"));
test_cfb_bulk(); + test_cfb8_bulk(); }
/* diff --git a/testsuite/testutils.c b/testsuite/testutils.c index 6ce13c4e59f1..657a41389828 100644 --- a/testsuite/testutils.c +++ b/testsuite/testutils.c @@ -423,6 +423,184 @@ test_cipher_cfb(const struct nettle_cipher *cipher, free(iv); }
+void +test_cipher_cfb8(const struct nettle_cipher *cipher, + const struct tstring *key, + const struct tstring *cleartext, + const struct tstring *ciphertext, + const struct tstring *iiv) +{ + void *ctx = xalloc(cipher->context_size); + uint8_t *data, *data2; + uint8_t *iv = xalloc(cipher->block_size); + size_t length; + + ASSERT (cleartext->length == ciphertext->length); + length = cleartext->length; + + ASSERT (key->length == cipher->key_size); + ASSERT (iiv->length == cipher->block_size); + + data = xalloc(length); + data2 = xalloc(length); + + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, cleartext->data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB8 encrypt failed:\nInput:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data2, data); + + if (!MEMEQ(length, data2, cleartext->data)) + { + fprintf(stderr, "CFB8 decrypt failed:\nInput:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data2); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + memcpy(data, cleartext->data, length); + + cfb8_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB8 inplace encrypt failed:\nInput:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, cleartext->data)) + { + fprintf(stderr, "CFB8 inplace decrypt failed:\nInput:"); + tstring_print_hex(ciphertext); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + tstring_print_hex(cleartext); + fprintf(stderr, "\n"); + FAIL(); + } + + /* Repeat all tests with incomplete last block */ + length -= 1; + + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, cleartext->data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB8 encrypt failed:\nInput:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data2, data); + + if (!MEMEQ(length, data2, cleartext->data)) + { + fprintf(stderr, "CFB8 decrypt failed:\nInput:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data2); + fprintf(stderr, "\nExpected:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + memcpy(data, cleartext->data, length); + + cfb8_encrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, ciphertext->data)) + { + fprintf(stderr, "CFB8 inplace encrypt failed:\nInput:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\n"); + FAIL(); + } + cipher->set_encrypt_key(ctx, key->data); + memcpy(iv, iiv->data, cipher->block_size); + + cfb8_decrypt(ctx, cipher->encrypt, + cipher->block_size, iv, + length, data, data); + + if (!MEMEQ(length, data, cleartext->data)) + { + fprintf(stderr, "CFB8 inplace decrypt failed:\nInput:"); + print_hex(length, ciphertext->data); + fprintf(stderr, "\nOutput: "); + print_hex(length, data); + fprintf(stderr, "\nExpected:"); + print_hex(length, cleartext->data); + fprintf(stderr, "\n"); + FAIL(); + } + + free(ctx); + free(data); + free(data2); + free(iv); +} + void test_cipher_ctr(const struct nettle_cipher *cipher, const struct tstring *key, diff --git a/testsuite/testutils.h b/testsuite/testutils.h index fbbba7b9fab5..ded57db6ab4f 100644 --- a/testsuite/testutils.h +++ b/testsuite/testutils.h @@ -129,6 +129,13 @@ test_cipher_cfb(const struct nettle_cipher *cipher, const struct tstring *ciphertext, const struct tstring *iv);
+void +test_cipher_cfb8(const struct nettle_cipher *cipher, + const struct tstring *key, + const struct tstring *cleartext, + const struct tstring *ciphertext, + const struct tstring *iv); + void test_cipher_ctr(const struct nettle_cipher *cipher, const struct tstring *key,
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
Add support for CFB and CFB8 modes support. CFB uses segment size = block size, CFB8 uses 8-bit segments.
That wasn't quite what I meant. Assume we have a block size of 16 octets, or 128 bits, for simplicity. I don't think we need separate functions for CFB8 or other segment sizes; instead I think it would be nice if cfb_encrypt worked as follows:
Call with length == 16:
Encrypt one "segment" of 16 octets, aka CFB128.
Call with length == 40:
Encrypt two segments of 16 octets each, and the left over as one segment of 8 octets, aka CFB64.
Call with length == 1:
Encrypt one segment of one octet, aka CFB8
Call with length == 3:
Encrypt one segment of three octets, aka CFB24.
...etc. Calls with different length, with the same context, can be mixed, each call will correspond to ceil(length / 16) segments, where the last segment may be smaller than 16.
As far as I understand your code and the spec, I think your cfb_encrypt almost does this, all that's needed is to change the way the iv is updated for the left-over segment, and the NIST spec seemed reasonably clear on how it should be done. Do you think that makes sense?
Regards, /Niels
2017-10-03 18:32 GMT+03:00 Niels Möller nisse@lysator.liu.se:
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
Add support for CFB and CFB8 modes support. CFB uses segment size = block size, CFB8 uses 8-bit segments.
That wasn't quite what I meant. Assume we have a block size of 16 octets, or 128 bits, for simplicity. I don't think we need separate functions for CFB8 or other segment sizes; instead I think it would be nice if cfb_encrypt worked as follows:
Hmm. I've never encountered things like CFB24 in wild. Just CFB1 (1-bit nightmare), CFB8 and CFB64/128 (depending on block size). Moreover it does not seem logical to merge everything into the same cfb_encrypt/cfb_decrypt pair. Such merge would allow one to easily intermix different segment lengths within one stream (and such usage contradicts the standard).
Call with length == 16:
Encrypt one "segment" of 16 octets, aka CFB128.
Call with length == 40:
Encrypt two segments of 16 octets each, and the left over as one segment of 8 octets, aka CFB64.
Call with length == 1:
Encrypt one segment of one octet, aka CFB8
Call with length == 3:
Encrypt one segment of three octets, aka CFB24.
...etc. Calls with different length, with the same context, can be mixed, each call will correspond to ceil(length / 16) segments, where the last segment may be smaller than 16.
According to the standard, data stream uses one segment length for the whole stream.
As far as I understand your code and the spec, I think your cfb_encrypt almost does this, all that's needed is to change the way the iv is updated for the left-over segment, and the NIST spec seemed reasonably clear on how it should be done. Do you think that makes sense?
Regards, /Niels
-- Niels Möller. PGP-encrypted email is preferred. Keyid 368C6677. Internet email is subject to wholesale government surveillance.
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
Hmm. I've never encountered things like CFB24 in wild. Just CFB1 (1-bit nightmare), CFB8 and CFB64/128 (depending on block size). Moreover it does not seem logical to merge everything into the same cfb_encrypt/cfb_decrypt pair. Such merge would allow one to easily intermix different segment lengths within one stream (and such usage contradicts the standard).
Hmm, I'd have to read up a bit more to have a solid opinion.
What about message sizes which aren't a multiple of the block size? As I understood your code, it would be possible to call cfb_encrypt with an input lenght of, e.g., 19 octets, which would be processed as one CFB128 segment and one CFB24 segment. Which is a mix of different segment lengths. That's why it seemed natural to me to generalize to any mix of segment lengths. Am I missing something?
I wonder what the openpgp usecase is. If there's no obvious need for message sizes which are not a multiple of the block size, I'd also be happy to go in the other direction and require full blocks, and hence *only* do CFB128 (or whatever the block size is). We could then extend the functionality later, if/when we have figured out what's useful.
According to the standard, data stream uses one segment length for the whole stream.
I'll have to read more carefully, at my first look, it seemed well defined how to take as input an iv, a keyed cipher, and a segment of arbitrary size (<= block size), and produce an ciphertext of the same size and an updated iv. With all history in the iv, without any other dependency on the sizes of previous segments.
Regards, /Niels
2017-10-03 23:04 GMT+03:00 Niels Möller nisse@lysator.liu.se:
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
Hmm. I've never encountered things like CFB24 in wild. Just CFB1 (1-bit nightmare), CFB8 and CFB64/128 (depending on block size). Moreover it does not seem logical to merge everything into the same cfb_encrypt/cfb_decrypt pair. Such merge would allow one to easily intermix different segment lengths within one stream (and such usage contradicts the standard).
Hmm, I'd have to read up a bit more to have a solid opinion.
What about message sizes which aren't a multiple of the block size? As I understood your code, it would be possible to call cfb_encrypt with an input lenght of, e.g., 19 octets, which would be processed as one CFB128 segment and one CFB24 segment. Which is a mix of different segment lengths. That's why it seemed natural to me to generalize to any mix of segment lengths. Am I missing something?
No, it is still CFB128, but with last segment being of partial size. SP800-38A supports that kind of operation (segments + partial one).
I wonder what the openpgp usecase is. If there's no obvious need for message sizes which are not a multiple of the block size, I'd also be happy to go in the other direction and require full blocks, and hence *only* do CFB128 (or whatever the block size is). We could then extend the functionality later, if/when we have figured out what's useful.
As far as I understood, OpenPGP uses the segment size = block size case (cfb_encrypt/decrypt in my patch), but with a bit strange startup sequence.
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
2017-10-03 23:04 GMT+03:00 Niels Möller nisse@lysator.liu.se:
What about message sizes which aren't a multiple of the block size? As I understood your code, it would be possible to call cfb_encrypt with an input lenght of, e.g., 19 octets, which would be processed as one CFB128 segment and one CFB24 segment. Which is a mix of different segment lengths. That's why it seemed natural to me to generalize to any mix of segment lengths. Am I missing something?
No, it is still CFB128, but with last segment being of partial size. SP800-38A supports that kind of operation (segments + partial one).
I've had another read of SP800-38A, section 6.3. It says "The CFB mode also requires an integer parameter, denoted s, such that 1 <= s <= b. In the specification of the CFB mode below, each plaintext segment (P j) and ciphertext segment (C j) consists of s bits.". So no partial segments, and I see no support for that in the section on padding either.
Now, the ciphertext for a partial segment is no problem, I see only one reasonable way to define that. Question is, what the output iv should be? E.g, if you want to set an initial iv, encrypt two 19-octet messages, with the output iv after the first message be the input iv for the second. I haven't seen neither a spec or a testcase for that, do you know any?
I see three reasonable approaches:
1. Support only segments matching the block size (i.e., CFB128 for a 16-octet block size), no partial segments.
2. Allow a partial segment, and encrypt it in the same way as if it had been padded to a block boundary with arbitrary data. Then the output iv is the complete output block from the block cipher.
3. Apply the small-segment formula for iv update,
Ij = LSB_{b-s}(I{j-1]) | C_{j -1}
The way I think about that is that we construct a key-stream sequence to be xor:ed to the plain text. And the output iv is then always the last 16 bytes of that sequence.
To me, (1) is easiest, I'm not sure if there are any clear use cases for partial segments. And it can easily be extended later. (2) is analogous to how Nettle does CTR mode. But for CFB it seems a bit non-standard, I'd rather not do that unless there's some important protocol or application which uses CFB in this way, and includes some test vectors.
(3) is a bit more work. It's reasonably easy to document, and it has the advantage, that it gives us small-segment support for free, and it can be tested to some degree using CFB8 test vectors.
What do you think? If I understood your code correctly, it does neither (2) or (3).
And one other unrelated question: Isn't encrypt and decrypt identical for CFB? We xor a keystream to the message to encrypt or decrypt. And the key stream is defined by the cipher, key and segment size (or sequence of segment sizes, in case we support mixing sizes), regardless if we're going to use it for encrypt or decrypt. Or is there something subtle I'm missing?
Regards, /Niels
2017-10-04 17:55 GMT+03:00 Niels Möller nisse@lysator.liu.se:
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
2017-10-03 23:04 GMT+03:00 Niels Möller nisse@lysator.liu.se:
What about message sizes which aren't a multiple of the block size? As I understood your code, it would be possible to call cfb_encrypt with an input lenght of, e.g., 19 octets, which would be processed as one CFB128 segment and one CFB24 segment. Which is a mix of different segment lengths. That's why it seemed natural to me to generalize to any mix of segment lengths. Am I missing something?
No, it is still CFB128, but with last segment being of partial size. SP800-38A supports that kind of operation (segments + partial one).
I've had another read of SP800-38A, section 6.3. It says "The CFB mode also requires an integer parameter, denoted s, such that 1 <= s <= b. In the specification of the CFB mode below, each plaintext segment (P j) and ciphertext segment (C j) consists of s bits.". So no partial segments, and I see no support for that in the section on padding either.
Hmm. Strange. I always thought about CFB as about close-to-stream cipher.
Now, the ciphertext for a partial segment is no problem, I see only one reasonable way to define that. Question is, what the output iv should be? E.g, if you want to set an initial iv, encrypt two 19-octet messages, with the output iv after the first message be the input iv for the second. I haven't seen neither a spec or a testcase for that, do you know any?
I see three reasonable approaches:
Support only segments matching the block size (i.e., CFB128 for a 16-octet block size), no partial segments.
Allow a partial segment, and encrypt it in the same way as if it had been padded to a block boundary with arbitrary data. Then the output iv is the complete output block from the block cipher.
Apply the small-segment formula for iv update,
Ij = LSB_{b-s}(I{j-1]) | C_{j -1}
The way I think about that is that we construct a key-stream sequence to be xor:ed to the plain text. And the output iv is then always the last 16 bytes of that sequence.
To me, (1) is easiest, I'm not sure if there are any clear use cases for partial segments. And it can easily be extended later.
GOST uses partial last segment when doing PBES2 encryption.
(2) is analogous to how Nettle does CTR mode. But for CFB it seems a bit non-standard, I'd rather not do that unless there's some important protocol or application which uses CFB in this way, and includes some test vectors.
(3) is a bit more work. It's reasonably easy to document, and it has the advantage, that it gives us small-segment support for free, and it can be tested to some degree using CFB8 test vectors.
What do you think? If I understood your code correctly, it does neither (2) or (3).
I have no strong preference here. I updated last IV in a way that application can (manually, of course) resync to the stream in case it would like continue processing data after processing last (incomplete) segment.
And one other unrelated question: Isn't encrypt and decrypt identical for CFB? We xor a keystream to the message to encrypt or decrypt. And the key stream is defined by the cipher, key and segment size (or sequence of segment sizes, in case we support mixing sizes), regardless if we're going to use it for encrypt or decrypt. Or is there something subtle I'm missing?
Yes, the feedback of ciphertext. You were probably thinking about OFB, where encrypted IV is fed back, rather than whole ciphertext.
BTW: do you want CFB8 mode implemented or I can drop it back from next version of patchset?
Dmitry Eremin-Solenikov dbaryshkov@gmail.com writes:
I see three reasonable approaches:
Support only segments matching the block size (i.e., CFB128 for a 16-octet block size), no partial segments.
Allow a partial segment, and encrypt it in the same way as if it had been padded to a block boundary with arbitrary data. Then the output iv is the complete output block from the block cipher.
Apply the small-segment formula for iv update,
Ij = LSB_{b-s}(I{j-1]) | C_{j -1}
The way I think about that is that we construct a key-stream sequence to be xor:ed to the plain text. And the output iv is then always the last 16 bytes of that sequence.
I got this wrong, the output iv, by that rule, is the last 16 bytes of *ciphertext*, not of the key stream. Which, as you say, is why encrypt and decrypt differ.
And then for option (2), we'd have to pretend some particular padding of the partial segment, e.g., padding with zeros.
To me, (1) is easiest, I'm not sure if there are any clear use cases for partial segments. And it can easily be extended later.
GOST uses partial last segment when doing PBES2 encryption.
Does it depend on the output iv, or does it explicitly set a new iv for the next message?
I'm looking at Nettle docs for CTR, it doesn't say what happens to the iv/ctr if you encrypt one message with a partial block last, and then another message, without manually setting the counter in between. It just says that all but the last call must use a length that is a multiple of the block size.
The implementation simply increments the counter by one also after the partial block, and discards the left-over of the key stream. While I guess an application might expect it to work like a stream cipher and buffer the left-over key stream.
So we also have the option
4. Leave unspecified what happens to the iv after processing a partial segment. And require the application to explicitly set a new iv for each message if it needs to encrypt several messages with partial last segment.
(2) is analogous to how Nettle does CTR mode. But for CFB it seems a bit non-standard, I'd rather not do that unless there's some important protocol or application which uses CFB in this way, and includes some test vectors.
(3) is a bit more work. It's reasonably easy to document, and it has the advantage, that it gives us small-segment support for free, and it can be tested to some degree using CFB8 test vectors.
What do you think? If I understood your code correctly, it does neither (2) or (3).
I have no strong preference here. I updated last IV in a way that application can (manually, of course) resync to the stream in case it would like continue processing data after processing last (incomplete) segment.
I lean towards (3), which would mean always updating the iv so that it's the most recent block_size octets of the cipher text. As far as I understand, that would almost for free bring support for a sequences with arbitrary fixed segment size according to the specification, as well as non-standard mixing of segments of different sizes (not sure if that makes any cryptographic sense; but I see no obvious way why that would break).
But I'd like to hear opinions from additional people, what do others think?
Yes, the feedback of ciphertext. You were probably thinking about OFB, where encrypted IV is fed back, rather than whole ciphertext.
You're right, I misread the iv update rules.
BTW: do you want CFB8 mode implemented or I can drop it back from next version of patchset?
You can drop the separate CFB8 functions for now.
Regards, /Niels
nettle-bugs@lists.lysator.liu.se