Add CFB variant with 8-bit segment size.
Signed-off-by: Dmitry Eremin-Solenikov dbaryshkov@gmail.com --- cfb.c | 75 +++++++++++++++++++++ cfb.h | 35 ++++++++++ nettle.texinfo | 96 +++++++++++++++++++++++++-- testsuite/cfb-test.c | 103 +++++++++++++++++++++++++++++ testsuite/testutils.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ testsuite/testutils.h | 7 ++ 6 files changed, 487 insertions(+), 7 deletions(-)
---
CFB8 support, contributed by Dmitry Eremin-Solenikov. * cfb.c (cfb8_encrypt, cfb8_decrypt): New functions. * cfb.h (CFB8_CTX, CFB8_SET_IV, CFB8_ENCRYPT, CFB8_DECRYPT): New macros. * testsuite/cfb-test.c: CFB8 test cases. * testsuite/testutils.c (test_cipher_cfb8): New function. * nettle.texinfo (CFB8): Documentation.
diff --git a/cfb.c b/cfb.c index 805b8c4533a0..8e0ca47130d4 100644 --- a/cfb.c +++ b/cfb.c @@ -162,3 +162,78 @@ cfb_decrypt(const void *ctx, nettle_cipher_func *f, } } } + +/* CFB-8 uses slight optimization: it encrypts or decrypts up to block_size + * bytes and does memcpy/memxor afterwards */ +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); + uint8_t i = 0; + + memcpy(buffer, iv, block_size); + memcpy(buffer + block_size, src, + length < block_size ? length : block_size); + + while (length) + { + + 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; + + memcpy(buffer, buffer + block_size, block_size); + memcpy(buffer + block_size, src, + length < block_size ? length : block_size); + + } + + memcpy(iv, buffer + i, block_size); +} diff --git a/cfb.h b/cfb.h index 16660df9b8ab..782ac133aa1c 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 aa374449c527..e610e74c79ab 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -94,6 +94,7 @@ Cipher modes * CBC:: * CTR:: * CFB:: +* CFB8:: * GCM:: * CCM::
@@ -1904,21 +1905,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 @@ -2090,7 +2092,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
@@ -2175,6 +2177,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 b59bee225bf8..b83233830b5a 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 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 08471958fbc0..5ee9eda35264 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 CFB variant with 8-bit segment size.
Thanks. I'm fine with separate functions like you do, that seems to be the most straightforward interface.
+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);
- uint8_t i = 0;
- memcpy(buffer, iv, block_size);
- memcpy(buffer + block_size, src,
length < block_size ? length : block_size);
- while (length)
- {
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);
If there's no other reason to handle src == dst and src != dst separately in this function, I think it's better to use memxor3 unconditionally.
diff --git a/nettle.texinfo b/nettle.texinfo index aa374449c527..e610e74c79ab 100644
+@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.
Do you think it would help the reader if the CFB and CFB8 docs were merged into a single section?
Regards, /Niels
nettle-bugs@lists.lysator.liu.se