On Thursday, October 17, 2002, at 04:30 PM, Richard Levitte - VMS Whacker wrote:
In message <[EMAIL PROTECTED]> on Thu, 17 Oct 2002 16:19:23 -0400, John Viega <[EMAIL PROTECTED]> said:I did spend some time on this tonight. I essentially designed a framework and built one test mode, OFB (I'll get to the rest of the modes tomorrow). Two files are attached (mode.h and mode.c).
viega> > viega> Also, why isn't counter mode implemented in a generic fashion?
viega> > It's
viega> > viega> so simple, it should be usable with any block cipher without
viega> > having to
viega> > viega> write additional code.
viega> >
viega> > Ask yourself why OFB, CFB, CBC and ECB modes aren't implemented
viega> > generically. A hint: speed. Someone made a test having a generic CBC
viega> > that would take a pointer to the cipher function to use. The speed
viega> > apparently decreased enough for it to be an issue.
viega>
viega> I mean have a generic implementation, not necessarily a generic
viega> interface. I was more thinking a counter mode macro that could be used
viega> to create counter mode instances for all block ciphers quickly.
That's something I've thought of just a few days ago. I'll add that
to my TODO.
Basically, where you would normally use the following code:
EVP_CIPHER_CTX ctx;
EVP_EncryptInit_ex(&ctx, EVP_bf_ofb(), 0, key, iv);
EVP_EncryptUpdate(&ctx, out, &outlen, in, inlen);
EVP_EncryptFinal(&ctx, out, &outlen); /* Shouldn't be necessary unless an engine needs to clean up */
You instead write the following, very similar (but slightly less complex) code:
MODE_OFB_CTX ctx;
MODE_OFB_Init(&ctx, BASE_bf(), 0, key, iv);
MODE_OFB_Update(&ctx, out, in, inlen); /* No need for an outlen parameter */
MODE_OFB_Final(&ctx); /* Shouldn't be necessary unless an engine needs to clean up */
You can also pre-compute keystream instead of encrypting by calling MODE_OFB_KeyStream(&ctx, out, outlen);
I do borrow EVP_EncryptInit and EVP_EncryptFinal for my implementation (just didn't feel like following the code; it can be unwound), but the actual update function should have exactly the same number of indirections as the EVP interface. So if this is significantly slower, it's probably due to my poor OFB code :-).
There are some other things that probably need to happen, like taking engines into account. If engines are done at a lower level of indirection, they *might* already work, but I doubt it (I suspect they replace the EVP_CIPHER_CTX do_encrypt pointer, which I ignore to avoid building ON TOP OF EVP).
I use macros to generate specific implementations of OFB mode for every block cipher. When you call MODE_OFB_Update, a function pointer gets dereferenced, calling the cipher-specific function. That way, if you make one call to encrypt a few gigs of data, we only dereference a function through a pointer a single time. The remainder of the time, we're calling our cipher's ECB encryption mechanism directly.
I'm going to finish this code tomorrow, adding support for CBC, CFB, CTR and CCM modes (CCM mode is CTR+CBC-MAC mode; it's in the draft 802.11i standard). If there's a desire to have it in OpenSSL, that's no problem.
John
#ifndef MODE_H__ #define MODE_H__ #include <openssl/evp.h> #include <openssl/engine.h>
#define GENERATE_MODE_FUNCS(THISMODE_NAME,THISMODE_BLOCK_LEN,THISMODE_ENCF,\ THISMODE_INIT) \ int MODE_##THISMODE_NAME##_OFB_KeyStream(EVP_CIPHER_CTX *ctx, \ unsigned char *out, int n) {\ unsigned char *lastbl, *newbl /*, finalbl[THISMODE_BLOCK_LEN]*/;\ int i, base, num = 0;\ if(ctx->num) {\ if(ctx->num > n) {\ num = n;\ } else {\ num = ctx->num;\ }\ base = THISMODE_BLOCK_LEN - ctx->num;\ for(i=0; i<num; i++) {\ out[i] = ctx->iv[base+i];\ }\ n -= num;\ if(!n) {\ ctx->num -= num;\ return 1; /* Success. */\ } else {\ ctx->num = 0;\ }\ }\ /* If we get here, we have no buffered keystream left. */\ lastbl = ctx->iv;\ newbl = out + num;\ \ /* Within the below loop, we can encrypt directly into the output. */\ for(i=0;i<n/THISMODE_BLOCK_LEN;i++) {\ THISMODE_ENCF(lastbl, newbl, ctx->cipher_data, 1);\ lastbl = newbl;\ newbl += THISMODE_BLOCK_LEN;\ }\ \ /* Go ahead and generate one more block of key stream, even if it's\ never used at all. What's one block? */\ /* lastbl can be ctx->iv, do we need to encr into a tmp buf then copy? */\ /* See above for proper error handling strategy when appropriate. */\ THISMODE_ENCF(lastbl, ctx->iv, ctx->cipher_data, 1);\ /* memcpy(ctx->iv, finalbl, THISMODE_BLOCK_LEN);*/\ num += n-(newbl-out);\ for(i=0;i<num;i++) {\ newbl[i] = ctx->iv[i];\ }\ ctx->num = THISMODE_BLOCK_LEN - num;\ return 1; /* Success. */\ }\ int MODE_##THISMODE_NAME##_OFB_Update(EVP_CIPHER_CTX *ctx, \ unsigned char *out, \ unsigned char *in, int inl) {\ int i;\ \ /* Currently, this doesn't actually fail under any circumstances. */\ MODE_##THISMODE_NAME##_OFB_KeyStream(ctx, out, inl);\ for(i=0;i<inl;i++) {\ out[i] ^= in[i];\ }\ return 1;\ }\ /* This could be internally malloced to save space when other ciphers\ go unused... but it isn't so much space. */\ static BASE_CIPHER THISMODE_NAME##_base_ob;\ BASE_CIPHER *BASE_##THISMODE_NAME() {\ THISMODE_NAME##_base_ob.handle = THISMODE_INIT();\ THISMODE_NAME##_base_ob.ofb_encr_func = MODE_##THISMODE_NAME##_OFB_Update;\ THISMODE_NAME##_base_ob.ofb_key_func = MODE_##THISMODE_NAME##_OFB_KeyStream;\ return &THISMODE_NAME##_base_ob;\ } #if 0 /* In the above macro-tized code, do this kind of thing if the encryption func can ever error. */ if(!BF_ecb_encrypt(lastbl, newbl, ctx->cipher_data, 1)) { /* Copy the last block into the IV */ memcpy(ctx->iv, lastbl, THISMODE_BLOCK_LEN); *outl = newbl-out; return 0; /* Failure. */ } /* Note also that if errors are possible, we can't copy directly into the IV like we do at the end now (in relevent modes, such as OFB). */ #endif typedef int (*OFB_ENCR_FUNC)(EVP_CIPHER_CTX *, unsigned char *, unsigned char *, int); typedef int (*OFB_KEY_FUNC)(EVP_CIPHER_CTX *, unsigned char *, int); typedef struct BASE_CIPHER_st { const EVP_CIPHER *handle; OFB_ENCR_FUNC ofb_encr_func; OFB_KEY_FUNC ofb_key_func; } BASE_CIPHER; extern BASE_CIPHER *BASE_AES_128(); extern BASE_CIPHER *BASE_AES_196(); extern BASE_CIPHER *BASE_AES_256(); extern BASE_CIPHER *BASE_BF(); extern BASE_CIPHER *BASE_CAST(); extern BASE_CIPHER *BASE_DES(); extern BASE_CIPHER *BASE_DES_EDE3(); extern BASE_CIPHER *BASE_DES_EDE(); extern BASE_CIPHER *BASE_IDEA(); extern BASE_CIPHER *BASE_RC2(); extern BASE_CIPHER *BASE_RC5(); /* EVP_rc5_32_16_12_ecb() */ typedef EVP_CIPHER_CTX MODE_ECB_CTX; #define MODE_ECB_EncryptInit(ctx, cipher, engine, key) \ EVP_EncryptInit_ex(ctx, (cipher)->handle, engine, key, 0) #define MODE_ECB_DecryptInit(ctx, cipher, engine, key) \ EVP_DecryptInit_ex(ctx, (cipher)->handle, engine, key, 0) #define MODE_ECB_EncryptUpdate(ctx, out, ol, in, inl) \ EVP_EncryptUpdate(ctx, out, ol, in, inl) #define MODE_ECB_DecryptUpdate(ctx, out, ol, in, inl) \ EVP_DecryptUpdate(ctx, out, ol, in, inl) #define MODE_ECB_EncryptFinal(ctx, out, ol) \ EVP_EncryptFinal_ex(ctx, out, ol) #define MODE_ECB_DecryptFinal(ctx, out, ol) \ EVP_DecryptFinal_ex(ctx, out, ol) typedef struct MODE_OFB_CTX_str { EVP_CIPHER_CTX e; OFB_ENCR_FUNC ef; OFB_KEY_FUNC kf; } MODE_OFB_CTX; int MODE_OFB_Init(MODE_OFB_CTX *ctx, BASE_CIPHER *cipher, ENGINE *engine, unsigned char *key, unsigned char *iv); #define MODE_OFB_Update(c,o,i,il) (*((c)->ef))(c.e,o,i,il) #define MODE_OFB_KeyStream(c,o,il) (*((c)->kf))(c.e,o,il) /* Go ahead and let OpenSSL do any cleanup it might do. * It's pretty dumb that buf needs to be declared, but it does. */ #define MODE_OFB_Final(c) {int tmp; char buf[EVP_MAX_BLOCK_LENGTH]; \ EVP_EncryptFinal(c.e, buf, &tmp); } #define MODE_OFB_EncryptInit(c,ci,en,iv) MODE_OFB_Init(c,ci,en,iv) #define MODE_OFB_DecryptInit(c,ci,en,iv) MODE_OFB_Init(c,ci,en,iv) #define MODE_OFB_EncryptUpdate(c,o,ol,i,il) MODE_OFB_Update(c,o,ol,i,il) #define MODE_OFB_DecryptUpdate(c,o,ol,i,il) MODE_OFB_Update(c,o,ol,i,il) #define MODE_OFB_EncryptFinal(c) MODE_OFB_Final(c) #define MODE_OFB_DecryptFinal(c) MODE_OFB_Final(c) #endif
#include <string.h> #include "mode.h" int MODE_OFB_Init(MODE_OFB_CTX *ctx, BASE_CIPHER *cipher, ENGINE *engine, unsigned char *key, unsigned char *iv) { int bl = EVP_CIPHER_block_size(cipher->handle); EVP_CIPHER_CTX_init(&ctx->e); if(!EVP_EncryptInit(&ctx->e, cipher->handle, key, 0)) return 0; ctx->ef = cipher->ofb_encr_func; ctx->kf = cipher->ofb_key_func; memcpy(ctx->e.oiv, iv, bl); memcpy(ctx->e.iv, iv, bl); return 1; } static void MODE_IDEA_ENC_WRAPPER(unsigned char *i, unsigned char *o, void *k, int e) { idea_ecb_encrypt(i,o,k); } struct DES_EDE_KEY { DES_key_schedule ks1; DES_key_schedule ks2; DES_key_schedule ks3; }; static void MODE_DES3_3_ENC_WRAPPER(unsigned char *in, unsigned char *out, char *k, int e) { struct DES_EDE_KEY *s = (struct DES_EDE_KEY *)k; DES_ecb3_encrypt((DES_cblock *)in, (DES_cblock *)out, &s->ks1, &s->ks2, &s->ks3, 1); } static void MODE_DES3_2_ENC_WRAPPER(unsigned char *in, unsigned char *out, char *k, int e) { struct DES_EDE_KEY *s = (struct DES_EDE_KEY *)k; DES_ecb3_encrypt((DES_cblock *)in, (DES_cblock *)out, &s->ks1, &s->ks2, &s->ks1, 1); } /* We do this, instead of using a single implementation of each with * a function pointer, because it adds an extra indirection for every block. */ GENERATE_MODE_FUNCS(aes128, 16, AES_ecb_encrypt, EVP_aes_128_ecb); GENERATE_MODE_FUNCS(aes192, 16, AES_ecb_encrypt, EVP_aes_192_ecb); GENERATE_MODE_FUNCS(aes256, 16, AES_ecb_encrypt, EVP_aes_256_ecb); GENERATE_MODE_FUNCS(bf, 8, BF_ecb_encrypt, EVP_bf_ecb); GENERATE_MODE_FUNCS(cast, 8, CAST_ecb_encrypt, EVP_cast5_ecb); GENERATE_MODE_FUNCS(des, 8, DES_ecb_encrypt, EVP_des_ecb); GENERATE_MODE_FUNCS(des3_3, 8, MODE_DES3_3_ENC_WRAPPER, EVP_des_ede3); GENERATE_MODE_FUNCS(des3_2, 8, MODE_DES3_2_ENC_WRAPPER, EVP_des_ede); GENERATE_MODE_FUNCS(idea, 8, MODE_IDEA_ENC_WRAPPER, EVP_idea_ecb); GENERATE_MODE_FUNCS(rc2, 8, RC2_ecb_encrypt, EVP_rc2_ecb); GENERATE_MODE_FUNCS(rc5, 8, RC5_32_ecb_encrypt, EVP_rc5_32_12_16_ecb);
PGP.sig
Description: PGP signature