Version 2 of security:encrypt and security:decrypt

Updated 2002-06-07 00:34:40

Note: this code causes core dumps. I didn't realize when I wrote it that I needed to use EVP_BytesToKey(). I have a version with the fix, but the salt is a static string, and I've heard that I should randomly generate the salt and store it with the encrypted data for decryption. I'll fix this issue in a future version. I want to get it done right before I upload another version to the Wiki. If you need something that works now, then send GPS(me) an email.

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. You will need to have the OpenSSL Crypto library, which is freely available, and comes with most modern systems. Hint: Link with -lcrypto

Enjoy!


  #include <stdio.h>
  #include <stdlib.h>
  #include <openssl/evp.h>
  #include <tcl.h>

  #define CMD_ARGS (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])

  int SecurityEncryptCmd CMD_ARGS {
    #define CHECK_STATUS(msg) if (status != 1) {\
      free (encryptedData);\
      EVP_CIPHER_CTX_cleanup (&ctx);\
      Tcl_SetResult (interp, msg, TCL_STATIC);\
      return TCL_ERROR;\
    }
    unsigned char *key;
    /*store meaningless numbers in iv rather than [8], for debugging*/
    unsigned char iv[] = "12345678";
    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;

    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 0, NULL, "security:encrypt data key");
      return TCL_ERROR;
    }

    inputData = Tcl_GetByteArrayFromObj (objv[1], &dataLength);
    /*The key should be NUL terminated*/
    key = Tcl_GetString (objv[2]);

    /*for adding one block to the length */
    blockSize = EVP_CIPHER_block_size (EVP_bf_cbc ());

    encryptedData = Tcl_Alloc (dataLength + blockSize + 2);

    OpenSSL_add_all_algorithms ();

    EVP_CIPHER_CTX_init (&ctx);

    status = EVP_EncryptInit (&ctx, EVP_bf_cbc (), key, iv);
    CHECK_STATUS ("EVP_EncryptInit failed");

    status = EVP_EncryptUpdate (&ctx, encryptedData, &len, inputData, dataLength);
    CHECK_STATUS ("EVP_EncryptUpdate failed");

    totalLength += len;

    status = EVP_EncryptFinal (&ctx, encryptedData + totalLength, &len);
    CHECK_STATUS ("EVP_EncryptFinal failed");

    totalLength += len;

    EVP_CIPHER_CTX_cleanup (&ctx);

    result = Tcl_NewByteArrayObj (encryptedData, totalLength); 

    Tcl_SetObjResult (interp, result);

    Tcl_Free (encryptedData);

    #undef CHECK_STATUS
    return TCL_OK;
  }


  int SecurityDecryptCmd CMD_ARGS {
    #define CHECK_STATUS(msg) if (status != 1) {\
      free (decryptedData);\
      EVP_CIPHER_CTX_cleanup (&ctx);\
      Tcl_SetResult (interp, msg, TCL_STATIC);\
      return TCL_ERROR;\
    }
    unsigned char *key;
    unsigned char iv[] = "12345678";
    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;


    if (objc != 3) {
      Tcl_WrongNumArgs (interp, 0, NULL, "security:decrypt data key");
      return TCL_ERROR;
    }

    encryptedData = Tcl_GetByteArrayFromObj (objv[1], &encryptedLength);
    key = Tcl_GetString (objv[2]);


    blockSize = EVP_CIPHER_block_size (EVP_bf_cbc ());

    decryptedData = Tcl_Alloc (encryptedLength + blockSize + 2);

    OpenSSL_add_all_algorithms ();

    EVP_CIPHER_CTX_init (&ctx);

    status = EVP_DecryptInit (&ctx, EVP_bf_cbc (), key, iv);
    CHECK_STATUS ("EVP_DecryptInit failed");

    status = EVP_DecryptUpdate (&ctx, decryptedData, &len, encryptedData, encryptedLength);
    CHECK_STATUS ("EVP_DecryptInit failed");

    totalLength += len;

    status = EVP_DecryptFinal (&ctx, decryptedData + totalLength, &len);
    CHECK_STATUS ("EVP_DecryptFinal failed; your password is probably incorrect.");

    totalLength += len;

    EVP_CIPHER_CTX_cleanup (&ctx);

    result = Tcl_NewByteArrayObj (decryptedData, totalLength);

    Tcl_SetObjResult (interp, result);

    Tcl_Free (decryptedData);

    #undef CHECK_STATUS
    return TCL_OK;
  }


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


  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