/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_crypto.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_crypto.h"
#include "qore/intern/EncryptionTransforms.h"

#include <cstdio>
#include <cstdlib>

#include <openssl/err.h>
#include <openssl/rand.h>

#define QCRYPTO_DECRYPT 0
#define QCRYPTO_ENCRYPT 1

extern QoreClass* QC_TRANSFORM;

// maximum length of a MAC value in bytes
#define Q_TAG_MAX 4096

// size of buffer when en/decrypting monolithic data
#define Q_CRYPTO_BUFSIZE 16384

// NOTE: the trailing null ('\0') is not included when encrypting strings or calculating digests

// default initialization vector
static unsigned char def_iv[] = { 0, 0, 0, 0, 0, 0, 0, 0 };

/*
*/
class CryptoHelper : public BaseHelper {
private:
public:
    unsigned char *key[3];
    int keylen[3];

    DLLLOCAL CryptoHelper() : iv(0), output(0), output_len(0), qe(QCS_DEFAULT) {
    }

    DLLLOCAL ~CryptoHelper() {
        if (output)
            free(output);
    }

    DLLLOCAL BinaryNode *getBinary() {
        BinaryNode *b = new BinaryNode(output, output_len);
        output = 0;
        return b;
    }

    DLLLOCAL QoreStringNode *getString(const QoreEncoding *enc = QCS_DEFAULT) {
        // create the string
        QoreStringNode *str = new QoreStringNode((char *)output, output_len, output_len, enc);
        // add trailing '\0'
        str->terminate(output_len);

        output = 0;
        return str;
    }

    DLLLOCAL int setSingleKey(const char *err, const QoreListNode *params, ExceptionSink *xsink, bool has_encoding = false) {
        if (setIV(err, get_param_value(params, 2), xsink))
            return -1;

        setInput(get_param_value(params, 0));
        setKey(params, 1);
        if (has_encoding)
            setEncoding(params, 2);
        return 0;
    }

    DLLLOCAL int doCipher(const EVP_CIPHER* type, const char* cipher, int do_crypt, ExceptionSink *xsink) {
        const char *err = (do_crypt ? "ENCRYPT-ERROR" : "DECRYPT-ERROR");

        QoreEvpCipherCtxHelper ctx;
        EVP_CipherInit_ex(*ctx, type, 0, 0, 0, do_crypt);
        if (key[0]) {
            if (keylen[0] > EVP_MAX_KEY_LENGTH) {
                keylen[0] = EVP_MAX_KEY_LENGTH;
            }

            if (!EVP_CIPHER_CTX_set_key_length(*ctx, keylen[0]) || !EVP_CipherInit_ex(*ctx, 0, 0, key[0], iv, -1)) {
                xsink->raiseException(err, "error setting %s key length=%d", cipher, keylen[0]);
                return -1;
            }
        }

        // we allocate 1 byte more than we need in case we return as a string so we can terminate it
        output = (unsigned char *)malloc(sizeof(char) * (input_len + (EVP_MAX_BLOCK_LENGTH * 2)));

        if (!EVP_CipherUpdate(*ctx, output, &output_len, input, input_len)) {
            xsink->raiseException(err, "error %scrypting %s block", do_crypt ? "en" : "de", cipher);
            return -1;
        }

        int tmplen;
        // Buffer passed to EVP_EncryptFinal() must be after data just encrypted to avoid overwriting it.
        if (!EVP_CipherFinal_ex(*ctx, output + output_len, &tmplen)) {
            xsink->raiseException(err, "error %scrypting final %s block", do_crypt ? "en" : "de", cipher);
            return -1;
        }

        output_len += tmplen;
        //printd(5, "cipher_intern() %s: in=%p (%d) out=%p (%d)\n", cipher, buf, len, cbuf, *size);
        return 0;
    }

    DLLLOCAL int checkKeyLen(const char *err, int n, int len, ExceptionSink *xsink) {
        if (keylen[n] < len) {
            xsink->raiseException(err, "key length is not %d bytes long (%d bytes)", len, keylen[n]);
            return -1;
        }
        keylen[n] = len;
        return 0;
    }

/*
    DLLLOCAL int setDESKey(int n, ExceptionSink *xsink) {
        // force odd parity
        //DES_set_odd_parity((DES_cblock *)key[n]);
        // populate the schedule structure
        //DES_set_key_unchecked((DES_cblock *)key[n], schedule);
        //key[n] = (unsigned char *)schedule;
        return 0;
    }
*/

private:
    unsigned char *iv, *output;
    int output_len;
    const QoreEncoding* qe;

    DLLLOCAL const char* getOrdinal(int n) {
        assert(n > 0 && n <= 3);
        return n == 1 ? "first" : (n == 2 ? "second" : "third");
    }

    DLLLOCAL void setKey(const QoreListNode* args, int n) {
        assert(n > 0 && n <= 3);
        const QoreValue pt = get_param_value(args, n);
        if (pt.getType() == NT_STRING) {
            const QoreStringNode* str = pt.get<const QoreStringNode>();
            key[n - 1] = (unsigned char*)str->getBuffer();
            keylen[n - 1] = str->strlen();
            return;
        }

        const BinaryNode* b = pt.get<const BinaryNode>();
        key[n - 1] = (unsigned char*)b->getPtr();
        keylen[n - 1] = b->size();
    }

    DLLLOCAL void setEncoding(const QoreListNode* args, int n) {
        QoreValue qv = get_param_value(args, n);
        qe = qv.getType() == NT_STRING ? QEM.findCreate(qv.get<const QoreStringNode>()) : QCS_DEFAULT;
    }

    // set the initialization vector (used with hard typing)
    DLLLOCAL int setIV(const char* err, const QoreValue pt, ExceptionSink* xsink) {
        assert(!pt.isNothing());
        if (pt.getType() == NT_STRING) {
            const QoreStringNode* str = pt.get<const QoreStringNode>();
            if (str->strlen() < 8) {
                xsink->raiseException(err, "the input vector must be at least 8 bytes long (%d bytes passed)",
                    str->strlen());
                return -1;
            }
            iv = (unsigned char*)str->getBuffer();
            return 0;
        }

        assert(pt.getType() == NT_BINARY);
        const BinaryNode* b = pt.get<const BinaryNode>();
        if (b->size() < 8) {
            xsink->raiseException(err, "the input vector must be at least 8 bytes long (%d bytes passed)",
                b->size());
            return -1;
        }
        iv = (unsigned char*)b->getPtr();
        return 0;
    }
};

static const EVP_MD* q_lookup_digest(const char* cipher) {
#ifdef OPENSSL_3_PLUS
    return EVP_MD_fetch(nullptr, cipher, nullptr);
#else
    // find digest
    digest_map_t::const_iterator i = digest_map.find(cipher);
    return i == digest_map.end() ? nullptr : i->second;
#endif
}

int HMACHelper::doHMAC(const char* err, const char* digest, const char* ptr, size_t len, ExceptionSink* xsink) {
#ifdef OPENSSL_3_PLUS
    EVP_MAC* mac = EVP_MAC_fetch(nullptr, "hmac", nullptr);
    if (!mac) {
        xsink->raiseException(err, "error looking up MAC '%s'", digest);
        return -1;
    }
    EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
    if (!ctx) {
        xsink->raiseException(err, "error creating MAC context for MAC '%s'", digest);
        return -1;
    }
    OSSL_PARAM params[2];
    params[0] = OSSL_PARAM_construct_utf8_string("digest", (char*)digest, 0);
    params[1] = OSSL_PARAM_construct_end();

    if (!EVP_MAC_init(ctx, (const unsigned char*)ptr, len, params)) {
        xsink->raiseException("DIGEST-ERROR", "error initialing MAC context for MAC '%s'", digest);
        return -1;
    }
    if (!EVP_MAC_update(ctx, input, input_len)
        || !EVP_MAC_final(ctx, md_value, &len, sizeof(md_value))) {
        xsink->raiseException(err, "error calculating MAC '%s'", digest);
        return -1;
    }
    md_len = (unsigned int)len;
#else
    QoreHmacHelper ctx;
    if (!*ctx) {
        xsink->raiseException(err, "error allocating HMAC object");
        return -1;
    }
    const EVP_MD* md = q_lookup_digest(digest);
    if (!md) {
        xsink->raiseException("DIGEST-ERROR", "error looking up digest '%s' for HMAC", digest);
        return -1;
    }
#ifdef HAVE_OPENSSL_HMAC_RV
    int rc = HMAC_Init_ex(*ctx, ptr, len, md, 0);
    if (!rc) {
        xsink->raiseException(err, "error initializing HMAC");
        return -1;
    }
    if (!HMAC_Update(*ctx, input, input_len)
        || !HMAC_Final(*ctx, md_value, &md_len)) {
        xsink->raiseException(err, "error calculating HMAC");
        return -1;
    }
#else
    HMAC_Init_ex(*ctx, ptr, len, md, 0);
    HMAC_Update(*ctx, input, input_len);
    HMAC_Final(*ctx, md_value, &md_len);
#endif
#endif
    return 0;
}

AbstractQoreNode* missing_openssl_feature(const char *f, ExceptionSink *xsink) {
   return xsink->raiseException("MISSING-FEATURE-ERROR", "the openssl library version that qore was compiled with did not support the %s algorithm; for maximum portability, check Option::HAVE_%s before calling this function", f, f);
}

static int check_mac(const ReferenceNode* mac, int64 mac_size, ExceptionSink* xsink) {
    if (!mac_size) {
        return 0;
    }

    if (!mac) {
        xsink->raiseException("MAC-ERROR", "mac_size argument passed but no mac argument passed");
        return -1;
    }

    if (mac_size < 0) {
        xsink->raiseException("MAC-ERROR", "mac_size argument is negative (" QLLD ")", mac_size);
        return -1;
    }

    if (mac_size > Q_TAG_MAX) {
        xsink->raiseException("MAC-ERROR", "mac_size argument is too large (" QLLD "); maximum value is %d", mac_size,
            Q_TAG_MAX);
        return -1;
    }

    return 0;
}

static QoreValue apply_transformation(SimpleRefHolder<Transform>& t, SimpleRefHolder<BinaryNode>& rv, const char* datastr,
        size_t data_len, ExceptionSink* xsink) {
    // preallocate space for the decrypted data
    rv->preallocate(data_len + (EVP_MAX_BLOCK_LENGTH * 2));
    size_t rv_maxsize = rv->size();
    size_t rvsize = 0;
    size_t offset = 0;

    // decrypt data
    while (offset != data_len) {
        // get block size
        size_t bs = data_len - offset;

        // ensure we always have buffer space for the encryption algorithm
        {
            size_t new_max = rvsize + bs + (EVP_MAX_BLOCK_LENGTH * 2);

            // reallocate target buffer if necessary
            if (new_max > rv_maxsize) {
                rv_maxsize = new_max;
                rv->preallocate(rv_maxsize);
            }
        }

        size_t cbs = rv_maxsize - rvsize;
        std::pair<int64, int64> p = t->apply(datastr + offset, bs, (char*)rv->getPtr() + rvsize, cbs, xsink);
        if (*xsink)
            return QoreValue();
        offset += p.first;
        rvsize += p.second;
    }

    // close the stream
    while (true) {
        // ensure we always have buffer space for the encryption algorithm
        {
            size_t new_max = rvsize + (EVP_MAX_BLOCK_LENGTH * 2);

            // reallocate target buffer if necessary
            if (new_max > rv_maxsize) {
                rv_maxsize = new_max;
                rv->preallocate(rv_maxsize);
            }
        }

        size_t cbs = rv_maxsize - rvsize;
        std::pair<int64, int64> p = t->apply(nullptr, 0, (char*)rv->getPtr() + rvsize, cbs, xsink);
        if (*xsink)
            return QoreValue();
        if (!p.second)
            break;
        rvsize += p.second;
    }

    rv->setSize(rvsize);
    return rv.release();
}

static const EVP_MD* q_get_digest(const QoreString& digest, ExceptionSink* xsink) {
    const EVP_MD* md = q_lookup_digest(digest.c_str());
    if (!md) {
        xsink->raiseException("DIGEST-ERROR", "unknown digest '%s'; check DigestMap for supported digests",
            digest.c_str());
    }
    return md;
}

/** @defgroup crypto_transformations Cryptographic Stream Transformations

    The following @ref Transform constants can be used with @ref TransformInputStream and @ref TransformOutputStream to transform stream data

    @par Example:
    @code{.cpp}
Qore::FileOutputStream of("my-file.txt");
Qore::TransformOutputStream ts(of, get_decryptor(Qore::CRYPTO_ALG_BLOWFISH));
    @endcode

    These constants are useful in the following functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @see @ref compression_transformations

    @since %Qore 0.8.13
 */
///@{
//! Identifies the <a href="http://www.counterpane.com/blowfish.html">blowfish cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a variable-length key
/** This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_BLOWFISH = "blowfish";

//! Identifies the <a href="http://www.counterpane.com/blowfish.html">blowfish cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a variable-length key
/** This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_BLOWFISH_CFB = "blowfish-cfb";

//! Identifies the <a href="http://www.counterpane.com/blowfish.html">blowfish cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a variable-length key
/** This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_BLOWFISH_OFB = "blowfish-ofb";

//! Identifies the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a 64-bit (8 byte) key
/** @note
    - This algorithm requires an 8-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES = "des";

//! Identifies the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a 64-bit (8 byte) key
/** @note
    - This algorithm requires an 8-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_CFB = "des-cfb";

//! Identifies the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a 64-bit (8 byte) key
/** @note
    - This algorithm requires an 8-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_OFB = "des-ofb";

//! Identifies the two-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a 128-bit (16 byte) key
/** @note
    - This algorithm requires a 16-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE = "desede";

//! Identifies the two-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a 128-bit (16 byte) key
/** @note
    - This algorithm requires a 16-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE_CFB = "desede-cfb";

//! Identifies the two-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a 128-bit (16 byte) key
/** @note
    - This algorithm requires a 16-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE_OFB = "desede-ofb";

//! Identifies the three-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a 192-bit (24 byte) key
/** @note
    - This algorithm requires a 24-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE3 = "desede3";

//! Identifies the three-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a 192-bit (24 byte) key
/** @note
    - This algorithm requires a 24-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE3_CFB = "desede3-cfb";

//! Identifies the three-key <a href="https://en.wikipedia.org/wiki/Triple_DES">triple DES algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a 192-bit (24 byte) key
/** @note
    - This algorithm requires a 24-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DES_EDE3_OFB = "desede3-ofb";

//! Identifies RSA's <a href="http://www.rsasecurity.com/">DESX cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a 192-bit (24 byte) key
/** @note
    - This algorithm requires a 24-byte key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD)
    - Keys longer than the required length will be used up to the required byte length; additional bytes are ignored
 */
const CRYPTO_ALG_DESX = "desx";

//! Identifies RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a variable-length key
/** @note this algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).

 */
const CRYPTO_ALG_RC2 = "rc2";

//! Identifies RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a variable-length key
/** @note this algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).

 */
const CRYPTO_ALG_RC2_CFB = "rc2-cfb";

//! Identifies RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a variable-length key
/** @note this algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).

 */
const CRYPTO_ALG_RC2_OFB = "rc2-ofb";

//! Identifies the Alleged <a href="http://en.wikipedia.org/wiki/RC4">RC4 cryptographic algorithm</a>, which should be compatible with RSA's RC4(TM) algorithm in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a variable-length key
/** @note
    - This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
    - This algorithm does not support (ignores) initialization vectors
 */
const CRYPTO_ALG_RC4 = "rc4";

//! Identifies the <a href="https://en.wikipedia.org/wiki/RC5">RC5 cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a variable-length key
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    Use of this cipher is only possible if the constant listed above is @ref True "True"

    @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_RC5 = "rc5";

//! Identifies the <a href="https://en.wikipedia.org/wiki/RC5">RC5 cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a variable-length key
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    Use of this cipher is only possible if the constant listed above is @ref True "True"

    @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_RC5_CFB = "rc5-cfb";

//! Identifies the <a href="https://en.wikipedia.org/wiki/RC5">RC5 cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a variable-length key
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    Use of this cipher is only possible if the constant listed above is @ref True "True"

    @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_RC5_OFB = "rc5-ofb";

//! Identifies the <a href="https://en.wikipedia.org/wiki/CAST-128">CAST5 cryptographic algorithm</a> in <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining (CBC)</a> mode with a variable-length key
/** @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_CAST5 = "cast5";

//! Identifies the <a href="https://en.wikipedia.org/wiki/CAST-128">CAST5 cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29">Cipher Feedback (CFB)</a> mode with a variable-length key
/** @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_CAST5_CFB = "cast5-cfb";

//! Identifies the <a href="https://en.wikipedia.org/wiki/CAST-128">CAST5 cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_Feedback_.28OFB.29">Output Feedback (OFB)</a> mode with a variable-length key
/** @note This algorithm supports a variable-length key and does not support a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> or Additional Authenticated Data (AAD).
 */
const CRYPTO_ALG_CAST5_OFB = "cast5-ofb";

//! Identifies the <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode">Galois Counter Mode (GCM)</a> with a 128-bit (16 byte) key
/** The initialization vector is recommended to be 12 bytes in this case

    @note
    - This algorithm supports returning a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> when encrypting and requires a MAC for decryption, therefore at least one byte of the MAC should be retrieved when encoding as well
    - The size of the calculated tag for the MAC is 16 bytes; shorter sizes can also be used
    - This algorithm also supports Additional Authenticated Data (AAD) for encryption and decryption
 */
const CRYPTO_ALG_AES_128 = "aes128";

//! Identifies the <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode">Galois Counter Mode (GCM)</a> with a 192-bit (24 byte) key
/** The initialization vector is recommended to be 12 bytes in this case.

    @note
    - This algorithm supports returning a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> when encrypting and requires a MAC for decryption, therefore at least one byte of the MAC should be retrieved when encoding as well
    - The size of the calculated tag for the MAC is 16 bytes; shorter sizes can also be used
    - This algorithm also supports Additional Authenticated Data (AAD) for encryption and decryption
 */
const CRYPTO_ALG_AES_192 = "aes192";

//! Identifies the <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES cryptographic algorithm</a> in <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode">Galois Counter Mode (GCM)</a> with a 256-bit (32 byte) key
/** The initialization vector is recommended to be 12 bytes in this case

    @note
    - This algorithm supports returning a <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> when encrypting and requires a MAC for decryption, therefore at least one byte of the MAC should be retrieved when encoding as well
    - The size of the calculated tag for the MAC is 16 bytes; shorter sizes can also be used
    - This algorithm also supports Additional Authenticated Data for encryption and decryption
 */
const CRYPTO_ALG_AES_256 = "aes256";
///@}

//! @qppinit: SimpleRefHolder<BinaryNode> DefaultIV(new BinaryNode); DefaultIV->append(def_iv, 8);
//! @qppval: Qore::DefaultIV=DefaultIV->refSelf()

/** @defgroup cryptographic_constants Cryptographic Contants
    Other generic cryptographic constants

    @see @ref crypto_transformations for constants related to cryptographic algorithms
 */
///@{
//! The default initialization vector is simply a 8-byte string of nulls
const DefaultIV = <0000000000000000>;
///@}

/** @defgroup digest_algorithms Crytographic Digest Algorithms

    The following constants can be used with the @ref Qore::digest() "digest()" and @ref Qore::hmac() "hmac()"
    functions to calculate cryptographic digests and HMACs on input data.

    @par Example:
    @code{.cpp}
Qore::FileOutputStream of("my-file.txt");
Qore::TransformOutputStream ts(of, get_decryptor(Qore::CRYPTO_ALG_BLOWFISH));
    @endcode

    These constants are useful in the following functions:
    - @ref Qore::digest()
    - @ref Qore::hmac()

    @since %Qore 0.9.3
 */
///@{
//! specifies the <a href="http://en.wikipedia.org/wiki/MD2_(cryptography)">MD2 message digest</a>
/**
*/
const CRYPTO_DIGEST_MD2 = "md2";

//! specifies the <a href="http://en.wikipedia.org/wiki/MD4">MD4 message digest</a>
/**
*/
const CRYPTO_DIGEST_MD4 = "md4";

//! specifies the <a href="http://en.wikipedia.org/wiki/MD5">MD5 message digest</a>
/**
*/
const CRYPTO_DIGEST_MD5 = "md5";

//! specifies the insecure and outdated SHA-0 message digest; only supplied for compatibility purposes
/**
*/
const CRYPTO_DIGEST_SHA = "sha";

//! specifies the <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1 message digest</a>
/**
*/
const CRYPTO_DIGEST_SHA1 = "sha1";

//! specifies the SHA-224 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>)
/**
*/
const CRYPTO_DIGEST_SHA224 = "sha224";

//! specifies the SHA-256 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>)
/**
*/
const CRYPTO_DIGEST_SHA256 = "sha256";

//! specifies the SHA-384 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>)
/**
*/
const CRYPTO_DIGEST_SHA384 = "sha384";

//! specifies the SHA-512 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>)
/**
*/
const CRYPTO_DIGEST_SHA512 = "sha512";

//! specifies the DSS message digest (based on SHA-0 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>)
/**
*/
const CRYPTO_DIGEST_DSS = "dss";

//! specifies the DSS1 message digest (based on <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>)
/**
*/
const CRYPTO_DIGEST_DSS1 = "dss1";

//! specifies the <a href="http://en.wikipedia.org/wiki/MDC2">MDC2 message digest</a>
/**
*/
const CRYPTO_DIGEST_MDC2 = "mdc2";

//! specifies the <a href="http://en.wikipedia.org/wiki/RIPEMD">RIPEMD message digest</a>
/**
*/
const CRYPTO_DIGEST_RIPEMD160 = "ripemd160";

//! A hash keyed by supported digest name where each value is @ref True "True"
/** @since %Qore 0.9.3

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all digests known to the OpenSSL
    library, so digest names not included as keys in this hash may also be recignized as aliases for known digests
*/
const DigestMap = qore(init_digest_map_hash());

//! A hash keyed by supported cipher name where each value is an informational hash
/**
    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, so cipher names not included as keys in this hash may also be recignized as aliases for known ciphers

    @since %Qore 1.4
*/
const CipherMap = qore(init_cipher_map_hash());
///@}

/** @defgroup cryptographic_functions Cryptographic Functions
    %Qore's cryptography support is provided by the OpenSSL library. Each of the encryption and decryption functions in this section accept an optional initialization vector, which is data used as initial input for the first block in chained encryption algorithms. Subsequent blocks take input from the last block encrypted/decrypted. If an initialization vector is not supplied, a default value of 8 zero bytes will be used (see @ref Qore::DefaultIV).

    See also:
        - @ref digest_functions
        - @ref hmac_functions
        - @ref cryptographic_constants

    Some functions require fixed-length keys, and some allow the use of variable-length keys. For functions requiring fixed-length keys any excess bytes are ignored. The same applies to initialization vector arguments.

    The following is an example of a function that uses \c /dev/random on UNIX to read in a random key for use with encryption functions:

    @code{.py}
# read a key from /dev/random and return the key
binary sub get_key(int size) {
    # throw an exception if an invalid key size was passed
    if (!size || size < 0) {
        throw "GET-KEY-ERROR", sprintf("invalid size = %n", size);
    }
    File f();
    # File::open2() will throw an exception if /dev/random cannot be opened for reading
    f.open2("/dev/random");
    return f.readBinary(size);
}
    @endcode

    @note The following generic cryptographic functions are recommended over the older cipher-specific functions which will be deprecated in a later release of %Qore:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
///@{

//! Returns a binary value of a random key for the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a>
/** @return a binary value of a random key for the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a>

    @par Example:
    @code{.py}
binary bin = des_random_key();
    @endcode
 */
binary des_random_key() [flags=CONSTANT] [flags=CONSTANT] {
#ifdef OPENSSL_3_PLUS
    QoreEvpCipherCtxHelper ctx;
    if (!ctx) {
        xsink->outOfMemory();
        return QoreValue();
    }
    if (!EVP_CipherInit_ex(*ctx, EVP_des_cbc(), nullptr, nullptr, nullptr, 1)) {
        xsink->raiseException("DES-ERROR", "failed to initialize DES encryption context to generate random key");
        return QoreValue();
    }
    int len = EVP_CIPHER_get_key_length(EVP_des_cbc());
    if (len <= 0) {
        xsink->raiseException("DES-ERROR", "OpenSSL return an invalid DES key length value: %d", len);
        return QoreValue();
    }
    unsigned char* key = (unsigned char*)malloc(sizeof(char) * len);
    if (!key) {
        xsink->outOfMemory();
        return QoreValue();
    }
    if (!EVP_CIPHER_CTX_rand_key(*ctx, key)) {
        xsink->raiseException("DES-ERROR", "OpenSSL failed to generate a DES key");
        return QoreValue();
    }
    return new BinaryNode(key, len);
#else
    DES_cblock* db = (DES_cblock*)malloc(sizeof(DES_cblock));
    DES_random_key(db);
    return new BinaryNode(db, sizeof(DES_cblock));
#endif
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.counterpane.com/blowfish.html">blowfish algorithm</a>
/** @param data the data to be encrypted
    @param key a variable-length key (recommended 16 bytes or more)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = blowfish_encrypt_cbc(data, key);
    @endcode

    @throw BLOWFISH-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_BLOWFISH "CRYPTO_ALG_BLOWFISH", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary blowfish_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("BLOWFISH-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_bf_cbc(), "blowfish", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.counterpane.com/blowfish.html">blowfish algorithm</a> and returns a binary value of the decrypted data
/** @param data the data to be decrypted
    @param key a variable-length key (recommended 16 bytes or more)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = blowfish_decrypt_cbc(data, key);
    @endcode

    @throw BLOWFISH-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_BLOWFISH "CRYPTO_ALG_BLOWFISH", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary blowfish_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("BLOWFISH-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_bf_cbc(), "blowfish", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.counterpane.com/blowfish.html">blowfish algorithm</a> and returns a string of the decrypted data
/** @param data the data to be decrypted
    @param key a variable-length key (recommended 16 bytes or more)
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string str = blowfish_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw BLOWFISH-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_BLOWFISH "CRYPTO_ALG_BLOWFISH", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string blowfish_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("BLOWFISH-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.doCipher(EVP_bf_cbc(), "blowfish", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using an 8-byte key
/** @param data the data to be encrypted
    @param key the encryption key must be at least 8 bytes long (only the first 8 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = des_encrypt_cbc(data, key);
    @endcode

    @throw DES-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_DES "CRYPTO_ALG_DES", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 8, xsink)
        || ch.doCipher(EVP_des_cbc(), "DES", QCRYPTO_ENCRYPT, xsink)) {
                return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using an 8-byte key
/** @param data the data to be decrypted
    @param key the decryption key must be at least 8 bytes long (only the first 8 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = des_decrypt_cbc(data, key);
    @endcode

    @throw DES-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_DES "CRYPTO_ALG_DES", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 8, xsink)
        || ch.doCipher(EVP_des_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
                return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using an 8-byte key
/** @param data the data to be decrypted
    @param key the decryption key must be at least 8 bytes long (only the first 8 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string bin = des_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw DES-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_DES "CRYPTO_ALG_DES", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string des_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 8, xsink)
        || ch.doCipher(EVP_des_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
                return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the two-key triple DES algorithm using two eight-byte keys (set by a single 16-byte key argument)
/** @param data the data to be encrypted
    @param key the encryption key must be at least 16 bytes long (only the first 16 bytes will be used for the two 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = des_ede_encrypt_cbc(data, key);
    @endcode

    @throw DES-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_DES_EDE "CRYPTO_ALG_DES_EDE", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_ede_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 16, xsink)
        || ch.doCipher(EVP_des_ede_cbc(), "DES", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the two-key triple DES algorithm using two eight-byte keys (set by a single 16-byte key argument)
/** @param data the data to be decrypted
    @param key the decryption key must be at least 16 bytes long (only the first 16 bytes will be used for the two 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = des_ede_decrypt_cbc(data, key);
    @endcode

    @throw DES-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_DES_EDE "CRYPTO_ALG_DES_EDE", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_ede_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 16, xsink)
        || ch.doCipher(EVP_des_ede_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the two-key triple DES algorithm using two eight-byte keys (set by a single 16-byte key argument)
/** @param data the data to be decrypted
    @param key the decryption key must be at least 16 bytes long (only the first 16 bytes will be used for the two 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string bin = des_ede_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw DES-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_DES_EDE "CRYPTO_ALG_DES_EDE", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string des_ede_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 16, xsink)
        || ch.doCipher(EVP_des_ede_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the three-key triple <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using three 8-byte keys (set by a single 24-byte key argument) and an optional 8-byte initialization vector
/** @param data the data to be encrypted
    @param key the encryption key must be at least 24 bytes long (only the first 24 bytes will be used for the three 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = des_ede3_encrypt_cbc(data, key);
    @endcode

    @throw DES-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_DES_EDE3 "CRYPTO_ALG_DES_EDE3", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_ede3_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_des_ede3_cbc(), "DES", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the three-key triple <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using three 8-byte keys (set by a single 24-byte key argument) and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key the decryption key must be at least 24 bytes long (only the first 24 bytes will be used for the three 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = des_ede3_decrypt_cbc(data, key);
    @endcode

    @throw DES-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_DES_EDE3 "CRYPTO_ALG_DES_EDE3", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary des_ede3_decrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_des_ede3_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the three-key triple <a href="http://www.itl.nist.gov/fipspubs/fip46-2.htm">DES algorithm</a> using three 8-byte keys (set by a single 24-byte key argument) and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key the decryption key must be at least 24 bytes long (only the first 24 bytes will be used for the three 8-byte keys)
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string bin = des_ede3_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw DES-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DES-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_DES_EDE3 "CRYPTO_ALG_DES_EDE2", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string des_ede3_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DES-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.checkKeyLen("DES-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_des_ede3_cbc(), "DES", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://www.rsasecurity.com/">DESX algorithm</a> using a 24-byte key and an optional 8-byte initialization vector
/** @param data the data to be encrypted
    @param key the encryption key must be at least 24 bytes long (only the first 24 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = desx_encrypt_cbc(data, key);
    @endcode

    @throw DESX-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DESX-KEY-ERROR invalid key (too short)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_DESX "CRYPTO_ALG_DESX", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary desx_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DESX-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DESX-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_desx_cbc(), "DESX", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://www.rsasecurity.com/">DESX algorithm</a> using a 24-byte key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key the decryption key must be at least 24 bytes long (only the first 24 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = desx_decrypt_cbc(data, key);
    @endcode

    @throw DESX-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DESX-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_DESX "CRYPTO_ALG_DESX", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary desx_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DESX-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.checkKeyLen("DESX-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_desx_cbc(), "DESX", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://www.rsasecurity.com/">DESX algorithm</a> using a 24-byte key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key the decryption key must be at least 24 bytes long (only the first 24 bytes will be used)
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
string str = desx_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw DESX-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DESX-KEY-ERROR invalid key (too short)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_DESX "CRYPTO_ALG_DESX", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string desx_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("DESX-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.checkKeyLen("DESX-KEY-ERROR", 0, 24, xsink)
        || ch.doCipher(EVP_desx_cbc(), "DESX", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the Alleged <a href="http://en.wikipedia.org/wiki/RC4">RC4 cipher algorithm</a>, which should be compatible with RSA's RC4(tm) algorithm using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be encrypted
    @param key a variable-width encryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = rc4_encrypt(data, key);
    @endcode

    @throw RC4-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note
    - Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_RC4 "CRYPTO_ALG_RC4", data, key, iv)
    - This algorithm does not support (ignores) initialization vectors

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc4_encrypt(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC4-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc4(), "rc4", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the Alleged <a href="http://en.wikipedia.org/wiki/RC4">RC4 cipher algorithm</a>, which should be compatible with RSA's RC4(tm) algorithm using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = rc4_decrypt(data, key);
    @endcode

    @throw RC4-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note
    - Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_RC4 "CRYPTO_ALG_RC4", data, key, iv)
    - This algorithm does not support (ignores) initialization vectors

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc4_decrypt(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC4-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc4(), "rc4", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the Alleged <a href="http://en.wikipedia.org/wiki/RC4">RC4 cipher algorithm</a>, which should be compatible with RSA's RC4(tm) algorithm using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string str = rc4_decrypt_to_string(encrypted_data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw RC4-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note
    - Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_RC4 "CRYPTO_ALG_RC4", data, key, iv)
    - This algorithm does not support (ignores) initialization vectors

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string rc4_decrypt_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC4-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.doCipher(EVP_rc4(), "rc4", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be encrypted
    @param key a variable-width encryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = rc2_encrypt_cbc(data, key);
    @endcode

    @throw RC2-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_RC2 "CRYPTO_ALG_RC2", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc2_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC2-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc2_cbc(), "rc2", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = rc2_decrypt_cbc(data, key);
    @endcode

    @throw RC2-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_RC2 "CRYPTO_ALG_RC2", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc2_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC2-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc2_cbc(), "rc2", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC2">RC2(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string str = rc2_decrypt_cbc_to_string(data, key);
    @endcode

    @throw RC2-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_RC2 "CRYPTO_ALG_RC2", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string rc2_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("RC2-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.doCipher(EVP_rc2_cbc(), "rc2", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://en.wikipedia.org/wiki/CAST5">CAST5 algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be encrypted
    @param key a variable-width encryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = cast5_encrypt_cbc(data, key);
    @endcode

    @throw CAST5-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_CAST5 "CRYPTO_ALG_CAST5", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary cast5_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("CAST5-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_cast5_cbc(), "CAST5", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://en.wikipedia.org/wiki/CAST5">CAST5 algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = cast5_decrypt_cbc(data, key);
    @endcode

    @throw CAST5-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_CAST5 "CRYPTO_ALG_CAST5", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary cast5_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("CAST5-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_cast5_cbc(), "CAST5", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for the <a href="http://en.wikipedia.org/wiki/CAST5">CAST5 algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string bin = cast5_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw CAST5-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_CAST5 "CRYPTO_ALG_CAST5", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string cast5_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
    CryptoHelper ch;

    if (ch.setSingleKey("CAST5-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.doCipher(EVP_cast5_cbc(), "CAST5", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
}

//! Encrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC5">RC5(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    @param data the data to be encrypted
    @param key a variable-width encryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the encrypted data

    @par Example:
    @code{.py}
binary bin = rc2_encrypt_cbc(data, key);
    @endcode

    @throw RC5-ENCRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw ENCRYPT-ERROR processing error encrypting the data (should not normally happen)

    @note Equivalent to @ref Qore::encrypt() "encrypt"(@ref Qore::CRYPTO_ALG_RC5 "CRYPTO_ALG_RC5", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc5_encrypt_cbc(data[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
#ifndef OPENSSL_NO_RC5
    CryptoHelper ch;

    if (ch.setSingleKey("RC5-ENCRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc5_32_12_16_cbc(), "rc5", QCRYPTO_ENCRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
#else
    return missing_openssl_feature("RC5", xsink);
#endif
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC5">RC5(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present

    @return a binary value of the decrypted data

    @par Example:
    @code{.py}
binary bin = rc2_decrypt_cbc(data, key);
    @endcode

    @throw RC5-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_binary() "decrypt_to_binary"(@ref Qore::CRYPTO_ALG_RC5 "CRYPTO_ALG_RC5", data, key, iv)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()
 */
binary rc5_decrypt_cbc(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV) [flags=RET_VALUE_ONLY] {
#ifndef OPENSSL_NO_RC5
    CryptoHelper ch;

    if (ch.setSingleKey("RC5-DECRYPT-PARAM-ERROR", args, xsink)
        || ch.doCipher(EVP_rc5_32_12_16_cbc(), "rc5", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getBinary();
#else
    return missing_openssl_feature("RC5", xsink);
#endif
}

//! Decrypts data using the <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a> function for RSA's <a href="http://en.wikipedia.org/wiki/RC5">RC5(tm) algorithm</a> using a variable-length key and an optional 8-byte initialization vector
/** @par Platform Availability:
    @ref Qore::Option::HAVE_RC5

    @param data the data to be decrypted
    @param key a variable-width decryption key
    @param iv the initialization vector must be at least 8 bytes long if present
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of the decrypted data

    @par Example:
    @code{.py}
string str = rc2_decrypt_cbc_to_string(data, key, NOTHING, "iso-8859-1");
    @endcode

    @throw RC5-DECRYPT-PARAM-ERROR invalid initialization vector (less than 8 bytes)
    @throw DECRYPT-ERROR processing error decrypting the data (for example invalid data)

    @note Equivalent to @ref Qore::decrypt_to_string() "decrypt_to_string"(@ref Qore::CRYPTO_ALG_RC5 "CRYPTO_ALG_RC5", data, key, iv, ...)

    @see the following generic cryptographic API functions:
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.4 added the encoding parameter to specify the output encoding of the string
 */
string rc5_decrypt_cbc_to_string(binary[doc] data, data[doc] key, data[doc] iv = Qore::DefaultIV, *string[doc] encoding) [flags=RET_VALUE_ONLY] {
#ifndef OPENSSL_NO_RC5
    CryptoHelper ch;

    if (ch.setSingleKey("RC5-DECRYPT-PARAM-ERROR", args, xsink, true)
        || ch.doCipher(EVP_rc5_32_12_16_cbc(), "rc5", QCRYPTO_DECRYPT, xsink)) {
        return QoreValue();
    }

    return ch.getString();
#else
    return missing_openssl_feature("RC5", xsink);
#endif
}
///@}

/** @defgroup digest_functions Digest (Hash) Functions
    %Qore's cryptography support is provided by the OpenSSL library.

    A cryptographic hash function is a hash function; that is, an algorithm
    that takes an arbitrary block of data and returns a fixed-size bit string,
    the (cryptographic) hash value, such that any (accidental or intentional)
    change to the data will (with very high probability) change the hash
    value. The data to be encoded are often called the "message," and the
    hash value is sometimes called the message digest or simply digest.

    For more info: <a href="http://en.wikipedia.org/wiki/Cryptographic_hash_function">Wikipedia's Cryptographic hash function article</a>.

    See also:
        - @ref cryptographic_functions
        - @ref hmac_functions
        - @ref cryptographic_constants

 */
///@{

//! Returns the <a href="http://en.wikipedia.org/wiki/MD2_(cryptography)">MD2 message digest</a> of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MD2

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "a9046c73e00331af68917d3804f70655")

    @par Example:
    @code{.py}
string str = MD2("hello"); # returns "a9046c73e00331af68917d3804f70655"
    @endcode

    @throw MD2-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MD2_bin()
    - digest()
 */
string MD2(data[doc] data) {
#if !defined(OPENSSL_NO_MD2) && !defined(NO_MD2)
    DigestHelper dh(args);
    if (dh.doDigest(MD2_ERR, EVP_md2(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("MD2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD4">MD4 message digest</a> of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "866437cb7a794bce2b727acc0362ee27")

    @par Example:
    @code{.py}
string str = MD4("hello"); # returns "866437cb7a794bce2b727acc0362ee27"
    @endcode

    @throw MD4-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MD4_bin()
    - digest()
 */
string MD4(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(MD4_ERR, EVP_md4(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD5">MD5 message digest</a> of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "5d41402abc4b2a76b9719d911017c592")

    @par Example:
    @code{.py}
string str = MD5("hello"); # returns "5d41402abc4b2a76b9719d911017c592"
    @endcode

    @throw MD5-DIGEST-ERROR error calculating digest (should not normally happen)

    @note
    - equivalent to <string>::toMD5()
    - the MD5 algorithm is not collision-resistant; it's recommended to use another hash algorithm (like SHA-256) if cryptographic security is important

    @see
    - MD5_bin()
    - digest()
*/
string MD5(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(MD5_ERR, EVP_md5(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the SHA (outdated SHA-0) message digest of the supplied argument as a hex string
/** This hash algorithm was withdrawn after publishing and is considered to have serious flaws.

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "ac62a630ca850b4ea07eda664eaecf9480843152")

    @par Example:
    @code{.py}
string str = SHA("hello"); returns "ac62a630ca850b4ea07eda664eaecf9480843152"
    @endcode

    @throw SHA-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA_bin()
    - digest()
*/
string SHA(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifdef HAVE_OPENSSL_SHA
    DigestHelper dh(args);
    if (dh.doDigest(SHA_ERR, EVP_sha(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    missing_openssl_feature("SHA", xsink);
    return 0;
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> message digest of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")

    @par Example:
    @code{.py}
string str = SHA1("hello"); # "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
    @endcode

    @throw SHA1-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to <string>::toSHA1()

    @see
    - SHA1_bin()
    - digest()
 */
string SHA1(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(SHA1_ERR, EVP_sha1(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the SHA-224 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA224

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193")

    @par Example:
    @code{.py}
string str = SHA224("hello"); # returns "ea09ae9cc6768c50fcee903ed054556e5bfc8347907f12598aa24193"
    @endcode

    @throw SHA224-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to <string>::toSHA224()

    @see
    - digest()
    - SHA224_bin()
*/
string SHA224(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA224_ERR, EVP_sha224(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("SHA224", xsink);
#endif
}

//! Returns the SHA-256 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA256

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")

    @par Example:
    @code{.py}
string str = SHA256("hello"); # returns "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    @endcode

    @throw SHA256-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to <string>::toSHA256()

    @see
    - SHA256_bin()
    - digest()
*/
string SHA256(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA256_ERR, EVP_sha256(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("SHA256", xsink);
#endif
}

//! Returns the SHA-384 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA384

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f")

    @par Example:
    @code{.py}
string str = SHA384("hello"); # returns "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"
    @endcode

    @throw SHA384-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to <string>::toSHA384()

    @see
    - SHA384_bin()
    - digest()
*/
string SHA384(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA384_ERR, EVP_sha384(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("SHA384", xsink);
#endif
}

//! Returns the SHA-512 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA512

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043")

    @par Example:
    @code{.py}
string str = SHA512("hello"); # returns "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
    @endcode

    @throw SHA512-DIGEST-ERROR error calculating digest (should not normally happen)

    @note equivalent to <string>::toSHA512()

    @see
    - SHA512_bin()
    - digest()
*/
string SHA512(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA512_ERR, EVP_sha512(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("SHA512", xsink);
#endif
}

//! Returns the DSS message digest (based on SHA-0 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")

    @par Example:
    @code{.py}
string str = DSS("hello"); # returns "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
    @endcode

    @throw DSS-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - DSS_bin()
    - digest()

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library
*/
string DSS(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    DigestHelper dh(args);
    if (dh.doDigest(DSS_ERR, EVP_dss(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the DSS1 message digest (based on <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d")

    @par Example:
    @code{.py}
string str = DSS1("hello"); # returns "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
    @endcode

    @throw DSS1-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - DSS1_bin()
    - digest()

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library
 */
string DSS1(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    DigestHelper dh(args);
    if (dh.doDigest(DSS1_ERR, EVP_dss1(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MDC2">MDC2 message digest</a> of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MDC2

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "4517036cf97b2407d6fe22aa5ab878a3")

    @par Example:
    @code{.py}
string str = MDC2("hello"); # returns "4517036cf97b2407d6fe22aa5ab878a3"
    @endcode

    @throw MDC2-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MDC2_bin()
    - digest()
 */
string MDC2(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifdef OPENSSL_HAVE_MDC2
    DigestHelper dh(args);
    if (dh.doDigest(MDC2_ERR, EVP_mdc2(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    return missing_openssl_feature("MDC2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/RIPEMD">RIPEMD message digest</a> of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a hex string of the digest (ex: \c "108f07b8382412612c048d07d13f814118445acd")

    @par Example:
    @code{.py}
string str = RIPEMD160("hello"); # returns "108f07b8382412612c048d07d13f814118445acd"
    @endcode

    @throw RIPEMD160-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - RIPEMD160_bin()
    - digest()
*/
string RIPEMD160(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(RIPEMD160_ERR, EVP_ripemd160(), xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD2_(cryptography)">MD2 message digest</a> of the supplied argument as binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MD2

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = MD2_bin("hello");
    @endcode

    @throw MD2-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MD2()
    - digest()
*/
binary MD2_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifdef OPENSSL_HAVE_MDC2
    DigestHelper dh(args);
    if (dh.doDigest(MD2_ERR, EVP_mdc2(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    return missing_openssl_feature("MD2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD4">MD4 message digest</a> of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = MD4_bin("hello");
    @endcode

    @throw MD4-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MD4()
    - digest()
*/
binary MD4_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(MD4_ERR, EVP_md4(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD5">MD5 message digest</a> of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = MD5_bin("hello");
    @endcode

    @throw MD5-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MD5()
    - digest()
*/
binary MD5_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(MD5_ERR, EVP_md5(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
}

//! Returns the SHA (outdated SHA-0) message digest of the supplied argument as a binary value
/** This hash algorithm was withdrawn after publishing and is considered to have serious flaws.

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA_bin("hello");
    @endcode

    @throw SHA-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA()
    - digest()
*/
binary SHA_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifdef HAVE_OPENSSL_SHA
    DigestHelper dh(args);
    if (dh.doDigest(SHA_ERR, EVP_sha(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    missing_openssl_feature("SHA", xsink);
    return 0;
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/SHA-1">SHA1</a> message digest of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA1_bin("hello");
    @endcode

    @throw SHA1-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA1()
    - digest()
*/
binary SHA1_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(SHA1_ERR, EVP_sha1(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
}

//! Returns the SHA-224 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA224

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA224_bin("hello");
    @endcode

    @throw SHA224-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA224()
    - digest()
*/
binary SHA224_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA224_ERR, EVP_sha224(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    return missing_openssl_feature("SHA224", xsink);
#endif
}

//! Returns the SHA-256 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA256

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA256_bin("hello");
    @endcode

    @throw SHA256-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA256()
    - digest()
*/
binary SHA256_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA256_ERR, EVP_sha256(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    return missing_openssl_feature("SHA256", xsink);
#endif
}

//! Returns the SHA-384 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA384

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA384_bin("hello");
    @endcode

    @throw SHA384-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA384()
    - digest()
*/
binary SHA384_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA384_ERR, EVP_sha384(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    return missing_openssl_feature("SHA384", xsink);
#endif
}

//! Returns the SHA-512 message digest (a variant of <a href="http://en.wikipedia.org/wiki/SHA-2">SHA-2</a>) of the supplied argument as a binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA512

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = SHA512_bin("hello");
    @endcode

    @throw SHA512-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - SHA512()
    - digest()
*/
binary SHA512_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#if !defined(OPENSSL_NO_SHA512) && defined(HAVE_OPENSSL_SHA512)
    DigestHelper dh(args);
    if (dh.doDigest(SHA512_ERR, EVP_sha512(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    return missing_openssl_feature("SHA512", xsink);
#endif
}

//! Returns the DSS message digest (based on SHA-0 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = DSS_bin("hello");
    @endcode

    @throw DSS-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - DSS()
    - digest()

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library
*/
binary DSS_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    DigestHelper dh(args);
    if (dh.doDigest(DSS_ERR, EVP_dss(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the DSS1 message digest (based on SHA-0 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = DSS1_bin("hello");
    @endcode

    @throw DSS1-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - DSS1()
    - digest()

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library
*/
binary DSS1_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    DigestHelper dh(args);
    if (dh.doDigest(DSS1_ERR, EVP_dss1(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MDC2">MDC2 message digest</a> of the supplied argument as a binary value
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MDC2

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = MDC2_bin("hello");
    @endcode

    @throw MDC2-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - MDC2_bin()
    - digest()
*/
binary MDC2_bin(data[doc] data) [flags=RET_VALUE_ONLY] {
#ifdef OPENSSL_HAVE_MDC2
    DigestHelper dh(args);
    if (dh.doDigest(MDC2_ERR, EVP_mdc2(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
#else
   return missing_openssl_feature("MDC2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/RIPEMD">RIPEMD message digest</a> of the supplied argument as a binary value
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return a binary value of the digest

    @par Example:
    @code{.py}
binary bin = RIPEMD160_bin("hello");
    @endcode

    @throw RIPEMD160-DIGEST-ERROR error calculating digest (should not normally happen)

    @see
    - RIPEMD160()
    - digest()
*/
binary RIPEMD160_binary(data[doc] data) [flags=RET_VALUE_ONLY] {
    DigestHelper dh(args);
    if (dh.doDigest(RIPEMD160_ERR, EVP_ripemd160(), xsink)) {
        return QoreValue();
    }

    return dh.getBinary();
}

///@}

/** @defgroup hmac_functions HMAC Functions
    %Qore's cryptography support is provided by the OpenSSL library.

    In cryptography, a keyed-hash message authentication code (HMAC) is
    a specific construction for calculating a message authentication code
    (MAC) involving a cryptographic hash function in combination with
    a secret cryptographic key. As with any MAC, it may be used
    to simultaneously verify both the data integrity and the authentication
    of a message. Any cryptographic hash function, such as MD5 or SHA-1,
    may be used in the calculation of an HMAC; the resulting MAC algorithm is
    termed HMAC-MD5 or HMAC-SHA1 accordingly. The cryptographic strength
    of the HMAC depends upon the cryptographic strength of the underlying
    hash function, the size of its hash output, and on the size and quality of the key.

    For more info: <a href="http://en.wikipedia.org/wiki/Hmac">Wikipedia's Hash-based message authentication code article</a>.

    See also:
        - @ref cryptographic_functions
        - @ref digest_functions
        - @ref cryptographic_constants
 */
///@{
//! Returns the <a href="http://en.wikipedia.org/wiki/MD2_(cryptography)">MD2</a> based HMAC of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_MD2

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "27f5f17500b408e97643403ea8ef1413")

    @par Example:
    @code{.py}
string str = MD2_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "27f5f17500b408e97643403ea8ef1413"
    @endcode

    @throw MD2-HMAC-ERROR error calculating digest (should not normally happen)

    @see
    - hmac()
    - MD2_bin()
 */
string MD2_hmac(data[doc] data, data key) {
#if !defined(OPENSSL_NO_MD2) && !defined(NO_MD2)
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("MD2-HMAC-ERROR", "md2", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("MD2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD4">MD4</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "053d084f321a3886e60166ebd9609ce1")

    @par Example:
    @code{.py}
string str = MD4_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "053d084f321a3886e60166ebd9609ce1"
    @endcode

    @throw MD4-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
 */
string MD4_hmac(data[doc] data, data key) {
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("MD4-HMAC-ERROR", "md4", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MD5">MD5</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "87505c6164aaf6ca6315233902a01ef4")

    @par Example:
    @code{.py}
string str = MD5_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "87505c6164aaf6ca6315233902a01ef4"
    @endcode

    @note the MD5 algorithm is not collision-resistant; it's recommended to use another hash algorithm (like SHA-256) if cryptographic security is important

    @throw MD5-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
 */
string MD5_hmac(data[doc] data, data key) {
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("MD5-HMAC-ERROR", "md5", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "0ad47c8d36dc4606d52f7e4cbd144ef2fda492a0")

    @par Example:
    @code{.py}
string str = SHA_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "0ad47c8d36dc4606d52f7e4cbd144ef2fda492a0"
    @endcode

    @note SHA/SHA0 was withdrawn shortly after publication due to an undisclosed "significant flaw" and replaced by the slightly revised version SHA-1.

    @throw SHA-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
 */
string SHA_hmac(data[doc] data, data key) {
#ifdef HAVE_OPENSSL_SHA
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA-HMAC-ERROR", "sha", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    missing_openssl_feature("SHA", xsink);
    return QoreValue();
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA1</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "37a3cc73159aa129b0eb22bbdf4b9309d389f629")

    @par Example:
    @code{.py}
string str = SHA1_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "37a3cc73159aa129b0eb22bbdf4b9309d389f629"
    @endcode

    @note Cryptographic weaknesses were discovered in SHA-1, and the standard was no longer approved for most cryptographic uses after 2010.

    @throw SHA1-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
 */
string SHA1_hmac(data[doc] data, data key) {
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA1-HMAC-ERROR", "sha1", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA224</a> based HMAC of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA224

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "fad5667fa5aa412044555b7e077fced62372fe9c6ce20815609da12c")

    @par Example:
    @code{.py}
string str = SHA224_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "fad5667fa5aa412044555b7e077fced62372fe9c6ce20815609da12c"
    @endcode

    @throw SHA224-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
 */
string SHA224_hmac(data[doc] data, data key) {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA224-HMAC-ERROR", "sha224", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("SHA224", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA256</a> based HMAC of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA256

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "1c90c21e227712b62019ff831f34cba22c2e70f1a902651ef69a70705ee0f754")

    @par Example:
    @code{.py}
string str = SHA256_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "1c90c21e227712b62019ff831f34cba22c2e70f1a902651ef69a70705ee0f754"
    @endcode

    @throw SHA256-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string SHA256_hmac(data[doc] data, data key) {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA256-HMAC-ERROR", "sha256", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("SHA256", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA384</a> based HMAC of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA384

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "e2c253c6dcb050990b4da3cee95cd7b227f43388fa8116f476f59395af295d0d3bb7156ab2fcd0663b0500249a7a0865")

    @par Example:
    @code{.py}
string str = SHA384_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "e2c253c6dcb050990b4da3cee95cd7b227f43388fa8116f476f59395af295d0d3bb7156ab2fcd0663b0500249a7a0865"
    @endcode

    @throw SHA384-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string SHA384_hmac(data[doc] data, data key) {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA384-HMAC-ERROR", "sha384", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("SHA384", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/Secure_Hash_Algorithm">SHA512</a> based HMAC of the supplied argument as a hex string
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_SHA512

    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "8dcefd7ea3f90ff1c822b5e9547fc36edf78c3e4ce13d47510a212a406bdda1a4094e7ea5ade90e1c736e204d331a814520eba49f3d074e2c261208de07264f6")

    @par Example:
    @code{.py}
string str = SHA512_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "8dcefd7ea3f90ff1c822b5e9547fc36edf78c3e4ce13d47510a212a406bdda1a4094e7ea5ade90e1c736e204d331a814520eba49f3d074e2c261208de07264f6"
    @endcode

    @throw SHA512-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string SHA512_hmac(data[doc] data, data key) {
#if !defined(OPENSSL_NO_SHA256) && defined(HAVE_OPENSSL_SHA512)
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("SHA512-HMAC-ERROR", "sha512", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("SHA512", xsink);
#endif
}

//! Returns the DSS (SHA-0 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "37a3cc73159aa129b0eb22bbdf4b9309d389f629")

    @par Example:
    @code{.py}
string str = DSS_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "37a3cc73159aa129b0eb22bbdf4b9309d389f629"
    @endcode

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library

    @throw DSS-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string DSS_hmac(data[doc] data, data key) {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC(DSS_ERR, "dss", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the DSS1 (SHA-1 and <a href="http://en.wikipedia.org/wiki/Digital_Signature_Algorithm">DSA</a>) based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "37a3cc73159aa129b0eb22bbdf4b9309d389f629")

    @par Example:
    @code{.py}
string str = DSS1_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "37a3cc73159aa129b0eb22bbdf4b9309d389f629"
    @endcode

    @note this digest algorithm is considered outdated and is included for backwards-compatibility only when %Qore is built with an older openssl library

    @throw DSS1-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string DSS1_hmac(data[doc] data, data key) {
#ifndef HAVE_OPENSSL_INIT_CRYPTO
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC(DSS1_ERR, "dss1", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    missing_openssl_feature("DSS", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/MDC2">MDC2</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "e0ef6a6803e58807c5db395e180a999c")

    @par Example:
    @code{.py}
string str = MDC2_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "e0ef6a6803e58807c5db395e180a999c"
    @endcode

    @throw MDC2-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string MDC2_hmac(data[doc] data, data key) {
#ifdef OPENSSL_HAVE_MDC2
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("MDC2-HMAC-ERROR", "mdc2", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
#else
    (void)key;
    return missing_openssl_feature("MDC2", xsink);
#endif
}

//! Returns the <a href="http://en.wikipedia.org/wiki/RIPEMD">RIPEMD</a> based HMAC of the supplied argument as a hex string
/**
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return a hex string of the digest (ex: \c "4bca70bca1601aba57624eeb2606535cb12f2079")

    @par Example:
    @code{.py}
string str = RIPEMD160_hmac("Hello There This is a Test - 1234567890", "a key");
# returns "4bca70bca1601aba57624eeb2606535cb12f2079"
    @endcode

    @throw RIPEMD160-HMAC-ERROR error calculating digest (should not normally happen)

    @see hmac()
*/
string RIPEMD160_hmac(data[doc] data, data key) {
    HMACHelper dh(args);
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);
    if (dh.doHMAC("RIPEMD160-HMAC-ERROR", "ripemd160", ptr, len, xsink)) {
        return QoreValue();
    }

    return dh.getString();
}

//! Returns the digest of the supplied arguments as a binary value
/** @par Example:
    @code{.py}
binary bin = digest("md2", "Hello There This is a Test - 1234567890");
# returns <a9046c73e00331af68917d3804f70655>
    @endcode

    @param digest the digest to use; for supported digests, see @ref Qore::DigestMap "DigestMap"
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments

    @return the binary value of the digest

    @throw DIGEST-ERROR unknown digest
    @throw DIGEST-CALCULATION-ERROR error calculating digest (should not normally happen)

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all digests known to the OpenSSL
    library, otherwise it tried to match the digest with known digests at compile time; use @ref DigestMap
    for a list of known digests

    @since %Qore 0.9.3
*/
binary digest(string digest, data data) [flags=RET_VALUE_ONLY] {
    const EVP_MD* md = q_get_digest(digest, xsink);
    if (!md) {
        return QoreValue();
    }

    DigestHelper dh(data);

    return dh.doDigest("DIGEST-CALCULATION-ERROR", md, xsink)
        ? QoreValue()
        : dh.getBinary();
}

//! Returns the digest-based HMAC of the supplied arguments as a binary value
/** @par Example:
    @code{.py}
binary bin = hmac("ripemd160", "Hello There This is a Test - 1234567890", "a key");
# returns <4bca70bca1601aba57624eeb2606535cb12f2079>
    @endcode

    @param digest the digest to use; for supported digests, see @ref Qore::DigestMap "DigestMap"
    @param data the data to process and produce a digest for; the trailing null character is not included in the digest when processing string arguments
    @param key a secret passphrase/key

    @return the binary value of the HMAC

    @throw DIGEST-ERROR unknown digest
    @throw HMAC-CALCULATION-ERROR error calculating digest (should not normally happen)

    @see @ref Qore::DigestMap "DigestMap"

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all digests known to the OpenSSL
    library, otherwise it tried to match the digest with known digests at compile time; use @ref DigestMap
    for a list of known digests

    @since %Qore 0.9.3
*/
binary hmac(string digest, data data, data key) [flags=RET_VALUE_ONLY] {
    HMACHelper dh(data);

    // get key data
    const char* ptr;
    size_t len;
    q_get_data(key, ptr, len);

    return dh.doHMAC("HMAC-CALCULATION-ERROR", digest->c_str(), ptr, len, xsink)
        ? QoreValue()
        : dh.getBinary();
}

//! Returns a list of supported digest algorithms for @ref hmac()
/** @par Example:
    @code{.py}
# make a map of supported digests
hash<string, bool> digest_map = map {$1: True}, get_digests();
    @endcode

    @return a list of supported digest algorithms for @ref hmac()

    @see hmac()

    @note This function returns the same value as the keys of @ref DigestMap

    @since %Qore 0.9.3
*/
list<string> get_digests() [flags=CONSTANT] {
    ReferenceHolder<QoreListNode> rv(new QoreListNode(stringTypeInfo), xsink);

    ReferenceHolder<QoreHashNode> h(init_digest_map_hash(), xsink);
    ConstHashIterator i(*h);
    while (i.next()) {
        rv->push(new QoreStringNode(i.getKey()), xsink);
    }

    return rv.release();
}

//! Returns a @ref Transform object for encrypting data using the given @ref crypto_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream
/** @par Example:
    @code{.cpp}
Qore::FileOutputStream of("my-file.txt.enc");
Qore::TransformOutputStream ts(of, get_encryptor(Qore::CRYPTO_ALG_BLOWFISH, key));
    @endcode

    @param alg the transformation algorithm; see @ref crypto_transformations for possible values
    @param key the encryption key
    @param iv the initialization vector
    @param mac a reference to a @ref binary_type "binary" lvalue that will store the <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> when the transformation is closed, if the algorithm produces one; it is not an error to provide a reference here for cryptographic algorithms that do not generate a MAC, however in such cases, nothing will be written to the reference; in case this argument is passed, the \a mac_size argument must also be passed or a \c MAC-ERROR exception will be thrown
    @param mac_size number of bytes of the MAC to return; this value must be non-negative and less than 4096 or a \c MAC-ERROR exception will be thrown
    @param aad <a href="https://www.cryptopp.com/wiki/ADATA">Additional Authenticated Data</a>; if the cryptographic algorithm does not support AAD, then this argument is ignored

    @return a @ref Transform object for encrypting data using the given @ref crypto_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream

    @throw MAC-ERROR this exception is thrown if the \a mac_size argument is passed but no \a mac argument or vice-versa or if the \a mac_size argument is negative or greater than 4096 bytes
    @throw ENCRYPT-ERROR unknown encryption algorithm; error with the encryption algorithm (ex: invalid key size)

    @see
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_crypto_info()
    - @ref Qore::get_decryptor()

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @since %Qore 0.8.13
 */
Transform get_encryptor(string alg, data key, data iv = Qore::DefaultIV, *reference<binary> mac, *int mac_size, *data aad) [flags=RET_VALUE_ONLY] {
    const char* keystr;
    size_t key_len;
    q_get_data(key, keystr, key_len);

    const char* ivstr;
    size_t iv_len;
    q_get_data(iv, ivstr, iv_len);

    if (check_mac(mac, mac_size, xsink))
        return QoreValue();

    const char* aadstr = nullptr;
    size_t aad_len = 0;
    if (aad)
        q_get_data(aad, aadstr, aad_len);

    SimpleRefHolder<Transform> t(EncryptionTransforms::getCryptoTransform(alg->c_str(), true, keystr, (unsigned)key_len, ivstr, (unsigned)iv_len, nullptr, 0, (unsigned)mac_size, mac, aadstr, (unsigned)aad_len, xsink));
    return *xsink ? nullptr : new QoreObject(QC_TRANSFORM, getProgram(), t.release());
}

//! Returns a @ref Transform object for decrypting data using the given @ref crypto_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream
/** @par Example:
    @code{.cpp}
Qore::FileOutputStream of("my-file.txt.enc");
Qore::TransformOutputStream ts(of, get_decryptor(Qore::CRYPTO_ALG_BLOWFISH, key));
    @endcode

    @param alg the transformation algorithm; see @ref crypto_transformations for possible values
    @param key the encryption key
    @param iv the initialization vector
    @param mac a message authentication code to be used to decrypt the message if required by the cryptographic algorithm, if not required, this argument is ignored
    @param aad <a href="https://www.cryptopp.com/wiki/ADATA">Additional Authenticated Data</a>; if the cryptographic algorithm does not support AAD, then this argument is ignored

    @return a @ref Transform object for decrypting data using the given @ref crypto_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream

    @throw DECRYPT-ERROR unknown encryption algorithm; error decrypting the data

    @see
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_crypto_info()
    - @ref Qore::get_encryptor()

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @since %Qore 0.8.13
 */
Transform get_decryptor(string alg, data key, data iv = Qore::DefaultIV, *data mac, *data aad) [flags=RET_VALUE_ONLY] {
    const char* keystr;
    size_t key_len;
    q_get_data(key, keystr, key_len);

    const char* ivstr;
    size_t iv_len;
    q_get_data(iv, ivstr, iv_len);

    const char* macstr = nullptr;
    size_t mac_len = 0;
    if (mac)
        q_get_data(mac, macstr, mac_len);

    const char* aadstr = nullptr;
    size_t aad_len = 0;
    if (aad)
        q_get_data(aad, aadstr, aad_len);

    SimpleRefHolder<Transform> t(EncryptionTransforms::getCryptoTransform(alg->c_str(), false, keystr, (unsigned)key_len, ivstr, (unsigned)iv_len, macstr, (unsigned)mac_len, 0, nullptr, aadstr, (unsigned)aad_len, xsink));
    return *xsink ? nullptr : new QoreObject(QC_TRANSFORM, getProgram(), t.release());
}

//! Returns a binary value of encrypted data corresponding to the input arguments inluding the given @ref crypto_transformations "algorithm"
/** @par Example:
    @code{.py}
binary bin = encrypt(Qore::CRYPTO_ALG_BLOWFISH, data, key);
    @endcode

    @param alg the transformation algorithm; see @ref crypto_transformations for possible values
    @param data the data to encrypt
    @param key the encryption key
    @param iv the initialization vector or cryptographic nonce
    @param mac a reference to a @ref binary_type "binary" lvalue that will store the <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> when the transformation is closed, if the algorithm produces one; it is not an error to provide a reference here for cryptographic algorithms that do not generate a MAC, however in such cases, nothing will be written to the reference; in case this argument is passed, the \a mac_size argument must also be passed or a \c MAC-ERROR exception will be thrown
    @param mac_size number of bytes of the MAC to return; this value must be non-negative and less than 4096 or a \c MAC-ERROR exception will be thrown
    @param aad <a href="https://www.cryptopp.com/wiki/ADATA">Additional Authenticated Data</a>; if the cryptographic algorithm does not support AAD, then this argument is ignored

    @return a binary value of encrypted data corresponding to the input arguments

    @throw MAC-ERROR this exception is thrown if the \a mac_size argument is passed but no \a mac argument or vice-versa or if the \a mac_size argument is negative or greater than 4096 bytes
    @throw ENCRYPT-ERROR unknown encryption algorithm; error with the encryption algorithm (ex: invalid key size)

    @see
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::get_crypto_info()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @since %Qore 0.8.13
 */
binary encrypt(string alg, data data, data key, data iv = Qore::DefaultIV, *reference<binary> mac, *int mac_size, *data aad) [flags=RET_VALUE_ONLY] {
    const char* datastr;
    size_t data_len;
    q_get_data(data, datastr, data_len);

    const char* keystr;
    size_t key_len;
    q_get_data(key, keystr, key_len);

    const char* ivstr;
    size_t iv_len;
    q_get_data(iv, ivstr, iv_len);

    if (check_mac(mac, mac_size, xsink)) {
        return QoreValue();
    }

    const char* aadstr = nullptr;
    size_t aad_len = 0;
    if (aad) {
        q_get_data(aad, aadstr, aad_len);
    }

    SimpleRefHolder<Transform> t(EncryptionTransforms::getCryptoTransform(alg->c_str(), true, keystr,
            (unsigned)key_len, ivstr, (unsigned)iv_len, nullptr, 0, (unsigned)mac_size, mac, aadstr,
            (unsigned)aad_len, xsink));
    if (*xsink) {
        return QoreValue();
    }

    SimpleRefHolder<BinaryNode> rv(new BinaryNode);
    return apply_transformation(t, rv, datastr, data_len, xsink);
}

//! Returns a binary value of decrypted data corresponding to the input arguments inluding the given @ref crypto_transformations "algorithm"
/** @par Example:
    @code{.py}
binary bin = decrypt_to_binary(Qore::CRYPTO_ALG_BLOWFISH, data, key);
    @endcode

    @param alg the transformation algorithm; see @ref crypto_transformations for possible values
    @param data the data to decrypt
    @param key the encryption key
    @param iv the initialization vector or cryptographic nonce
    @param mac a message authentication code to be used to decrypt the message if required by the cryptographic algorithm, if not required, this argument is ignored
    @param aad <a href="https://www.cryptopp.com/wiki/ADATA">Additional Authenticated Data</a>; if the cryptographic algorithm does not support AAD, then this argument is ignored

    @return a binary value of decrypted data corresponding to the input arguments

    @throw DECRYPT-ERROR unknown decryption algorithm; error with the decryption algorithm (ex: invalid key)

    @see
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_crypto_info()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @since %Qore 0.8.13
 */
binary decrypt_to_binary(string alg, data data, data key, data iv = Qore::DefaultIV, *data mac, *data aad) [flags=RET_VALUE_ONLY] {
    const char* datastr;
    size_t data_len;
    q_get_data(data, datastr, data_len);

    const char* keystr;
    size_t key_len;
    q_get_data(key, keystr, key_len);

    const char* ivstr;
    size_t iv_len;
    q_get_data(iv, ivstr, iv_len);

    const char* macstr = nullptr;
    size_t mac_len = 0;
    if (mac)
        q_get_data(mac, macstr, mac_len);

    const char* aadstr = nullptr;
    size_t aad_len = 0;
    if (aad)
        q_get_data(aad, aadstr, aad_len);

    SimpleRefHolder<Transform> t(EncryptionTransforms::getCryptoTransform(alg->c_str(), false, keystr, (unsigned)key_len, ivstr, (unsigned)iv_len, macstr, (unsigned)mac_len, 0, nullptr, aadstr, (unsigned)aad_len, xsink));
    if (*xsink)
        return QoreValue();

    SimpleRefHolder<BinaryNode> rv(new BinaryNode);
    return apply_transformation(t, rv, datastr, data_len, xsink);
}

//! Returns a string of decrypted data corresponding to the input arguments inluding the given @ref crypto_transformations "algorithm"
/** @par Example:
    @code{.py}
string str = decrypt_to_string(Qore::CRYPTO_ALG_BLOWFISH, data, key);
    @endcode

    @param alg the transformation algorithm; see @ref crypto_transformations for possible values
    @param data the data to decrypt
    @param key the encryption key
    @param iv the initialization vector or cryptographic nonce
    @param mac a message authentication code to be used to decrypt the message if required by the cryptographic algorithm, if not required, this argument is ignored
    @param aad <a href="https://www.cryptopp.com/wiki/ADATA">Additional Authenticated Data</a>; if the cryptographic algorithm does not support AAD, then this argument is ignored
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string of decrypted data corresponding to the input arguments

    @throw DECRYPT-ERROR unknown decryption algorithm; error with the decryption algorithm (ex: invalid key)

    @see
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::encrypt()
    - @ref Qore::get_crypto_info()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @since %Qore 0.8.13
 */
string decrypt_to_string(string alg, data data, data key, data iv = Qore::DefaultIV, *data mac, *data aad, *string encoding) [flags=RET_VALUE_ONLY] {
    const char* datastr;
    size_t data_len;
    q_get_data(data, datastr, data_len);

    const char* keystr;
    size_t key_len;
    q_get_data(key, keystr, key_len);

    const char* ivstr;
    size_t iv_len;
    q_get_data(iv, ivstr, iv_len);

    const char* macstr = nullptr;
    size_t mac_len = 0;
    if (mac)
        q_get_data(mac, macstr, mac_len);

    const char* aadstr = nullptr;
    size_t aad_len = 0;
    if (aad)
        q_get_data(aad, aadstr, aad_len);

    SimpleRefHolder<Transform> t(EncryptionTransforms::getCryptoTransform(alg->c_str(), false, keystr, (unsigned)key_len, ivstr, (unsigned)iv_len, macstr, (unsigned)mac_len, 0, nullptr, aadstr, (unsigned)aad_len, xsink));
    if (*xsink)
        return QoreValue();

    SimpleRefHolder<BinaryNode> bin(new BinaryNode);
    ValueHolder vh(apply_transformation(t, bin, datastr, data_len, xsink), xsink);
    if (*xsink)
        return QoreValue();

    const QoreEncoding* enc = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;

    size_t size = vh->get<BinaryNode>()->size();
    // create string with len == allocated to ensure that it gets terminated
    return size ? new QoreStringNode((char*)vh->get<BinaryNode>()->giveBuffer(), size, size, enc) : new QoreStringNode(enc);
}

//! Returns a hash of information about each cryptographic algorithmn supported by the current build of %Qore
/** @par Example:
    @code{.py}
hash<auto> h = get_crypto_info();
    @endcode

    @return a hash of information about each cryptographic algorithmn supported by the current build of %Qore; keys
    are algorithm names, values are hashes with the following keys:
    - \c key_len: the minimum required length of the cryptographic key; if 0 it means that the cipher takes a variable
      key length
    - \c iv_len: the minimum required lengh of the initialization vector or nonce; if 0 it means that the
      initialization vector can be of any length (note that initialization vector or nonce is always optional); -1
      means that the algorithm does not support (ignores) initialization vectors
    - \c type: either \c "GCM" for <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode">Galois Counter Mode</a>
      (meaning that the cipher accepts AAD and produces / requires a
      <a href="https://en.wikipedia.org/wiki/Message_authentication_code">Message Authentication Code (MAC)</a> for
      encryption / decryption, respectively) or \c "CBC" for
      <a href="http://en.wikipedia.org/wiki/Cipher_block_chaining#Cipher-block_chaining_.28CBC.29">Cipher Block Chaining</a>
      (meaning that the cipher does not support AAD or a MAC)

    @see
    - @ref Qore::decrypt_to_binary()
    - @ref Qore::decrypt_to_string()
    - @ref Qore::encrypt()
    - @ref Qore::get_decryptor()
    - @ref Qore::get_encryptor()

    @since %Qore 0.8.13
 */
hash<auto> get_crypto_info() [flags=CONSTANT] {
#ifdef OPENSSL_3_PLUS
    return init_cipher_map_hash();
#else
    ReferenceHolder<QoreHashNode> rv(new QoreHashNode(autoTypeInfo), xsink);

    for (auto& i : crypto_map) {
        rv->setKeyValue(i.first.c_str(), i.second.getInfo(), nullptr);
    }

    return rv.release();
#endif
}

//! Returns information about the given cipher or @ref NOTHING if the cipher is unknown
/** @param cipher the cipher to look up

    @return a hash with information about the cipher or @ref NOTHING if the cipher is unknown

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all ciphers known to the OpenSSL
    library, otherwise it tried to match the cipher with known ciphers at compile time; use @ref CipherMap
    for a list of known ciphers

    @throw ENCODING-CONVERSION-ERROR this error is thrown if the string cannot be converted to the
    @ref default_encoding "default character encoding"

    @since %Qore 1.4.0
*/
*hash<auto> get_cipher_info(string cipher) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper temp(cipher, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

#ifdef OPENSSL_3_PLUS
    const EVP_CIPHER* c = q_lookup_cipher(temp->c_str());
    if (!c) {
        return QoreValue();
    }
    return q_get_cipher_hash(c);
#else
    crypto_map_t::const_iterator i = crypto_map.find(temp->c_str());
    if (i == crypto_map.end()) {
        return QoreValue();
    }
    return i->second.getInfo();
#endif
}

//! Returns @ref True if the given digest is supported, @ref False if not
/** @param digest the digest to check

    @return @ref True if the given digest is supported, @ref False if not

    @note If %Qore is compiled with OpenSSL 3+, it will use dynamic lookups of all digests known to the OpenSSL
    library, otherwise it tried to match the digest with known digests at compile time; use @ref DigestMap
    for a list of known digests

    @throw ENCODING-CONVERSION-ERROR this error is thrown if the string cannot be converted to the
    @ref default_encoding "default character encoding"

    @since %Qore 1.4.0
*/
bool has_digest(string digest) [flags=RET_VALUE_ONLY] {
    TempEncodingHelper temp(digest, QCS_DEFAULT, xsink);
    if (*xsink) {
        return QoreValue();
    }

    return q_lookup_digest(temp->c_str()) ? true : false;
}

//! returns a binary value of random bytes using openssl
/** @par Example:
    @code{.py}
# return 16 bytes of random data
binary b = get_random_bytes(16);
    @endcode

    @param size the number of bytes in the output; if this value is not positive, an empty binary value is returned

    @return a binary value of random bytes using openssl

    @throw GET-RANDOM-BYTES-ERROR thrown if there is an error generating or acquiring random data (ex: not enough
    entropy; random function not supported by the current random method)

    @since %Qore 0.8.13
*/
binary get_random_bytes(int size) [flags=RET_VALUE_ONLY] {
    SimpleRefHolder<BinaryNode> rv(new BinaryNode);
    if (size > 0) {
        rv->preallocate(size);
        int rc = RAND_bytes((unsigned char*)rv->getPtr(), size);
        if (rc <= 0) {
            unsigned long err = ERR_get_error();
            char buf[121];
            ERR_error_string(err, buf);
            xsink->raiseException("GET-RANDOM-BYTES-ERROR", "the RAND_bytes() returned error code %d with a request "
                "of length %d: %s", rc, (int)size, buf);
            return QoreValue();
        }
    }
    return rv.release();
}
///@}
