Version 3 of security:encrypt and security:decrypt

Updated 2002-06-14 03:55:40

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]

Category Cryptography