Browse Source

Add support for CFB1 and CFB8

Signed-off-by: Steffen Jaeckel <[email protected]>
Steffen Jaeckel 1 year ago
parent
commit
cf79facfb9

+ 42 - 10
doc/crypt.tex

@@ -799,7 +799,7 @@ size of the cipher.  Given a key $k$, a plaintext $P$ and a cipher $E$ we shall
 $P$ under the key $k$ as $E_k(P)$.  In some modes there exists an initialization vector denoted as $C_{-1}$.
 
 \subsubsection{ECB Mode}
-\index{ECB mode}
+\index{ECB Mode}
 ECB or Electronic Codebook Mode is the simplest method to use.  It is given as:
 \begin{equation}
 C_i = E_k(P_i)
@@ -808,7 +808,7 @@ This mode is very weak since it allows people to swap blocks and perform replay
 than once.
 
 \subsubsection{CBC Mode}
-\index{CBC mode}
+\index{CBC Mode}
 CBC or Cipher Block Chaining mode is a simple mode designed to prevent trivial forms of replay and swap attacks on ciphers.
 It is given as:
 \begin{equation}
@@ -817,7 +817,7 @@ C_i = E_k(P_i \oplus C_{i - 1})
 It is important that the initialization vector be unique and preferably random for each message encrypted under the same key.
 
 \subsubsection{CTR Mode}
-\index{CTR mode}
+\index{CTR Mode}
 CTR or Counter Mode is a mode which only uses the encryption function of the cipher.  Given a initialization vector which is
 treated as a large binary counter the CTR mode is given as:
 \begin{eqnarray}
@@ -829,24 +829,24 @@ encrypted under the same key replay and swap attacks are infeasible.  CTR mode m
 as the block cipher is under a chosen plaintext attack (provided the initialization vector is unique).
 
 \subsubsection{CFB Mode}
-\index{CFB mode}
+\index{CFB Mode}
 CFB or Ciphertext Feedback Mode is a mode akin to CBC.  It is given as:
 \begin{eqnarray}
 C_i = P_i \oplus C_{-1} \nonumber \\
 C_{-1} = E_k(C_i)
 \end{eqnarray}
-Note that in this library the output feedback width is equal to the size of the block cipher.  That is this mode is used
-to encrypt whole blocks at a time.  However, the library will buffer data allowing the user to encrypt or decrypt partial
+The library supports all output feedback widths as specified in NIST SP 800-38A: CFB1, CFB8, and CFB64 resp. CFB128, i.e. equal
+to the size of the block cipher.  The library will buffer data allowing the user to encrypt or decrypt partial
 blocks without a delay.  When this mode is first setup it will initially encrypt the initialization vector as required.
 
 \subsubsection{OFB Mode}
-\index{OFB mode}
+\index{OFB Mode}
 OFB or Output Feedback Mode is a mode akin to CBC as well.  It is given as:
 \begin{eqnarray}
 C_{-1} = E_k(C_{-1}) \nonumber \\
 C_i = P_i \oplus C_{-1}
 \end{eqnarray}
-Like the CFB mode the output width in CFB mode is the same as the width of the block cipher.  OFB mode will also
+The output width in OFB mode is the same as the width of the block cipher.  OFB mode will also
 buffer the output which will allow you to encrypt or decrypt partial blocks without delay.
 
 \subsection{Choice of Mode}
@@ -874,8 +874,8 @@ support this mode directly but it is fairly easy to emulate with a call to the c
 The more sane way to deal with partial blocks is to pad them with zeroes, and then use CBC normally.
 
 \subsection{Initialization}
-\index{CBC Mode} \index{CTR Mode}
-\index{OFB Mode} \index{CFB Mode}
+\index{CBC Initialization} \index{CTR Initialization}
+\index{OFB Initialization} \index{CFB Initialization}
 The library provides simple support routines for handling CBC, CTR, CFB, OFB and ECB encoded messages.  Assuming the mode
 you want is XXX there is a structure called \textit{symmetric\_XXX} that will contain the information required to
 use that mode.  They have identical setup routines (except CTR and ECB mode):
@@ -913,6 +913,7 @@ is a pointer to the structure you want to hold the information for the mode of o
 The routines return {\bf CRYPT\_OK} if the cipher initialized correctly, otherwise, they return an error code.
 
 \subsubsection{CTR Mode}
+\index{CTR Initialization - specific}
 In the case of CTR mode there is an additional parameter \textit{ctr\_mode} which specifies the mode that the counter is to be used in.
 If \textbf{CTR\_COUNTER\_ LITTLE\_ENDIAN} was specified then the counter will be treated as a little endian value.  Otherwise, if
 \textbf{CTR\_COUNTER\_BIG\_ENDIAN} was specified the counter will be treated as a big endian value.  As of v1.15 the RFC 3686 style of
@@ -942,6 +943,37 @@ if ((err = ctr_start(find_cipher("aes"),
 Changing the counter size has little (really no) effect on the performance of the CTR chaining mode.  It is provided for compatibility
 with other software (and hardware) which have smaller fixed sized counters.
 
+\subsubsection{CFB Mode}
+\index{CFB Initialization - specific}
+
+In the case of the CFB mode there are multiple segment sizes possible.  The most common one, where each processed segment equals the
+block size of the underlying cipher, and two speciality modes. 1-bit CFB mode and 8-bit CFB mode, where each processed segment is
+either 1 or 8 bits wide.  Each segment denotes here one block cipher operation.
+To produce 16 bytes AES-CFB output, a single AES operation is required.
+To produce 16 bytes AES-CFB8 output, 16 AES operations are required.
+To produce 16 bytes AES-CFB1 output, 128 AES operations are required.
+
+The extended setup API looks as follows and accepts the values \textit{0, 1, 8 and 64 or 128}.  Whether \textit{64} or \textit{128} is
+accepted depends on the block size of the underlying cipher, \textit{0} will automatically select the block size as width.
+
+\begin{small}
+\begin{verbatim}
+/**
+   Extended initialization of a CFB context
+   @param cipher      The index of the cipher desired
+   @param IV          The initialization vector
+   @param key         The secret key
+   @param keylen      The length of the secret key (octets)
+   @param num_rounds  Number of rounds in the cipher desired (0 for default)
+   @param width       The width of the mode (0 for default)
+   @param cfb         The CFB state to initialize
+   @return CRYPT_OK if successful
+*/
+int cfb_start_ex(int cipher, const unsigned char *IV, const unsigned char *key,
+                 int keylen, int num_rounds, int width, symmetric_CFB *cfb);
+\end{verbatim}
+\end{small}
+
 \subsection{Encryption and Decryption}
 To actually encrypt or decrypt the following routines are provided:
 \index{ecb\_encrypt()} \index{ecb\_decrypt()} \index{cfb\_encrypt()} \index{cfb\_decrypt()}

+ 4 - 0
src/headers/tomcrypt_cipher.h

@@ -275,6 +275,8 @@ typedef struct {
    int                 cipher,
    /** The block size of the given cipher */
                        blocklen,
+   /** The width of the mode: 1, 8, 64, or 128 */
+                       width,
    /** The padding offset */
                        padlen;
 } symmetric_CFB;
@@ -910,6 +912,8 @@ int ecb_done(symmetric_ECB *ecb);
 #ifdef LTC_CFB_MODE
 int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key,
               int keylen, int num_rounds, symmetric_CFB *cfb);
+int cfb_start_ex(int cipher, const unsigned char *IV, const unsigned char *key,
+                 int keylen, int num_rounds, int width, symmetric_CFB *cfb);
 int cfb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_CFB *cfb);
 int cfb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_CFB *cfb);
 int cfb_getiv(unsigned char *IV, unsigned long *len, const symmetric_CFB *cfb);

+ 68 - 6
src/modes/cfb/cfb_decrypt.c

@@ -9,6 +9,28 @@
 
 #ifdef LTC_CFB_MODE
 
+static LTC_INLINE void s_shift1left_64(unsigned char *b, unsigned char v)
+{
+   ulong64 bval;
+   LOAD64H(bval, b);
+   bval <<= 1;
+   bval |= v & 0x01u;
+   STORE64H(bval, b);
+}
+
+static LTC_INLINE void s_shift1left_128(unsigned char *b, unsigned char v)
+{
+   ulong64 bval[2];
+   LOAD64H(bval[0], b);
+   LOAD64H(bval[1], b + 8);
+   bval[0] <<= 1;
+   bval[0] |= (bval[1] >> 63) & 0x01u;
+   bval[1] <<= 1;
+   bval[1] |= v & 0x01u;
+   STORE64H(bval[0], b);
+   STORE64H(bval[1], b + 8);
+}
+
 /**
    CFB decrypt
    @param ct      Ciphertext
@@ -20,11 +42,18 @@
 int cfb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, symmetric_CFB *cfb)
 {
    int err;
+   ulong64 bitlen = len * 8, bits_per_round;
+   unsigned int cur_bit = 0;
+   unsigned char pt_ = 0, ct_ = 0;
 
    LTC_ARGCHK(pt != NULL);
    LTC_ARGCHK(ct != NULL);
    LTC_ARGCHK(cfb != NULL);
 
+   if (bitlen < len) {
+      return CRYPT_OVERFLOW;
+   }
+
    if ((err = cipher_is_valid(cfb->cipher)) != CRYPT_OK) {
        return err;
    }
@@ -35,18 +64,51 @@ int cfb_decrypt(const unsigned char *ct, unsigned char *pt, unsigned long len, s
       return CRYPT_INVALID_ARG;
    }
 
-   while (len-- > 0) {
+   bits_per_round = cfb->width == 1 ? 1 : 8;
+
+   while (bitlen > 0) {
        if (cfb->padlen == cfb->blocklen) {
           if ((err = cipher_descriptor[cfb->cipher].ecb_encrypt(cfb->pad, cfb->IV, &cfb->key)) != CRYPT_OK) {
              return err;
           }
           cfb->padlen = 0;
        }
-       cfb->pad[cfb->padlen] = *ct;
-       *pt = *ct ^ cfb->IV[cfb->padlen];
-       ++pt;
-       ++ct;
-       ++(cfb->padlen);
+       switch (cfb->width) {
+         case 1:
+            if (cur_bit++ % 8 == 0) {
+               ct_ = *ct++;
+               pt_ = 0;
+            } else {
+               ct_ <<= 1;
+               pt_ <<= 1;
+            }
+            if (cfb->blocklen == 16)
+               s_shift1left_128(cfb->pad, ct_ >> 7);
+            else
+               s_shift1left_64(cfb->pad, ct_ >> 7);
+            pt_ |= ((ct_ ^ cfb->IV[0]) >> 7) & 0x01u;
+            cfb->padlen = cfb->blocklen;
+            if (cur_bit % 8 == 0) {
+               *pt++ = pt_;
+               cur_bit = 0;
+            }
+            break;
+         case 8:
+            XMEMMOVE(cfb->pad, cfb->pad + 1, cfb->blocklen - 1);
+            cfb->pad[cfb->blocklen - 1] = *ct;
+            *pt++ = *ct++ ^ cfb->IV[0];
+            cfb->padlen = cfb->blocklen;
+            break;
+         case 64:
+         case 128:
+            cfb->pad[cfb->padlen] = *ct;
+            *pt++ = *ct++ ^ cfb->IV[cfb->padlen];
+            ++(cfb->padlen);
+            break;
+         default:
+            return CRYPT_INVALID_ARG;
+      }
+      bitlen -= bits_per_round;
    }
    return CRYPT_OK;
 }

+ 74 - 11
src/modes/cfb/cfb_encrypt.c

@@ -9,6 +9,28 @@
 
 #ifdef LTC_CFB_MODE
 
+static LTC_INLINE void s_shift1left_64(unsigned char *b, unsigned char v)
+{
+   ulong64 bval;
+   LOAD64H(bval, b);
+   bval <<= 1;
+   bval |= v & 0x01u;
+   STORE64H(bval, b);
+}
+
+static LTC_INLINE void s_shift1left_128(unsigned char *b, unsigned char v)
+{
+   ulong64 bval[2];
+   LOAD64H(bval[0], b);
+   LOAD64H(bval[1], b + 8);
+   bval[0] <<= 1;
+   bval[0] |= (bval[1] >> 63) & 0x01u;
+   bval[1] <<= 1;
+   bval[1] |= v & 0x01u;
+   STORE64H(bval[0], b);
+   STORE64H(bval[1], b + 8);
+}
+
 /**
   CFB encrypt
   @param pt     Plaintext
@@ -20,11 +42,18 @@
 int cfb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, symmetric_CFB *cfb)
 {
    int err;
+   ulong64 bitlen = len * 8, bits_per_round;
+   unsigned int cur_bit = 0;
+   unsigned char pt_ = 0, ct_ = 0;
 
    LTC_ARGCHK(pt != NULL);
    LTC_ARGCHK(ct != NULL);
    LTC_ARGCHK(cfb != NULL);
 
+   if (bitlen < len) {
+      return CRYPT_OVERFLOW;
+   }
+
    if ((err = cipher_is_valid(cfb->cipher)) != CRYPT_OK) {
        return err;
    }
@@ -35,17 +64,51 @@ int cfb_encrypt(const unsigned char *pt, unsigned char *ct, unsigned long len, s
       return CRYPT_INVALID_ARG;
    }
 
-   while (len-- > 0) {
-       if (cfb->padlen == cfb->blocklen) {
-          if ((err = cipher_descriptor[cfb->cipher].ecb_encrypt(cfb->pad, cfb->IV, &cfb->key)) != CRYPT_OK) {
-             return err;
-          }
-          cfb->padlen = 0;
-       }
-       cfb->pad[cfb->padlen] = (*ct = *pt ^ cfb->IV[cfb->padlen]);
-       ++pt;
-       ++ct;
-       ++(cfb->padlen);
+   bits_per_round = cfb->width == 1 ? 1 : 8;
+
+   while (bitlen > 0) {
+      if (cfb->padlen == cfb->blocklen) {
+         if ((err = cipher_descriptor[cfb->cipher].ecb_encrypt(cfb->pad, cfb->IV, &cfb->key)) != CRYPT_OK) {
+            return err;
+         }
+         cfb->padlen = 0;
+      }
+      switch (cfb->width) {
+         case 1:
+            if (cur_bit++ % 8 == 0) {
+               pt_ = *pt++;
+               ct_ = 0;
+            } else {
+               pt_ <<= 1;
+               ct_ <<= 1;
+            }
+            ct_ |= ((pt_ ^ cfb->IV[0]) >> 7) & 0x01u;
+            if (cfb->blocklen == 16)
+               s_shift1left_128(cfb->pad, ct_);
+            else
+               s_shift1left_64(cfb->pad, ct_);
+            cfb->padlen = cfb->blocklen;
+            if (cur_bit % 8 == 0) {
+               *ct++ = ct_;
+               cur_bit = 0;
+            }
+            break;
+         case 8:
+            XMEMMOVE(cfb->pad, cfb->pad + 1, cfb->blocklen - 1);
+            cfb->pad[cfb->blocklen - 1] = (*ct = *pt ^ cfb->IV[0]);
+            ++pt;
+            ++ct;
+            cfb->padlen = cfb->blocklen;
+            break;
+         case 64:
+         case 128:
+             cfb->pad[cfb->padlen] = (*ct = *pt ^ cfb->IV[cfb->padlen]);
+             ++pt;
+             ++ct;
+             ++(cfb->padlen);
+            break;
+      }
+      bitlen -= bits_per_round;
    }
    return CRYPT_OK;
 }

+ 1 - 1
src/modes/cfb/cfb_getiv.c

@@ -25,7 +25,7 @@ int cfb_getiv(unsigned char *IV, unsigned long *len, const symmetric_CFB *cfb)
       *len = cfb->blocklen;
       return CRYPT_BUFFER_OVERFLOW;
    }
-   XMEMCPY(IV, cfb->IV, cfb->blocklen);
+   XMEMCPY(IV, cfb->pad, cfb->blocklen);
    *len = cfb->blocklen;
 
    return CRYPT_OK;

+ 1 - 0
src/modes/cfb/cfb_setiv.c

@@ -33,6 +33,7 @@ int cfb_setiv(const unsigned char *IV, unsigned long len, symmetric_CFB *cfb)
 
    /* force next block */
    cfb->padlen = 0;
+   XMEMCPY(cfb->pad, IV, len);
    return cipher_descriptor[cfb->cipher].ecb_encrypt(IV, cfb->IV, &cfb->key);
 }
 

+ 41 - 5
src/modes/cfb/cfb_start.c

@@ -10,18 +10,20 @@
 
 #ifdef LTC_CFB_MODE
 
+
 /**
-   Initialize a CFB context
+   Extended initialization of a CFB context
    @param cipher      The index of the cipher desired
    @param IV          The initialization vector
    @param key         The secret key
    @param keylen      The length of the secret key (octets)
    @param num_rounds  Number of rounds in the cipher desired (0 for default)
+   @param width       The width of the mode in bits (0 for default)
    @param cfb         The CFB state to initialize
    @return CRYPT_OK if successful
 */
-int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key,
-              int keylen, int num_rounds, symmetric_CFB *cfb)
+int cfb_start_ex(int cipher, const unsigned char *IV, const unsigned char *key,
+                 int keylen, int num_rounds, int width, symmetric_CFB *cfb)
 {
    int x, err;
 
@@ -33,12 +35,30 @@ int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key,
       return err;
    }
 
+   switch (width) {
+      case 0:
+         width = cipher_descriptor[cipher].block_length * 8;
+         break;
+      case 1:
+      case 8:
+         LTC_ARGCHK(cipher_descriptor[cipher].block_length == 8
+                    || cipher_descriptor[cipher].block_length == 16);
+         break;
+      case 64:
+      case 128:
+         LTC_ARGCHK(width == cipher_descriptor[cipher].block_length * 8);
+         break;
+      default:
+         return CRYPT_INVALID_ARG;
+   }
+
 
    /* copy data */
    cfb->cipher = cipher;
+   cfb->width = width;
    cfb->blocklen = cipher_descriptor[cipher].block_length;
    for (x = 0; x < cfb->blocklen; x++) {
-       cfb->IV[x] = IV[x];
+       cfb->pad[x] = IV[x];
    }
 
    /* init the cipher */
@@ -48,7 +68,23 @@ int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key,
 
    /* encrypt the IV */
    cfb->padlen = 0;
-   return cipher_descriptor[cfb->cipher].ecb_encrypt(cfb->IV, cfb->IV, &cfb->key);
+   return cipher_descriptor[cfb->cipher].ecb_encrypt(cfb->pad, cfb->IV, &cfb->key);
+}
+
+/**
+   Initialize a CFB context
+   @param cipher      The index of the cipher desired
+   @param IV          The initialization vector
+   @param key         The secret key
+   @param keylen      The length of the secret key (octets)
+   @param num_rounds  Number of rounds in the cipher desired (0 for default)
+   @param cfb         The CFB state to initialize
+   @return CRYPT_OK if successful
+*/
+int cfb_start(int cipher, const unsigned char *IV, const unsigned char *key,
+              int keylen, int num_rounds, symmetric_CFB *cfb)
+{
+   return cfb_start_ex(cipher, IV, key, keylen, num_rounds, 0, cfb);
 }
 
 #endif

+ 50 - 10
tests/modes_test.c

@@ -3,18 +3,42 @@
 /* test CFB/OFB/CBC modes */
 #include <tomcrypt_test.h>
 
+#ifdef LTC_CFB_MODE
+static const struct {
+   int width;
+   const char *key, *iv, *pt, *ct;
+} cfb_testvectors[] = {
+                       {
+                        1,
+                        "2b7e151628aed2a6abf7158809cf4f3c",
+                        "000102030405060708090a0b0c0d0e0f",
+                        "6bc1",
+                        "68b3",
+                       },
+                       {
+                        8,
+                        "2b7e151628aed2a6abf7158809cf4f3c",
+                        "000102030405060708090a0b0c0d0e0f",
+                        "6bc1b3e22e409f96e93d7e117393172aae2d",
+                        "3b79424c9c0dd436bace9e0ed4586a4f32b9",
+                       },
+};
+#endif
+
 int modes_test(void)
 {
    int ret = CRYPT_NOP;
 #ifdef LTC_CBC_MODE
    symmetric_CBC cbc;
 #endif
-#ifdef LTC_CFB_MODE
-   symmetric_CFB cfb;
-#endif
 #ifdef LTC_OFB_MODE
    symmetric_OFB ofb;
 #endif
+#ifdef LTC_CFB_MODE
+   symmetric_CFB cfb;
+   unsigned char tmp2[64];
+   unsigned long n;
+#endif
 #if defined(LTC_CBC_MODE) || defined(LTC_CFB_MODE) || defined(LTC_OFB_MODE)
    unsigned char pt[64], ct[64], tmp[64], key[16], iv[16], iv2[16];
    int cipher_idx;
@@ -70,19 +94,35 @@ int modes_test(void)
    l = sizeof(iv2);
    DO(ret = cfb_getiv(iv2, &l, &cfb));
    /* note we don't memcmp iv2/iv since cfb_start processes the IV for the first block */
-   if (l != 16) {
-      fprintf(stderr, "cfb_getiv failed");
-      return 1;
-   }
+   ENSURE(l == 16);
    DO(ret = cfb_encrypt(pt, ct, 64, &cfb));
 
    /* decode the block */
    DO(ret = cfb_setiv(iv, l, &cfb));
    zeromem(tmp, sizeof(tmp));
    DO(ret = cfb_decrypt(ct, tmp, 64, &cfb));
-   if (memcmp(tmp, pt, 64) != 0) {
-      fprintf(stderr, "CFB failed");
-      return 1;
+   COMPARE_TESTVECTOR(tmp, 64, pt, 64, "cfb128-enc-dec", 0);
+   cfb_done(&cfb);
+   XMEMSET(&cfb, 0, sizeof(cfb));
+#define b16(e, w) do { \
+   l = sizeof(w); \
+   DO(base16_decode(e . w, XSTRLEN(e . w), w, &l)); \
+} while(0)
+   for (n = 0; n < sizeof(cfb_testvectors)/sizeof(cfb_testvectors[0]); ++n) {
+      b16(cfb_testvectors[n], key);
+      b16(cfb_testvectors[n], iv);
+      b16(cfb_testvectors[n], pt);
+      b16(cfb_testvectors[n], ct);
+
+      DO(cfb_start_ex(cipher_idx, iv, key, 16, 0, cfb_testvectors[n].width, &cfb));
+      l = sizeof(iv2);
+      DO(cfb_getiv(iv2, &l, &cfb));
+      ENSURE(l == 16);
+      DO(ret = cfb_encrypt(pt, tmp, 2, &cfb));
+      COMPARE_TESTVECTOR(tmp, 2, ct, 2, "cfb-enc", n);
+      DO(cfb_setiv(iv2, l, &cfb));
+      DO(ret = cfb_decrypt(tmp, tmp2, 2, &cfb));
+      COMPARE_TESTVECTOR(tmp2, 2, pt, 2, "cfb-dec", n);
    }
 #endif