security:encrypt and security:decrypt

GPS - Thu May 9, 2002: I needed the ability to encrypt and decrypt a password database for my file server. I came up with this little extension to do so using Blowfish. You will need to have the OpenSSL Crypto library, which is freely available, and comes with most modern systems. Hint: Link with -lcrypto


  /*Updated Thurs Jun 13 to fix a bug that caused seg faults with some keys*/
  /*I've personally tested this with large and small files and it works well.*/
  /*No warranty expressed or implied*/
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <openssl/evp.h>
  #include <tcl.h>
  
  #define OBJ_CMD_ARGS (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
  
  int SecurityEncryptCmd OBJ_CMD_ARGS {
    unsigned char key[EVP_MAX_KEY_LENGTH];
    unsigned char iv[EVP_MAX_IV_LENGTH];
    unsigned char *inputData;
    unsigned char *encryptedData;
    EVP_CIPHER_CTX ctx;
    int totalLength = 0;
    int status = 0;
    int len = 0;
    int dataLength = 0;
    int blockSize = 0;
    Tcl_Obj *result;
    char *password;
    int headerOffset = 0;
    int headerSize = 0;
    char salt[40];
    int i;
    int nSalt = 0;
    int nRand = 0;
    int nTmpSalt = 0;
    int nSplit = 127;
  
    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 0, objv, "security:encrypt data password");
      return TCL_ERROR;
    }
  
    inputData = Tcl_GetByteArrayFromObj (objv[1], &dataLength);
    /*The key should be NUL terminated*/
    password = Tcl_GetString (objv[2]);
    
    /*for adding one block to the length */
    blockSize = EVP_CIPHER_block_size (EVP_bf_cbc ());
    
    while ((nRand = rand ()) <= 0);
  
    /*This should be sufficiently random*/
    nSalt = getpid () + nRand;
    nSalt = nSalt & 0xffff;
    
  
    /*+ 1 for the last remainder, and + 1 for the final 0 sentinel*/
    headerSize = ((nSalt / nSplit) + 2);
  
    encryptedData = ckalloc (dataLength + blockSize + headerSize + 1);
  
    nTmpSalt = nSalt;
  
    i = 0;
    while (nTmpSalt > nSplit) {
      encryptedData[i] = nSplit;
      nTmpSalt -= nSplit;
      ++i;
    }
  
    if (nTmpSalt == 0) {
      /*done with the header*/
      encryptedData[i] = 0;
    } else {
      encryptedData[i] = nTmpSalt;
      ++i;
      encryptedData[i] = 0;
    }
  
    ++i;
    headerOffset = i;
  
    sprintf (salt, "%d", nSalt);
    
    /*fprintf (stderr, "comp nSalt %d\n", nSalt);*/
  
    OpenSSL_add_all_algorithms ();
  
    EVP_CIPHER_CTX_init (&ctx);
  
    EVP_BytesToKey (EVP_bf_cbc (), EVP_md5 (), salt, password, strlen (password), 1, key, iv);
  
    status = EVP_EncryptInit (&ctx, EVP_bf_cbc (), key, iv);
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_EncryptInit failed", TCL_STATIC);
      goto error;
    }
  
    status = EVP_EncryptUpdate (&ctx, &encryptedData[headerOffset], &len, inputData, dataLength);
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_EncryptUpdate failed", TCL_STATIC);
      goto error;
    }
    
    totalLength += len;
    
    status = EVP_EncryptFinal (&ctx, &encryptedData[headerOffset + totalLength], &len);
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_EncryptFinal failed", TCL_STATIC);
      goto error;
    }
    
    totalLength += len;
  
    EVP_CIPHER_CTX_cleanup (&ctx);
    
    result = Tcl_NewByteArrayObj (encryptedData, (totalLength + headerOffset)); 
  
    Tcl_SetObjResult (interp, result);
    
    ckfree (encryptedData);
  
    return TCL_OK;
  
    error:
    EVP_CIPHER_CTX_cleanup (&ctx);
    ckfree (encryptedData);
  
    return TCL_ERROR;
  }
  

  int SecurityDecryptCmd OBJ_CMD_ARGS {
    unsigned char key[EVP_MAX_KEY_LENGTH];
    unsigned char iv[EVP_MAX_IV_LENGTH];
    unsigned char *encryptedData;
    unsigned char *decryptedData;
    EVP_CIPHER_CTX ctx;
    int totalLength = 0;
    int status = 0;
    int len = 0;
    int encryptedLength = 0;
    int blockSize = 0;
    Tcl_Obj *result;
    char *password;
    char salt[40];
    int headerOffset = 0;
    int i = 0;
    int nSalt = 0;
  
    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 0, objv, "security:decrypt data key");
      return TCL_ERROR;
    }
  
    encryptedData = Tcl_GetByteArrayFromObj (objv[1], &encryptedLength);
    password = Tcl_GetString (objv[2]);
  
    blockSize = EVP_CIPHER_block_size (EVP_bf_cbc ());
  
    for (i = 0; i < encryptedLength; ++i) {
      int value = encryptedData[i];
  
      if (value == 0) {
        break;
      } else {
        nSalt += value;
      }
    }
  
    if (nSalt <= 0 || i == encryptedLength) {
      Tcl_SetResult (interp, "bad input", TCL_STATIC);
      return TCL_ERROR;
    }
  
    ++i;
    headerOffset = i;
  
    sprintf (salt, "%d", nSalt);
  
    decryptedData = ckalloc (encryptedLength + blockSize + 2);
  
    OpenSSL_add_all_algorithms ();
  
    EVP_CIPHER_CTX_init (&ctx);
  
    EVP_BytesToKey (EVP_bf_cbc (), EVP_md5 (), salt, password, strlen (password), 1, key, iv);
  
    status = EVP_DecryptInit (&ctx, EVP_bf_cbc (), key, iv);
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_DecryptInit failed", TCL_STATIC);
      goto error;
    }
  
    status = EVP_DecryptUpdate (&ctx, decryptedData, &len, &encryptedData[headerOffset], (encryptedLength - headerOffset));
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_DecryptInit failed", TCL_STATIC);
      goto error;
    }
  
    totalLength += len;
  
    status = EVP_DecryptFinal (&ctx, decryptedData + totalLength, &len);
    if (status != 1) {
      Tcl_SetResult (interp, "EVP_DecryptFinal failed; your password is probably incorrect.", TCL_STATIC);
      goto error;
    }
  
    totalLength += len;
  
    EVP_CIPHER_CTX_cleanup (&ctx);
  
    result = Tcl_NewByteArrayObj (decryptedData, totalLength);
  
    Tcl_SetObjResult (interp, result);
  
    ckfree (decryptedData);
  
    return TCL_OK;
  
    error:
    EVP_CIPHER_CTX_cleanup (&ctx);
    ckfree (decryptedData);
  
    return TCL_ERROR;
  }
  
  
  int Security_Init (Tcl_Interp *interp) {
    #define OBJ_CMD(name,func) Tcl_CreateObjCommand(interp, name, func, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL)
  
    OBJ_CMD ("security:encrypt", SecurityEncryptCmd);
    OBJ_CMD ("security:decrypt", SecurityDecryptCmd);
    
    #undef OBJ_CMD
  
    return TCL_OK;
  }
  
  #undef OBJ_CMD_ARGS

  int main (int argc, char *argv[]) {
  
    Tcl_Interp *interp;
  
    Tcl_FindExecutable (argv[0]);

    interp = Tcl_CreateInterp ();
  
    if (Tcl_Init (interp) != TCL_OK) {
      fprintf (stderr, "Tcl_Init error %s", Tcl_GetStringResult (interp));
      exit (1);
    }
  
    if (Security_Init (interp) != TCL_OK) {
      fprintf (stderr, "Security_Init error %s\n", Tcl_GetStringResult (interp));
      exit (1);
    }
  
    if (Tcl_EvalFile (interp, "./encrypt_test.tcl") != TCL_OK) {
      fprintf (stderr, "%s", Tcl_GetStringResult (interp));
      exit (1);
    }
  
    return 0;
  }

  #Test code
  set str "Hello World"
  set key abc

  set encData [security:encrypt $str $key]

  puts [security:decrypt $encData $key]

MDD: How about Windows and Linux binaries? :-)

GPS: I'm not currently in a position to provide either. I've tried MingW in XP but I experienced problems that I couldn't solve. I don't have Linux (only OpenBSD 3.0), but sometime in the future I may be able to do cross-compiling for Linux. I'm willing to host any binary extensions compiled by others of this code. It isn't that difficult to make into an extension (maybe slightly more complicated in Windows for a DLL). In my own code I don't have the main in the example above. My friend SO might be able to provide a Windows DLL, because he has VC++, but I'll have to ask him for help with it.

There's a Blowfish extension in Critcl, see [L1 ], maybe that can be of use. -jcw