[patch] allow ctr mode to handle 'unaligned' plaintext blocks and improve ctr benchmarks
Robert Hogan
robert at roberthogan.net
Mon Dec 29 22:07:16 CET 2008
Hi there,
I needed to use AES in CTR mode but found that libgcrypt's current
implementation does not allow for 'unaligned' blocks of plaintext, i.e.
where the plaintext is not a multiple of the context's blocksize.
I adapted an implementation found at c. line 289 of:
https://svn.torproject.org/svn/tor/trunk/src/common/aes.c
to add this functionality to libgcrypt and have supplied the patch below.
The code there is licensed under 3-clause BSD which is GPL-compatible. I
also ran it by Nick Mathewson of Tor (at #tor at irc.oftc.net) and he was
fine with it too. (Though any errors remaining in the patch are very much
my own.)
I haven't tested the change to destruction but it does pass the existing
unit tests and libgcrypt now decrypts correctly the unaligned plaintext
blocks it was previously choking on in my application.
The results from the unit tests I've added to basic.c are the output from
patched do_ctr_encrypt() on my machine so shouldn't be taken as validating
the new code in itself! I'm sure there are numerous other tests that
could/should be added to validate the patch and I'm also sure it will be
subject to careful review!
Finally, you can see the patch results in a 33% performance improvement:
Old do_ctr_encrypt():
$ ./benchmark --cipher-repetition 10 --verbose cipher AES
Running each test 10 times.
CTR
---------------
AES 370ms 360ms
Patched do_ctr_encrypt():
$ ./benchmark --cipher-repetition 10 --verbose cipher AES
Running each test 10 times.
CTR
---------------
AES 240ms 240ms
The Tor code also finds some optimization while incrementing the counter. I
will test this out later and see if the gains are appreciable.
Alternatively, you're welcome to try them out for yourself.
The patch isn't very readable so here is the new do_ctr_encrypt() function
by itself:
static void
do_ctr_encrypt( gcry_cipher_hd_t c, byte *outbuf, const byte *inbuf,
unsigned int nbytes )
{
unsigned int n;
int i;
n = c->ctrpos;
while (1) {
if (n == 0)
c->cipher->encrypt (&c->context.c, c->ctrbuf, c->ctr);
do {
if (nbytes-- == 0) { c->ctrpos = n; return; }
*(outbuf++) = *(inbuf++) ^ c->ctrbuf[n];
} while (++n != c->cipher->blocksize);
c->ctrpos = n = 0;
for (i = c->cipher->blocksize; i > 0; i--)
{
c->ctr[i-1]++;
if (c->ctr[i-1] != 0)
break;
}
if (nbytes == 0) { c->ctrpos = n; return; }
}
}
Index: tests/basic.c
===================================================================
--- tests/basic.c (revision 1375)
+++ tests/basic.c (working copy)
@@ -363,6 +363,16 @@
{ "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17\xad\x2b\x41\x7b\xe6\x6c\x37\x10",
16,
"\x1e\x03\x1d\xda\x2f\xbe\x03\xd1\x79\x21\x70\xa0\xf3\x00\x9c\xee" },
+ { "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17\xad\x2b\x41\x7b\xe6\x6c\x37\x10"
+ "\x6c\x37\x10",
+ 19,
+ "\x46\x92\x63\xbd\xcb\xc5\x0a\x19\x5d\x43\x71\xec\x76\x27\x92\x12"
+ "\x34\xae\x54"},
+ { "\xf1\x92\x24\x35\xaf\x4f\x9b\x17\xad\x2b\x41\x7b\xe6\x6c\x37\x10"
+ "\x6c\x37\x11",
+ 19,
+ "\xab\xdf\xc5\x34\x5a\x5c\x51\xc6\x35\x56\xc8\x92\xfd\x57\xee\xbc"
+ "\x15\x7e\xcf"},
}
},
{ GCRY_CIPHER_AES192,
Index: cipher/cipher.c
===================================================================
--- cipher/cipher.c (revision 1375)
+++ cipher/cipher.c (working copy)
@@ -205,7 +205,19 @@
unsigned char ctr[MAX_BLOCKSIZE]; /* For Counter (CTR) mode. */
+ /*This is the context's current position in ctrbuf. Where the number
+ of bytes encrypted are not a multiple of c->cipher_block_size this
+ stores the position of the next byte in the keystream to be xor'd
+ by do_encrypt_ctr. ctr gets incremented every time ctrpos reaches
+ c->cipher_block_size. */
+ unsigned int ctrpos; /* For Counter (CTR) mode. */
+ /* This contains the context's current keystream. ctrpos stores the
+ next position to be xor'd in ctrbuf when do_ctr_encrypt is called.
+ The contents of ctr are encrypted every time ctrpos reaches
+ zero in do_ctr_encrypt, and the result is stored in ctrbuf. */
+ byte ctrbuf[MAX_BLOCKSIZE]; /* For Counter (CTR) mode. */
+
/* What follows are two contexts of the cipher in use. The first
one needs to be aligned well enough for the cipher operation
whereas the second one is a copy created by cipher_setkey and
@@ -921,6 +933,8 @@
memset (c->u_iv.iv, 0, c->cipher->blocksize);
memset (c->lastiv, 0, c->cipher->blocksize);
memset (c->ctr, 0, c->cipher->blocksize);
+ memset (c->ctrbuf, 0, c->cipher->blocksize);
+ c->ctrpos=0;
}
@@ -1355,32 +1369,31 @@
}
}
-
static void
do_ctr_encrypt( gcry_cipher_hd_t c, byte *outbuf, const byte *inbuf,
unsigned int nbytes )
{
unsigned int n;
- byte tmp[MAX_BLOCKSIZE];
int i;
+ n = c->ctrpos;
- for(n=0; n < nbytes; n++)
- {
- if ((n % c->cipher->blocksize) == 0)
- {
- c->cipher->encrypt (&c->context.c, tmp, c->ctr);
+ while (1) {
+ if (n == 0)
+ c->cipher->encrypt (&c->context.c, c->ctrbuf, c->ctr);
+ do {
+ if (nbytes-- == 0) { c->ctrpos = n; return; }
+ *(outbuf++) = *(inbuf++) ^ c->ctrbuf[n];
+ } while (++n != c->cipher->blocksize);
+ c->ctrpos = n = 0;
+ for (i = c->cipher->blocksize; i > 0; i--)
+ {
+ c->ctr[i-1]++;
+ if (c->ctr[i-1] != 0)
+ break;
+ }
+ if (nbytes == 0) { c->ctrpos = n; return; }
+ }
- for (i = c->cipher->blocksize; i > 0; i--)
- {
- c->ctr[i-1]++;
- if (c->ctr[i-1] != 0)
- break;
- }
- }
-
- /* XOR input with encrypted counter and store in output. */
- outbuf[n] = inbuf[n] ^ tmp[n % c->cipher->blocksize];
- }
}
static void
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part.
URL: </pipermail/attachments/20081229/2ff8bc39/attachment.pgp>
More information about the Gcrypt-devel
mailing list