Index: configure
==================================================================
--- configure
+++ configure
@@ -5394,11 +5394,11 @@
 # This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS
 # and PKG_TCL_SOURCES.
 #-----------------------------------------------------------------------
 
 
-    vars="tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsX509.c"
+    vars="tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsKey.c tlsX509.c"
     for i in $vars; do
 	case $i in
 	    \$*)
 		# allow $-var names
 		PKG_SOURCES="$PKG_SOURCES $i"

Index: configure.ac
==================================================================
--- configure.ac
+++ configure.ac
@@ -69,11 +69,11 @@
 # and runtime Tcl library files in TEA_ADD_TCL_SOURCES.
 # This defines PKG(_STUB)_SOURCES, PKG(_STUB)_OBJECTS, PKG_HEADERS
 # and PKG_TCL_SOURCES.
 #-----------------------------------------------------------------------
 
-TEA_ADD_SOURCES([tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsX509.c])
+TEA_ADD_SOURCES([tls.c tlsBIO.c tlsDigest.c tlsEncrypt.c tlsInfo.c tlsIO.c tlsKey.c tlsX509.c])
 TEA_ADD_HEADERS([generic/tls.h])
 TEA_ADD_INCLUDES([])
 TEA_ADD_LIBS([])
 TEA_ADD_CFLAGS([])
 TEA_ADD_STUB_SOURCES([])

Index: doc/cryptography.html
==================================================================
--- doc/cryptography.html
+++ doc/cryptography.html
@@ -38,10 +38,12 @@
 	    <dd><b>tls::sha512</b> <em>data</em></dd>
 	    <dd><b>tls::unstack</b> <em>channelId</em></dd>
 	    <dt>&nbsp;</dt>
 	    <dd><b>tls::encrypt</b> <b>-cipher</b> <em>name</em> <b>-key</b> <em>key ?options?</em></dd>
 	    <dd><b>tls::decrypt</b> <b>-cipher</b> <em>name</em> <b>-key</b> <em>key ?options?</em></dd>
+	    <dt>&nbsp;</dt>
+	    <dd><b>tls::derive_key</b> <em>key ?options?</em></dd>
 	</dl>
     </dd>
     <dd><a href="#OPTIONS">OPTIONS</a></dd>
     <dd><a href="#COMMANDS">COMMANDS</a></dd>
     <dd><a href="#GLOSSARY">GLOSSARY</a> </dd>
@@ -85,10 +87,12 @@
 <a href="#tls::sha512"><b>tls::sha512</b> <i>data</i></a><br>
 <a href="#tls::unstack"><b>tls::unstack</b> <i>channelId</i></a><br>
 <br>
 <a href="#tls::encrypt"><b>tls::encrypt</b> <b>-cipher</b> <i>name</i> <b>-key</b> <i>key ?options?</i></a><br>
 <a href="#tls::decrypt"><b>tls::decrypt</b> <b>-cipher</b> <i>name</i> <b>-key</b> <i>key ?options?</i></a><br>
+<br>
+<a href="#tls::derive_key"><b>tls::derive_key</b> <i>?options?</i></a><br>
 </p>
 
 <br>
 <h3><a name="OPTIONS">OPTIONS</a></h3>
 
@@ -111,31 +115,31 @@
     See <a href="#tls::digests"><b>tls::digests</b></a> for the valid values.</dd>
 </dl>
 
 <dl>
     <dt><a name="-iterations"><strong>-iterations</strong> <em>count</em></a></dt>
-    <dd>Number (integer) of iterations on the password to use in deriving the
-    encryption key. Default is 10000. Some KDF implementations require an
-    iteration count.</dd>
+    <dd>Number (integer &gt; 0) of iterations to use in deriving the encryption
+    key. Default is 2048. Some <a href="#KDF"><b>KDF</b></a> implementations
+    require an iteration count.</dd>
 </dl>
 
 <dl>
     <dt><a name="-iv"><strong>-iv</strong> <em>string</em></a></dt>
     <dd>Initialization vector (IV) to use. Required for some ciphers and GMAC.
-    Cipher modes CBC, CFB, OFB and CTR all need an IV while ECB mode does not.
+    Cipher modes CBC, CFB, and OFB all need an IV while ECB and CTR modes do not.
     A new, random IV should be created for each use. Think of the IV as a nonce
     (number used once), it's public but random and unpredictable. See the
    <a href="#tls::cipher"><b>tls::cipher</b></a> for iv_length and
-    when required (length > 0). If not set, it will default to \x00 fill data.</dd>
+    when required (length > 0). Max is 16 bytes. If not set, it will default to \x00 fill data.</dd>
 </dl>
 
 <dl>
     <dt><a name="-key"><strong>-key</strong> <em>string</em></a></dt>
     <dd>Encryption key to use for cryptography function. Can be a binary or
     text string. Longer keys provide better protection. Used by ciphers, HMAC,
     some CMAC, and some KDF implementations. If the length of the key is &lt;
-    <b>key_length</b> it will be padded. If &gt; key_length, it will be rejected.
+    <b>key_length</b> it will be padded. Max is 64 bytes. If &gt; key_length, it will be rejected.
     See the <a href="#tls::cipher"><b>tls::cipher</b></a> for key_length.</dd>
 </dl>
 
 <dl>
     <dt><a name="-mac"><strong>-mac</strong> <em>name</em></a></dt>
@@ -143,23 +147,25 @@
     See <a href="#tls::mac"><b>tls::macs</b></a> for the valid values.</dd>
 </dl>
 
 <dl>
     <dt><a name="-password"><strong>-password</strong> <em>string</em></a></dt>
-    <dd>Password to use for some KDF functions.</dd>
+    <dd>Password to use for some KDF functions. If not specified, the default
+    value is used. Can be a binary or text string.</dd>
 </dl>
 
 <dl>
     <dt><a name="-properties"><strong>-properties</strong> <em>list</em></a></dt>
-    <dd>List of additional properties to pass to cryptography function.</dd>
+    <dd>List of additional properties to pass to cryptographic function.</dd>
 </dl>
 
 <dl>
     <dt><a name="-salt"><strong>-salt</strong> <em>string</em></a></dt>
-    <dd>Specifies salt value to use when encrypting data. Default is to use a
-    randomly generated value. This option is used by BLAKE2 MAC and some KDF
-    implementations use a non-secret unique cryptographic salt.</dd>
+    <dd>Specifies salt value to use when encrypting data. Can be a binary or
+    text string. Default is to use a randomly generated value. This option is
+    used by BLAKE2 MAC and some KDF implementations use a non-secret unique
+    cryptographic salt.</dd>
 </dl>
 
 <dl>
     <dt><a name="-size"><strong>-size</strong> <em>number</em></a></dt>
     <dd>Set the output hash size in bytes. Used by KMAC128 or KMAC256 to specify
@@ -282,11 +288,11 @@
 
 <p>The following commands provide access to the OpenSSL cryptography functions.</p>
 
 <dl>
 
-<h4>Info Commands</h4>
+<h4><a name="Info">Info Commands</a></h4>
 
     <dt><a name="tls::cipher"><strong>tls::cipher</strong> <em>name</em></a></dt>
     <dd>Return a list of property names and values describing cipher
 	<i>name</i>. Properties include name, description, block_size,
 	key_length, iv_length, type, and mode list. If block-size is 1,
@@ -327,11 +333,11 @@
     <dt><a name="tls::version"><strong>tls::version</strong></a></dt>
     <dd>Returns the OpenSSL version string.</dd>
 
 <br>
 
-<h4>Message Digest (MD) and Message Authentication Code (MAC) Commands</h4>
+<h4><a name="MD_MAC">Message Digest (MD) and Message Authentication Code (MAC) Commands</a></h4>
 
     <dt><a name="tls::cmac"><strong>tls::cmac</strong>
 	<em>?</em><b>-cipher</b><em>? name</em>
 	<b>-key</b> <em>key ?</em><b>-bin</b>|<b>-hex</b><em>?
 	[</em><b>-chan</b> <em>channelId |</em> <b>-command</b> <em>cmdName |</em>
@@ -392,11 +398,11 @@
     <dt><a name="tls::unstack"><strong>tls::unstack</strong> <em>channelId</em></a></dt>
     <dd>Removes the top level cryptographic transform from channel <em>channelId</em>.</dd>
 
 <br>
 
-<h4>Encryption and Decryption Commands</h4>
+<h4><a name="Cipher">Encryption and Decryption Commands</a></h4>
 
     <dt><a name="tls::encrypt"><strong>tls::encrypt</strong>
 	<em>?</em><b>-cipher</b><em>? name</em> <b>-key</b> <em>key ?</em><b>-iv</b> <em>string?
 	[</em><b>-chan</b> <em>channelId |</em> <b>-command</b> <em>cmdName |</em>
 	<b>-infile</b> <em>filename</em> <b>-outfile</b> <em>filename |</em>
@@ -419,10 +425,27 @@
 	info. Option <b>-iv</b> is only used for some ciphers. See the
 	&quot;<b>tls::cipher</b> <em>cipher</em>&quot; command for key and iv
 	sizes and when the iv is used (iv_length &gt; 0).</dd>
 </dl>
 
+<br>
+
+<h4><a name="KDF">Key Derivation Function (KDF) Commands</a></h4>
+
+    <dt><a name="tls::derive_key"><strong>tls::derive_key</strong>
+	<em>[</em><b>-cipher</b> <em>cipher |</em> <b>-size</b> <em>size]</em>
+	<b>-digest</b> <em>digest ?</em><b>-iterations</b> <em>count?
+	?</em><b>-password</b> <em>string? ?</em><b>-salt</b> <em>string?</em></a></dt>
+    <dd>Derive a key and initialization vector (iv) from a password and salt
+	value using PKCS5_PBKDF2_HMAC. This is a more secure way to generate
+	keys and ivs for use by <a href="#tls::encrypt"><b>tls::encrypt</b></a>.
+	See <a href="#OPTIONS"><b>options</b></a> for usage info. If <b>-cipher</b>
+	is specified, then the derived key and iv sized for that cipher are
+	returned as a key-value list. If not or if <b>-size</b> is specified,
+	then the derived key (dk) of <em>size</em> bytes is returned.</dd>
+</dl>
+
 <br>
 <h3><a name="GLOSSARY">GLOSSARY</a></h3>
 
 <p>The following is a list of the terminology used in this package along with
 brief definitions. For more details, please consult with the OpenSSL documentation.</p>

Index: generic/tls.c
==================================================================
--- generic/tls.c
+++ generic/tls.c
@@ -2550,10 +2550,11 @@
     Tcl_CreateObjCommand(interp, "tls::status", StatusObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
 
     Tls_DigestCommands(interp);
     Tls_EncryptCommands(interp);
     Tls_InfoCommands(interp);
+    Tls_KeyCommands(interp);
 
     if (interp) {
 	Tcl_Eval(interp, tlsTclInitScript);
     }
 

Index: generic/tlsDigest.c
==================================================================
--- generic/tlsDigest.c
+++ generic/tlsDigest.c
@@ -1239,10 +1239,11 @@
 	OPTOBJ("-command", cmdObj);
 	OPTOBJ("-data", dataObj);
 	OPTOBJ("-digest", digestObj);
 	OPTOBJ("-file", fileObj);
 	OPTOBJ("-filename", fileObj);
+	OPTOBJ("-hash", digestObj);
 	OPTOBJ("-key", keyObj);
 	OPTOBJ("-mac", macObj);
 
 	OPTBAD("option", "-bin, -channel, -cipher, -command, -data, -digest, -file, -filename, -hex, -key, or -mac");
 	return TCL_ERROR;

Index: generic/tlsEncrypt.c
==================================================================
--- generic/tlsEncrypt.c
+++ generic/tlsEncrypt.c
@@ -1248,10 +1248,11 @@
 	OPTSTR("-channel", channel);
 	OPTOBJ("-cipher", cipherObj);
 	OPTOBJ("-command", cmdObj);
 	OPTOBJ("-data", dataObj);
 	OPTOBJ("-digest", digestObj);
+	OPTOBJ("-hash", digestObj);
 	OPTOBJ("-infile", inFileObj);
 	OPTOBJ("-outfile", outFileObj);
 	OPTOBJ("-key", keyObj);
 	OPTOBJ("-iv", ivObj);
 	OPTOBJ("-mac", macObj);

Index: generic/tlsInt.h
==================================================================
--- generic/tlsInt.h
+++ generic/tlsInt.h
@@ -35,11 +35,10 @@
 #	define CONST86 const
 #   else
 #	define CONST86
 #   endif
 #endif
-
 /*
  * Backwards compatibility for size type change
  */
 #if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7
 #   define Tcl_Size int
@@ -196,11 +195,12 @@
 void            Tls_Clean(State *statePtr);
 int             Tls_WaitForConnect(State *statePtr, int *errorCodePtr, int handshakeFailureIsPermanent);
 int             Tls_DigestCommands(Tcl_Interp *interp);
 int             Tls_EncryptCommands(Tcl_Interp *interp);
 int             Tls_InfoCommands(Tcl_Interp *interp);
+int             Tls_KeyCommands(Tcl_Interp *interp);
 
 BIO             *BIO_new_tcl(State* statePtr, int flags);
 
 #define PTR2INT(x) ((int) ((intptr_t) (x)))
 
 #endif /* _TLSINT_H */

ADDED   generic/tlsKey.c
Index: generic/tlsKey.c
==================================================================
--- /dev/null
+++ generic/tlsKey.c
@@ -0,0 +1,152 @@
+/*
+ * Key Derivation Function (KDF) Module
+ *
+ * Provides commands to derive keys.
+ *
+ * Copyright (C) 2023 Brian O'Hagan
+ *
+ */
+
+#include "tlsInt.h"
+#include "tclOpts.h"
+#include <openssl/crypto.h>
+
+/*******************************************************************/
+
+/*
+ *-------------------------------------------------------------------
+ *
+ * DeriveKey --
+ *
+ *	PKCS5_PBKDF2_HMAC key derivation function (KDF) specified by PKCS #5.
+ *	See RFC 6070.
+ *
+ * Returns:
+ *	TCL_OK or TCL_ERROR
+ *
+ * Side effects:
+ *	Sets result to a list of key and iv values, or an error message
+ *
+ *-------------------------------------------------------------------
+ */
+static int DeriveKey(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) {
+    int key_len = 0, md_len = 0, pass_len = 0, salt_len = 0;
+    int iklen, ivlen, iter = PKCS5_DEFAULT_ITER;
+    unsigned char *passwd = NULL, *salt = NULL;
+    Tcl_Obj *cipherObj = NULL, *digestObj = NULL, *passwdObj = NULL, *saltObj = NULL, *resultObj;
+    const EVP_MD *md = NULL;
+    const EVP_CIPHER *cipher = NULL;
+    int max = EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH, size = max;
+    unsigned char tmpkeyiv[EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH];
+
+    dprintf("Called");
+
+    /* Clear errors */
+    Tcl_ResetResult(interp);
+    ERR_clear_error();
+
+    /* Validate arg count */
+    if (objc < 3 || objc > 11) {
+	Tcl_WrongNumArgs(interp, 1, objv, "[-cipher cipher | -size length] -digest digest ?-iterations count? ?-password string? ?-salt string?");
+	return TCL_ERROR;
+    }
+
+    /* Init buffers */
+    memset(tmpkeyiv, 0, EVP_MAX_KEY_LENGTH + EVP_MAX_IV_LENGTH);
+
+    /* Get options */
+    for (int idx = 1; idx < objc; idx++) {
+	char *opt = Tcl_GetStringFromObj(objv[idx], NULL);
+
+	if (opt[0] != '-') {
+	    break;
+	}
+
+	OPTOBJ("-cipher", cipherObj);
+	OPTOBJ("-digest", digestObj);
+	OPTOBJ("-hash", digestObj);
+	OPTINT("-iterations", iter);
+	OPTOBJ("-password", passwdObj);
+	OPTOBJ("-salt", saltObj);
+	OPTINT("-size", size);
+
+	OPTBAD("option", "-cipher, -digest, -iterations, -password, -salt, or -size option");
+	return TCL_ERROR;
+    }
+
+    /* Validate options */
+    if (cipherObj != NULL) {
+	char *name = Tcl_GetByteArrayFromObj(cipherObj, NULL);
+	cipher = EVP_get_cipherbyname(name);
+    }
+    if (digestObj != NULL) {
+	char *name = Tcl_GetStringFromObj(digestObj, &md_len);
+	md = EVP_get_digestbyname(name);
+    } else {
+	Tcl_AppendResult(interp, "No digest specified", NULL);
+	return TCL_ERROR;
+    }
+    if (passwdObj != NULL) {
+	passwd = Tcl_GetByteArrayFromObj(passwdObj, &pass_len);
+    }
+    if (saltObj != NULL) {
+	salt = Tcl_GetByteArrayFromObj(saltObj, &salt_len);
+    }
+    if (iter < 1) {
+	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid iterations count %d: must be > 0", iter));
+	return TCL_ERROR;
+    }
+    if (size < 1 || size > max) {
+	Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid derived key length %d: must be 0 < size <= %d", size, max));
+	return TCL_ERROR;
+    }
+
+    if (cipher == NULL) {
+	if (size > max) size = max;
+	iklen = size;
+	ivlen = 0;
+    } else {
+	iklen = EVP_CIPHER_key_length(cipher);
+	ivlen = EVP_CIPHER_iv_length(cipher);
+	size = iklen+ivlen;
+    }
+
+    /* Perform password derivation */
+    if (!PKCS5_PBKDF2_HMAC(passwd, pass_len, salt, salt_len, iter, md, size, tmpkeyiv)) {
+	Tcl_AppendResult(interp, "Key derivation failed: ", REASON(), NULL);
+	return TCL_ERROR;
+    }
+
+   /* Return key and iv */
+    if (cipher == NULL) {
+	resultObj = Tcl_NewByteArrayObj(tmpkeyiv, size);
+    } else {
+	resultObj = Tcl_NewListObj(0, NULL);
+	LAPPEND_BARRAY(interp, resultObj, "key", tmpkeyiv, iklen);
+	LAPPEND_BARRAY(interp, resultObj, "iv", tmpkeyiv+iklen, ivlen);
+    }
+    Tcl_SetObjResult(interp, resultObj);
+    return TCL_OK;
+    	clientData = clientData;
+}
+
+/*
+ *-------------------------------------------------------------------
+ *
+ * Tls_KeyCommands --
+ *
+ *	Create key commands
+ *
+ * Returns:
+ *	TCL_OK or TCL_ERROR
+ *
+ * Side effects:
+ *	Creates commands
+ *
+ *-------------------------------------------------------------------
+ */
+int Tls_KeyCommands(Tcl_Interp *interp) {
+    Tcl_CreateObjCommand(interp, "tls::derive_key", DeriveKey, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
+    return TCL_OK;
+}
+

Index: win/makefile.vc
==================================================================
--- win/makefile.vc
+++ win/makefile.vc
@@ -29,10 +29,11 @@
 	$(TMP_DIR)\tlsBIO.obj \
 	$(TMP_DIR)\tlsDigest.obj \
 	$(TMP_DIR)\tlsEncrypt.obj \
 	$(TMP_DIR)\tlsInfo.obj \
 	$(TMP_DIR)\tlsIO.obj \
+	$(TMP_DIR)\tlsKey.obj \
 	$(TMP_DIR)\tlsX509.obj
 
 # Define any additional project include flags
 # SSL_INSTALL_FOLDER = with the OpenSSL installation folder following.
 PRJ_INCLUDES = -I"$(SSL_INSTALL_FOLDER)\include" -I"$(OPENSSL_INSTALL_DIR)\include"