[PATCH] CTS support for CBC

Simon Josefsson jas@extundo.com
Sun, 10 Nov 2002 11:55:54 +0100


This patch adds Cipher Text Stealing (CTS) treatment for CBC mode, and
some self tests on AES using third party test vectors.

cipher/ChangeLog:

2002-11-10  Simon Josefsson  <jas@extundo.com>

	* cipher.c (gcry_cipher_open): Don't reject CTS flag.
	(do_cbc_encrypt, do_cbc_decrypt, cipher_encrypt) 
	(gcry_cipher_encrypt, cipher_decrypt)
	(gcry_cipher_decrypt): Support CTS flag.
	(gcry_cipher_ctl): Toggle CTS flag.

src/ChangeLog:

2002-11-10  Simon Josefsson  <jas@extundo.com>

	* gcrypt.h (gcry_ctl_cmds): New GCRYCTL_SET_CBC_CTS control flag.
	(gcry_cipher_flags): New GCRY_CIPHER_CBC_CTS gcry_cipher_open() flag.
	(gcry_cipher_cts): New macro for toggling CTS.

tests/ChangeLog:

2002-11-10  Simon Josefsson  <jas@extundo.com>

	* basic.c (check_aes128_cbc_cts_cipher): New function.
	(check_one_cipher): Add flags parameter.
	(check_ciphers): Support flags parameter.
	(main): Check CTS.

Index: cipher/cipher.c
===================================================================
RCS file: /home/cvs/shishi/crypto/cipher/cipher.c,v
retrieving revision 1.1
diff -u -p -r1.1 cipher.c
--- cipher/cipher.c	30 Oct 2002 00:17:59 -0000	1.1
+++ cipher/cipher.c	10 Nov 2002 10:46:20 -0000
@@ -513,7 +513,9 @@ gcry_cipher_open( int algo, int mode, un
     }
 
     /* check flags */
-    if( (flags & ~(GCRY_CIPHER_SECURE|GCRY_CIPHER_ENABLE_SYNC)) ) {
+    if( (flags & ~(GCRY_CIPHER_SECURE|
+		   GCRY_CIPHER_ENABLE_SYNC|
+		   GCRY_CIPHER_CBC_CTS)) ) {
 	set_lasterr( GCRYERR_INV_CIPHER_ALGO );
 	return NULL;
     }
@@ -642,12 +644,18 @@ do_ecb_decrypt( GCRY_CIPHER_HD c, byte *
 }
 
 static void
-do_cbc_encrypt( GCRY_CIPHER_HD c, byte *outbuf, const byte *inbuf, unsigned nblocks )
+do_cbc_encrypt( GCRY_CIPHER_HD c, byte *outbuf, const byte *inbuf, unsigned nbytes )
 {
     unsigned int n;
     byte *ivp;
     int i;
     size_t blocksize = c->blocksize;
+    unsigned nblocks = nbytes / blocksize;
+
+    if (c->flags & GCRY_CIPHER_CBC_CTS && nbytes > blocksize) {
+      if ((nbytes % blocksize) == 0)
+	nblocks--;
+    }
 
     for(n=0; n < nblocks; n++ ) {
 	/* fixme: the xor should works on words and not on
@@ -660,15 +668,44 @@ do_cbc_encrypt( GCRY_CIPHER_HD c, byte *
 	inbuf  += c->blocksize;
 	outbuf += c->blocksize;
     }
+
+    if (c->flags & GCRY_CIPHER_CBC_CTS && nbytes > blocksize)
+      {
+	int restbytes;
+
+	if ((nbytes % blocksize) == 0)
+	  restbytes = blocksize;
+	else
+	  restbytes = nbytes % blocksize;
+
+	memcpy(outbuf, outbuf - c->blocksize, restbytes);
+	outbuf -= c->blocksize;
+
+	for(ivp=c->iv,i=0; i < restbytes; i++ )
+	    outbuf[i] = inbuf[i] ^ *ivp++;
+	for(; i < blocksize; i++ )
+	    outbuf[i] = 0 ^ *ivp++;
+
+	(*c->encrypt)( &c->context.c, outbuf, outbuf );
+	memcpy(c->iv, outbuf, blocksize );
+      }
 }
 
 static void
-do_cbc_decrypt( GCRY_CIPHER_HD c, byte *outbuf, const byte *inbuf, unsigned nblocks )
+do_cbc_decrypt( GCRY_CIPHER_HD c, byte *outbuf, const byte *inbuf, unsigned nbytes )
 {
     unsigned int n;
     byte *ivp;
     int i;
     size_t blocksize = c->blocksize;
+    unsigned nblocks = nbytes / blocksize;
+
+    if (c->flags & GCRY_CIPHER_CBC_CTS && nbytes > blocksize) {
+      nblocks--;
+      if ((nbytes % blocksize) == 0)
+	nblocks--;
+      memcpy(c->lastiv, c->iv, blocksize );
+    }
 
     for(n=0; n < nblocks; n++ ) {
 	/* because outbuf and inbuf might be the same, we have
@@ -682,6 +719,30 @@ do_cbc_decrypt( GCRY_CIPHER_HD c, byte *
 	inbuf  += c->blocksize;
 	outbuf += c->blocksize;
     }
+
+    if (c->flags & GCRY_CIPHER_CBC_CTS && nbytes > blocksize) {
+	int restbytes;
+
+	if ((nbytes % blocksize) == 0)
+	  restbytes = blocksize;
+	else
+	  restbytes = nbytes % blocksize;
+
+	memcpy(c->lastiv, c->iv, blocksize ); /* save Cn-2 */
+	memcpy(c->iv, inbuf + blocksize, restbytes ); /* save Cn */
+
+	(*c->decrypt)( &c->context.c, outbuf, (char*)/*argggg*/inbuf );
+	for(ivp=c->iv,i=0; i < restbytes; i++ )
+	    outbuf[i] ^= *ivp++;
+
+	memcpy(outbuf + blocksize, outbuf, restbytes);
+	for(i=restbytes; i < blocksize; i++)
+	  c->iv[i] = outbuf[i];
+	(*c->decrypt)( &c->context.c, outbuf, c->iv );
+	for(ivp=c->lastiv,i=0; i < blocksize; i++ )
+	    outbuf[i] ^= *ivp++;
+	/* c->lastiv is now really lastlastiv, does this matter? */
+    }
 }
 
 
@@ -810,8 +871,9 @@ cipher_encrypt( GCRY_CIPHER_HD c, byte *
             rc = GCRYERR_INV_ARG;
 	break;
       case GCRY_CIPHER_MODE_CBC:
-	if (!(nbytes%c->blocksize))
-            do_cbc_encrypt(c, outbuf, inbuf, nbytes/c->blocksize );
+	if (!(nbytes%c->blocksize) || (nbytes > c->blocksize && 
+				       (c->flags & GCRY_CIPHER_CBC_CTS)))
+            do_cbc_encrypt(c, outbuf, inbuf, nbytes );
         else 
             rc = GCRYERR_INV_ARG;
 	break;
@@ -855,10 +917,12 @@ gcry_cipher_encrypt( GCRY_CIPHER_HD h, b
 	if ( outsize < inlen )
 	    return set_lasterr ( GCRYERR_TOO_SHORT );
         if ( ( h->mode == GCRY_CIPHER_MODE_ECB ||
-               h->mode == GCRY_CIPHER_MODE_CBC ) &&
-             (inlen % h->blocksize) != 0 )
-            return set_lasterr( GCRYERR_INV_ARG );
-        
+               (h->mode == GCRY_CIPHER_MODE_CBC && 
+		!((h->flags & GCRY_CIPHER_CBC_CTS) &&
+		  (inlen > h->blocksize)))) &&
+	     (inlen % h->blocksize) != 0 )
+	  return set_lasterr( GCRYERR_INV_ARG );
+
 	rc = cipher_encrypt ( h, out, in, inlen );
     }
 
@@ -886,8 +950,9 @@ cipher_decrypt( GCRY_CIPHER_HD c, byte *
             rc = GCRYERR_INV_ARG;
 	break;
       case GCRY_CIPHER_MODE_CBC:
-	if (!(nbytes%c->blocksize))
-            do_cbc_decrypt(c, outbuf, inbuf, nbytes/c->blocksize );
+	if (!(nbytes%c->blocksize) || (nbytes > c->blocksize && 
+				       (c->flags & GCRY_CIPHER_CBC_CTS)))
+            do_cbc_decrypt(c, outbuf, inbuf, nbytes );
         else 
             rc = GCRYERR_INV_ARG;
 	break;
@@ -927,8 +992,10 @@ gcry_cipher_decrypt( GCRY_CIPHER_HD h, b
 	if( outsize < inlen )
 	    return set_lasterr( GCRYERR_TOO_SHORT );
         if ( ( h->mode == GCRY_CIPHER_MODE_ECB ||
-               h->mode == GCRY_CIPHER_MODE_CBC )
-             && ( inlen % h->blocksize ) != 0 )
+               (h->mode == GCRY_CIPHER_MODE_CBC && 
+		!((h->flags & GCRY_CIPHER_CBC_CTS) &&
+		  (inlen > h->blocksize)))) &&
+	     (inlen % h->blocksize) != 0 )
             return set_lasterr( GCRYERR_INV_ARG );
 
 	rc = cipher_decrypt( h, out, in, inlen );
@@ -968,6 +1035,12 @@ gcry_cipher_ctl( GCRY_CIPHER_HD h, int c
       break;
     case GCRYCTL_CFB_SYNC:
       cipher_sync( h );
+      break;
+    case GCRYCTL_SET_CBC_CTS:
+      if (buflen)
+	h->flags |= GCRY_CIPHER_CBC_CTS;
+      else
+	h->flags &= ~GCRY_CIPHER_CBC_CTS;
       break;
 
     case GCRYCTL_DISABLE_ALGO:
Index: src/gcrypt.h
===================================================================
RCS file: /home/cvs/shishi/crypto/src/gcrypt.h,v
retrieving revision 1.1
diff -u -p -r1.1 gcrypt.h
--- src/gcrypt.h	30 Oct 2002 00:18:01 -0000	1.1
+++ src/gcrypt.h	10 Nov 2002 10:46:20 -0000
@@ -157,7 +157,8 @@ enum gcry_ctl_cmds 
     GCRYCTL_DISABLE_SECMEM      = 37,
     GCRYCTL_INITIALIZATION_FINISHED = 38,
     GCRYCTL_INITIALIZATION_FINISHED_P = 39,
-    GCRYCTL_ANY_INITIALIZATION_P = 40
+    GCRYCTL_ANY_INITIALIZATION_P = 40,
+    GCRYCTL_SET_CBC_CTS = 41
   };
 
 /* Perform various operations defined by CMD. */
@@ -507,7 +508,8 @@ enum gcry_cipher_modes 
 enum gcry_cipher_flags
   {
     GCRY_CIPHER_SECURE	    = 1,  /* Allocate in secure memory. */
-    GCRY_CIPHER_ENABLE_SYNC = 2   /* Enable CFB sync mode. */
+    GCRY_CIPHER_ENABLE_SYNC = 2,  /* Enable CFB sync mode. */
+    GCRY_CIPHER_CBC_CTS = 4       /* Enable CBC cipher text stealing (CTS). */
   };
 
 
@@ -568,6 +570,10 @@ int gcry_cipher_decrypt (GcryCipherHd h,
    cipher handle H. */
 #define gcry_cipher_sync(h)  gcry_cipher_ctl( (h), GCRYCTL_CFB_SYNC, \
 								   NULL, 0 )
+
+/* Enable or disable CTS in future calls to gcry_encrypt(). CBC mode only. */
+#define gcry_cipher_cts(h,on)  gcry_cipher_ctl( (h), GCRYCTL_SET_CBC_CTS, \
+								   NULL, on )
 
 /* Retrieved the key length used with algorithm A. */
 #define gcry_cipher_get_algo_keylen(a) \
Index: tests/basic.c
===================================================================
RCS file: /home/cvs/shishi/crypto/tests/basic.c,v
retrieving revision 1.1
diff -u -p -r1.1 basic.c
--- tests/basic.c	30 Oct 2002 00:18:02 -0000	1.1
+++ tests/basic.c	10 Nov 2002 10:46:20 -0000
@@ -49,9 +49,94 @@ die ( const char *format, ... )
     exit (1);
 }
 
+#define MAX_DATA_LEN 100
 
 static void
-check_one_cipher (int algo, int mode)
+check_aes128_cbc_cts_cipher ()
+{
+  char key[128/8] = "chicken teriyaki";
+  char plaintext[] = "I would like the General Gau's Chicken, please, and wonton soup.";
+  struct tv {
+    char out[MAX_DATA_LEN];
+    int inlen;
+  } tv[] = {
+    {  "\xc6\x35\x35\x68\xf2\xbf\x8c\xb4\xd8\xa5\x80\x36\x2d\xa7\xff\x7f"
+      "\x97", 17 },
+    { "\xfc\x00\x78\x3e\x0e\xfd\xb2\xc1\xd4\x45\xd4\xc8\xef\xf7\xed\x22"
+      "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5", 31 },
+    { "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8"
+      "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84", 32 },
+    { "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84"
+      "\xb3\xff\xfd\x94\x0c\x16\xa1\x8c\x1b\x55\x49\xd2\xf8\x38\x02\x9e"
+      "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5", 47 },
+    { "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84"
+      "\x9d\xad\x8b\xbb\x96\xc4\xcd\xc0\x3b\xc1\x03\xe1\xa1\x94\xbb\xd8"
+      "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8", 48 },
+    { "\x97\x68\x72\x68\xd6\xec\xcc\xc0\xc0\x7b\x25\xe2\x5e\xcf\xe5\x84"
+      "\x39\x31\x25\x23\xa7\x86\x62\xd5\xbe\x7f\xcb\xcc\x98\xeb\xf5\xa8"
+      "\x48\x07\xef\xe8\x36\xee\x89\xa5\x26\x73\x0d\xbc\x2f\x7b\xc8\x40"
+      "\x9d\xad\x8b\xbb\x96\xc4\xcd\xc0\x3b\xc1\x03\xe1\xa1\x94\xbb\xd8", 64 }
+  };
+  GCRY_CIPHER_HD hd;
+  char out[MAX_DATA_LEN];
+  int i;
+
+  hd = gcry_cipher_open (GCRY_CIPHER_AES, 
+			 GCRY_CIPHER_MODE_CBC, 
+			 GCRY_CIPHER_CBC_CTS);
+  if (!hd) {
+    fail ("aes-cbc-cts, grcy_open_cipher failed: %s\n", gcry_strerror (-1) );
+    return;
+  }
+
+  if (gcry_cipher_setkey (hd, key, 128/8)) { 
+    fail ("aes-cbc-cts, gcry_cipher_setkey failed: %s\n", gcry_strerror (-1) );
+    gcry_cipher_close (hd);
+    return;
+  }
+
+  for (i = 0; i < sizeof(tv) / sizeof(tv[0]); i++)
+    {
+      if (gcry_cipher_setiv (hd, NULL, 0)) { 
+        fail ("aes-cbc-cts, gcry_cipher_setiv failed: %s\n",
+	      gcry_strerror (-1) );
+        gcry_cipher_close (hd);
+        return;
+      }
+
+      if ( gcry_cipher_encrypt (hd, out, MAX_DATA_LEN, 
+				plaintext, tv[i].inlen)) { 
+	fail ("aes-cbc-cts, gcry_cipher_encrypt failed: %s\n",
+	      gcry_strerror (-1) );
+	gcry_cipher_close (hd);
+	return;
+      }
+
+      if ( memcmp (tv[i].out, out, tv[i].inlen) )
+        fail ("aes-cbc-cts, encrypt mismatch entry %d\n", i);
+
+      if (gcry_cipher_setiv (hd, NULL, 0)) { 
+        fail ("aes-cbc-cts, gcry_cipher_setiv failed: %s\n",
+	      gcry_strerror (-1) );
+        gcry_cipher_close (hd);
+        return;
+      }
+      if ( gcry_cipher_decrypt (hd, out, tv[i].inlen, NULL, 0)) { 
+	fail ("aes-cbc-cts, gcry_cipher_decrypt failed: %s\n",
+	      gcry_strerror (-1) );
+	gcry_cipher_close (hd);
+	return;
+      }
+
+      if ( memcmp (plaintext, out, tv[i].inlen) )
+        fail ("aes-cbc-cts, decrypt mismatch entry %d\n", i);
+    }
+
+  gcry_cipher_close (hd);
+}
+
+static void
+check_one_cipher (int algo, int mode, int flags)
 {
     GCRY_CIPHER_HD hd;
     char key[32], plain[16], in[16], out[16];
@@ -67,7 +152,7 @@ check_one_cipher (int algo, int mode)
         return;
     }
 
-    hd = gcry_cipher_open (algo, mode, 0);
+    hd = gcry_cipher_open (algo, mode, flags);
     if (!hd) {
         fail ("algo %d, mode %d, grcy_open_cipher failed: %s\n",
               algo, mode, gcry_strerror (-1) );
@@ -90,7 +175,7 @@ check_one_cipher (int algo, int mode)
     }
 
     gcry_cipher_close (hd);
-    hd = gcry_cipher_open (algo, mode, 0);
+    hd = gcry_cipher_open (algo, mode, flags);
     if (!hd) {
         fail ("algo %d, mode %d, grcy_open_cipher failed: %s\n",
               algo, mode, gcry_strerror (-1) );
@@ -143,9 +228,10 @@ check_ciphers (void)
       if (verbose)
         fprintf (stderr, "checking `%s'\n", gcry_cipher_algo_name (algos[i]));
                  
-      check_one_cipher (algos[i], GCRY_CIPHER_MODE_ECB);
-      check_one_cipher (algos[i], GCRY_CIPHER_MODE_CFB);
-      check_one_cipher (algos[i], GCRY_CIPHER_MODE_CBC);
+      check_one_cipher (algos[i], GCRY_CIPHER_MODE_ECB, 0);
+      check_one_cipher (algos[i], GCRY_CIPHER_MODE_CFB, 0);
+      check_one_cipher (algos[i], GCRY_CIPHER_MODE_CBC, 0);
+      check_one_cipher (algos[i], GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_CBC_CTS);
     }
 
   for (i=0; algos2[i]; i++ ) 
@@ -153,7 +239,7 @@ check_ciphers (void)
       if (verbose)
         fprintf (stderr, "checking `%s'\n", gcry_cipher_algo_name (algos2[i]));
                  
-      check_one_cipher (algos2[i], GCRY_CIPHER_MODE_STREAM);
+      check_one_cipher (algos2[i], GCRY_CIPHER_MODE_STREAM, 0);
     }
   /* we have now run all cipher's selftests */
 
@@ -178,6 +264,7 @@ main (int argc, char **argv)
   if (!gcry_check_version (GCRYPT_VERSION))
     die ("version mismatch\n");
   check_ciphers ();
+  check_aes128_cbc_cts_cipher ();
   check_digests ();
   
   return error_count? 1:0;