[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;