diff options
author | Daniel Veillard <veillard@src.gnome.org> | 2004-06-30 16:43:53 +0000 |
---|---|---|
committer | Daniel Veillard <veillard@src.gnome.org> | 2004-06-30 16:43:53 +0000 |
commit | 1bc6a1a495c2acec0fa2c46783a4dc0adefc1860 (patch) | |
tree | 803d776b536c44194efbb353452da2652334ad2d /libexslt/crypto.c | |
parent | 19739dbd5fd3449b0cdae8984f3dd8b787d18ad2 (diff) | |
download | libxslt-1bc6a1a495c2acec0fa2c46783a4dc0adefc1860.tar.gz libxslt-1bc6a1a495c2acec0fa2c46783a4dc0adefc1860.tar.bz2 libxslt-1bc6a1a495c2acec0fa2c46783a4dc0adefc1860.zip |
applied patch from Joel Reed to get EXSLT crypto extensions based on
* win32/Makefile.msvc win32/Makefile.mingw libexslt/Makefile.am
libexslt/exslt.c libexslt/exslt.h config.h.in configure.in
libexslt/crypto.c: applied patch from Joel Reed to get EXSLT
crypto extensions based on libgcrypt if found at configure time.
* tests/namespaces/Makefile.am: fixed a small breakage
Daniel
Diffstat (limited to 'libexslt/crypto.c')
-rw-r--r-- | libexslt/crypto.c | 714 |
1 files changed, 714 insertions, 0 deletions
diff --git a/libexslt/crypto.c b/libexslt/crypto.c new file mode 100644 index 00000000..218e38a0 --- /dev/null +++ b/libexslt/crypto.c @@ -0,0 +1,714 @@ +#define IN_LIBEXSLT +#include "libexslt/libexslt.h" + +#if defined(WIN32) && !defined (__CYGWIN__) && (!__MINGW32__) +#include <win32config.h> +#else +#include "config.h" +#endif + +#include <libxml/tree.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> +#include <libxml/parser.h> +#include <libxml/encoding.h> +#include <libxml/uri.h> + +#include <libxslt/xsltconfig.h> +#include <libxslt/xsltutils.h> +#include <libxslt/xsltInternals.h> +#include <libxslt/extensions.h> + +#include "exslt.h" + +#define HASH_DIGEST_LENGTH 32 +#define MD5_DIGEST_LENGTH 16 +#define SHA1_DIGEST_LENGTH 20 + +/* gcrypt rc4 can do 256 bit keys, but cryptoapi limit + seems to be 128 for the default provider */ +#define RC4_KEY_LENGTH 128 + +/** + * exsltCryptoBin2Hex: + * @bin: binary blob to convert + * @binlen: length of binary blob + * @hex: buffer to store hex version of blob + * @hexlen: length of buffer to store hex version of blob + * + * Helper function which encodes a binary blob as hex. + */ +void exsltCryptoBin2Hex(const unsigned char* bin, int binlen, + unsigned char* hex, int hexlen) +{ + static const char bin2hex[] = { '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' }; + + unsigned char lo, hi; + int i, pos; + for (i = 0, pos = 0; (i < binlen && pos < hexlen); i++) + { + lo = bin[i] & 0xf; + hi = bin[i] >> 4; + hex[pos++] = bin2hex[hi]; + hex[pos++] = bin2hex[lo]; + } + + hex[pos] = '\0'; +} + +/** + * exsltCryptoHex2Bin: + * @hex: hex version of blob to convert + * @hexlen: length of hex buffer + * @bin: destination binary buffer + * @binlen: length of binary buffer + * + * Helper function which decodes a hex blob to binary + */ +int exsltCryptoHex2Bin(const unsigned char* hex, int hexlen, + unsigned char* bin, int binlen) +{ + int i = 0, j = 0; + unsigned char lo, hi, result, tmp; + + while (i<hexlen && j<binlen) + { + hi = lo = 0; + + tmp = hex[i++]; + if (tmp >= '0' && tmp <= '9') hi = tmp - '0'; + else if (tmp >= 'a' && tmp <= 'f') hi = 10 + (tmp - 'a'); + + tmp = hex[i++]; + if (tmp >= '0' && tmp <= '9') lo = tmp - '0'; + else if (tmp >= 'a' && tmp <= 'f') lo = 10 + (tmp - 'a'); + + result = hi << 4; + result += lo; + bin[j++] = result; + } + + return j; +} + +#if defined(WIN32) + +#define HAVE_CRYPTO +#define PLATFORM_HASH exsltCryptoCryptoApiHash +#define PLATFORM_RC4_ENCRYPT exsltCryptoCryptoApiRc4Encrypt +#define PLATFORM_RC4_DECRYPT exsltCryptoCryptoApiRc4Decrypt +#define PLATFORM_MD4 CALG_MD4 +#define PLATFORM_MD5 CALG_MD5 +#define PLATFORM_SHA1 CALG_SHA1 + +#include <windows.h> +#include <wincrypt.h> +#pragma comment(lib, "advapi32.lib") + +void exsltCryptoCryptoApiReportError(xmlXPathParserContextPtr ctxt, int line) +{ + LPVOID lpMsgBuf; + DWORD dw = GetLastError(); + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, 0, NULL ); + + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto error (line %d). %s", line, lpMsgBuf); + LocalFree(lpMsgBuf); +} + +HCRYPTHASH exsltCryptoCryptoApiCreateHash(xmlXPathParserContextPtr ctxt, + HCRYPTPROV hCryptProv, ALG_ID algorithm, + const char* msg, unsigned int msglen, + char* dest, unsigned int destlen) +{ + HCRYPTHASH hHash = 0; + DWORD dwHashLen = destlen; + + if (!CryptCreateHash(hCryptProv, algorithm, 0, 0, &hHash)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + return 0; + } + + if(!CryptHashData(hHash, (const BYTE*)msg, msglen, 0)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + + if (!CryptGetHashParam(hHash, HP_HASHVAL, dest, &dwHashLen, 0)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + +fail: + return hHash; +} + +/** + * exsltCryptoCryptoApiHash: + * @ctxt: an XPath parser context + * @algorithm: hashing algorithm to use + * @msg: text to be hashed + * @msglen: length of text to be hashed + * @dest: buffer to place hash result + * + * Helper function which hashes a message using MD4, MD5, or SHA1. + * Uses Win32 CryptoAPI. + */ +void exsltCryptoCryptoApiHash(xmlXPathParserContextPtr ctxt, ALG_ID algorithm, + const char* msg, unsigned long msglen, char dest[HASH_DIGEST_LENGTH]) +{ + HCRYPTPROV hCryptProv; + HCRYPTHASH hHash; + + if(! CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + return; + } + + hHash = exsltCryptoCryptoApiCreateHash(ctxt, hCryptProv, + algorithm, msg, msglen, + dest, HASH_DIGEST_LENGTH); + if (0 != hHash) + { + CryptDestroyHash(hHash); + } + + CryptReleaseContext(hCryptProv, 0); +} + +void exsltCryptoCryptoApiRc4Encrypt(xmlXPathParserContextPtr ctxt, + const unsigned char* key, + const unsigned char* msg, int msglen, + unsigned char* dest, int destlen) +{ + HCRYPTPROV hCryptProv; + HCRYPTKEY hKey; + HCRYPTHASH hHash; + DWORD dwDataLen; + unsigned char hash[HASH_DIGEST_LENGTH]; + + if (msglen > destlen) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto : internal error exsltCryptoCryptoApiRc4Encrypt dest buffer too small.\n"); + return; + } + + if(! CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + return; + } + + hHash = exsltCryptoCryptoApiCreateHash(ctxt, hCryptProv, + CALG_SHA1, key, RC4_KEY_LENGTH, + hash, HASH_DIGEST_LENGTH); + + if (!CryptDeriveKey(hCryptProv, CALG_RC4, hHash, 0x00800000, &hKey)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + +// Now encrypt data. + dwDataLen = msglen; + memcpy(dest, msg, msglen); + if (!CryptEncrypt(hKey, 0, TRUE, 0, dest, &dwDataLen, msglen)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + +fail: + if (0 != hHash) + { + CryptDestroyHash(hHash); + } + + CryptDestroyKey(hKey); + CryptReleaseContext(hCryptProv, 0); +} + +void exsltCryptoCryptoApiRc4Decrypt(xmlXPathParserContextPtr ctxt, + const unsigned char* key, + const unsigned char* msg, int msglen, + unsigned char* dest, int destlen) +{ + HCRYPTPROV hCryptProv; + HCRYPTKEY hKey; + HCRYPTHASH hHash; + DWORD dwDataLen; + unsigned char hash[HASH_DIGEST_LENGTH]; + + if (msglen > destlen) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto : internal error exsltCryptoCryptoApiRc4Encrypt dest buffer too small.\n"); + return; + } + + if(! CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + return; + } + + hHash = exsltCryptoCryptoApiCreateHash(ctxt, hCryptProv, + CALG_SHA1, key, RC4_KEY_LENGTH, + hash, HASH_DIGEST_LENGTH); + + if (!CryptDeriveKey(hCryptProv, CALG_RC4, hHash, 0x00800000, &hKey)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + +// Now encrypt data. + dwDataLen = msglen; + memcpy(dest, msg, msglen); + if (!CryptDecrypt(hKey, 0, TRUE, 0, dest, &dwDataLen)) + { + exsltCryptoCryptoApiReportError(ctxt, __LINE__); + goto fail; + } + +fail: + if (0 != hHash) + { + CryptDestroyHash(hHash); + } + + CryptDestroyKey(hKey); + CryptReleaseContext(hCryptProv, 0); +} + +#endif /* defined(WIN32) */ + +#if defined(HAVE_GCRYPT) + +#define HAVE_CRYPTO +#define PLATFORM_HASH exsltCryptoGcryptHash +#define PLATFORM_RC4_ENCRYPT exsltCryptoGcryptRc4Encrypt +#define PLATFORM_RC4_DECRYPT exsltCryptoGcryptRc4Decrypt +#define PLATFORM_MD4 GCRY_MD_MD4 +#define PLATFORM_MD5 GCRY_MD_MD5 +#define PLATFORM_SHA1 GCRY_MD_SHA1 + +#include <gcrypt.h> + +void exsltCryptoGcryptInit (void) +{ + static int gcrypt_init; + xmlLockLibrary(); + + if (! gcrypt_init) + { +/* The function `gcry_check_version' must be called before any other + function in the library, because it initializes the thread support + subsystem in Libgcrypt. To achieve this in all generality, it is + necessary to synchronize the call to this function with all other calls + to functions in the library, using the synchronization mechanisms + available in your thread library. (from gcrypt.info) +*/ + gcry_check_version(GCRYPT_VERSION); + gcrypt_init=1; + } + + xmlUnlockLibrary(); +} + +/** + * exsltCryptoGcryptHash: + * @ctxt: an XPath parser context + * @algorithm: hashing algorithm to use + * @msg: text to be hashed + * @msglen: length of text to be hashed + * @dest: buffer to place hash result + * + * Helper function which hashes a message using MD4, MD5, or SHA1. + * using gcrypt + */ +void exsltCryptoGcryptHash(xmlXPathParserContextPtr ctxt, enum gcry_md_algos algorithm, + const char* msg, unsigned long msglen, char dest[HASH_DIGEST_LENGTH]) +{ + exsltCryptoGcryptInit(); + gcry_md_hash_buffer(algorithm, dest, msg, msglen); +} + +void exsltCryptoGcryptRc4Encrypt(xmlXPathParserContextPtr ctxt, + const unsigned char* key, + const unsigned char* msg, int msglen, + unsigned char* dest, int destlen) +{ + gcry_cipher_hd_t cipher; + gcry_error_t rc = 0; + + exsltCryptoGcryptInit(); + + rc = gcry_cipher_open(&cipher, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_open)\n", + gcry_strerror(rc)); + } + + rc = gcry_cipher_setkey(cipher, key, RC4_KEY_LENGTH); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_setkey)\n", + gcry_strerror(rc)); + } + + rc = gcry_cipher_encrypt(cipher, (unsigned char*)dest, destlen, + (const unsigned char*)msg, msglen); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_encrypt)\n", + gcry_strerror(rc)); + } + + gcry_cipher_close(cipher); +} + +void exsltCryptoGcryptRc4Decrypt(xmlXPathParserContextPtr ctxt, + const unsigned char* key, + const unsigned char* msg, int msglen, + unsigned char* dest, int destlen) +{ + gcry_cipher_hd_t cipher; + gcry_error_t rc = 0; + + exsltCryptoGcryptInit(); + + rc = gcry_cipher_open(&cipher, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_open)\n", + gcry_strerror(rc)); + } + + rc = gcry_cipher_setkey(cipher, key, RC4_KEY_LENGTH); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_setkey)\n", + gcry_strerror(rc)); + } + + rc = gcry_cipher_decrypt(cipher, (unsigned char*)dest, destlen, + (const unsigned char*)msg, msglen); + if (rc) + { + xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL, + "exslt:crypto internal error %s (gcry_cipher_decrypt)\n", + gcry_strerror(rc)); + } + + gcry_cipher_close(cipher); +} + +#endif /* defined(HAVE_GCRYPT) */ + +#if defined(HAVE_CRYPTO) + +/** + * exsltCryptoPopString: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * Helper function which checks for and returns first string argument and its length + */ +static int +exsltCryptoPopString (xmlXPathParserContextPtr ctxt, int nargs, xmlChar **str) { + + int str_len = 0; + + if ((nargs < 1) || (nargs > 2)) { + xmlXPathSetArityError(ctxt); + return 0; + } + + *str = xmlXPathPopString(ctxt); + str_len = xmlUTF8Strlen(*str); + + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(*str); + return 0; + } + + return str_len; +} + +/** + * exsltCryptoMd4Function: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * computes the md4 hash of a string and returns as hex + */ +static void +exsltCryptoMd4Function (xmlXPathParserContextPtr ctxt, int nargs) { + + int str_len = 0; + xmlChar *str = NULL, *ret = NULL; + unsigned char hash[HASH_DIGEST_LENGTH]; + unsigned char hex[MD5_DIGEST_LENGTH*2+1]; + + str_len = exsltCryptoPopString(ctxt, nargs, &str); + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(str); + return; + } + + PLATFORM_HASH(ctxt, PLATFORM_MD4, str, str_len, hash); + exsltCryptoBin2Hex(hash, sizeof(hash)-1, hex, sizeof(hex)-1); + + ret = xmlStrdup((xmlChar *)hex); + xmlXPathReturnString(ctxt, ret); + + if (str != NULL) + xmlFree(str); +} + +/** + * exsltCryptoMd5Function: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * computes the md5 hash of a string and returns as hex + */ +static void +exsltCryptoMd5Function (xmlXPathParserContextPtr ctxt, int nargs) { + + int str_len = 0; + xmlChar *str = NULL, *ret = NULL; + unsigned char hash[HASH_DIGEST_LENGTH]; + unsigned char hex[MD5_DIGEST_LENGTH*2+1]; + + str_len = exsltCryptoPopString(ctxt, nargs, &str); + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(str); + return; + } + + PLATFORM_HASH(ctxt, PLATFORM_MD5, str, str_len, hash); + exsltCryptoBin2Hex(hash, sizeof(hash)-1, hex, sizeof(hex)-1); + + ret = xmlStrdup((xmlChar *)hex); + xmlXPathReturnString(ctxt, ret); + + if (str != NULL) + xmlFree(str); +} + +/** + * exsltCryptoSha1Function: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * computes the sha1 hash of a string and returns as hex + */ +static void +exsltCryptoSha1Function (xmlXPathParserContextPtr ctxt, int nargs) { + + int str_len = 0; + xmlChar *str = NULL, *ret = NULL; + unsigned char hash[HASH_DIGEST_LENGTH]; + unsigned char hex[SHA1_DIGEST_LENGTH*2+1]; + + str_len = exsltCryptoPopString(ctxt, nargs, &str); + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(str); + return; + } + + PLATFORM_HASH(ctxt, PLATFORM_SHA1, str, str_len, hash); + exsltCryptoBin2Hex(hash, sizeof(hash)-1, hex, sizeof(hex)-1); + + ret = xmlStrdup((xmlChar *)hex); + xmlXPathReturnString(ctxt, ret); + + if (str != NULL) + xmlFree(str); +} + +/** + * exsltCryptoRc4EncryptFunction: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * computes the sha1 hash of a string and returns as hex + */ +static void +exsltCryptoRc4EncryptFunction (xmlXPathParserContextPtr ctxt, int nargs) { + + int key_len = 0, key_size = 0; + int str_len = 0, bin_len = 0, hex_len = 0; + xmlChar *key = NULL, *str = NULL, *padkey = NULL; + xmlChar *bin = NULL, *hex = NULL; + + if ((nargs < 1) || (nargs > 3)) { + xmlXPathSetArityError(ctxt); + return; + } + + str = xmlXPathPopString(ctxt); + str_len = xmlUTF8Strlen(str); + + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(str); + return; + } + + key = xmlXPathPopString(ctxt); + key_len = xmlUTF8Strlen(str); + + if (key_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(key); + xmlFree(str); + return; + } + + padkey = xmlMallocAtomic(RC4_KEY_LENGTH); + key_size = xmlUTF8Strsize(key, key_len); + memcpy(padkey, key, key_size); + memset(padkey+key_size, '\0', sizeof(padkey)); + +/* encrypt it */ + bin_len = str_len; + bin = xmlStrdup(str); + PLATFORM_RC4_ENCRYPT(ctxt, padkey, str, str_len, bin, bin_len); + +/* encode it */ + hex_len = str_len*2+1; + hex = xmlMallocAtomic(hex_len); + + exsltCryptoBin2Hex(bin, str_len, hex, hex_len); + xmlXPathReturnString(ctxt, hex); + + if (key != NULL) + xmlFree(key); + if (str != NULL) + xmlFree(str); + if (padkey != NULL) + xmlFree(padkey); + if (bin != NULL) + xmlFree(bin); +} + +/** + * exsltCryptoRc4DecryptFunction: + * @ctxt: an XPath parser context + * @nargs: the number of arguments + * + * computes the sha1 hash of a string and returns as hex + */ +static void +exsltCryptoRc4DecryptFunction (xmlXPathParserContextPtr ctxt, int nargs) { + + int key_len = 0, key_size = 0; + int str_len = 0, bin_len = 0, ret_len = 0; + xmlChar *key = NULL, *str = NULL, *padkey = NULL, *bin = NULL, *ret = NULL; + + if ((nargs < 1) || (nargs > 3)) { + xmlXPathSetArityError(ctxt); + return; + } + + str = xmlXPathPopString(ctxt); + str_len = xmlUTF8Strlen(str); + + if (str_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(str); + return; + } + + key = xmlXPathPopString(ctxt); + key_len = xmlUTF8Strlen(str); + + if (key_len == 0) { + xmlXPathReturnEmptyString(ctxt); + xmlFree(key); + xmlFree(str); + return; + } + + padkey = xmlMallocAtomic(RC4_KEY_LENGTH); + key_size = xmlUTF8Strsize(key, key_len); + memcpy(padkey, key, key_size); + memset(padkey+key_size, '\0', sizeof(padkey)); + +/* decode hex to binary */ + bin_len = str_len; + bin = xmlMallocAtomic(bin_len); + ret_len = exsltCryptoHex2Bin(str, str_len, bin, bin_len); + +/* decrypt the binary blob */ + ret = xmlMallocAtomic(ret_len); + PLATFORM_RC4_DECRYPT(ctxt, padkey, bin, ret_len, ret, ret_len); + + xmlXPathReturnString(ctxt, ret); + + if (key != NULL) + xmlFree(key); + if (str != NULL) + xmlFree(str); + if (padkey != NULL) + xmlFree(padkey); + if (bin != NULL) + xmlFree(bin); +} + +/** + * exsltCryptoRegister: + * + * Registers the EXSLT - Crypto module + */ + +void +exsltCryptoRegister (void) { + xsltRegisterExtModuleFunction ((const xmlChar *) "md4", + EXSLT_CRYPTO_NAMESPACE, + exsltCryptoMd4Function); + xsltRegisterExtModuleFunction ((const xmlChar *) "md5", + EXSLT_CRYPTO_NAMESPACE, + exsltCryptoMd5Function); + xsltRegisterExtModuleFunction ((const xmlChar *) "sha1", + EXSLT_CRYPTO_NAMESPACE, + exsltCryptoSha1Function); + xsltRegisterExtModuleFunction ((const xmlChar *) "rc4_encrypt", + EXSLT_CRYPTO_NAMESPACE, + exsltCryptoRc4EncryptFunction); + xsltRegisterExtModuleFunction ((const xmlChar *) "rc4_decrypt", + EXSLT_CRYPTO_NAMESPACE, + exsltCryptoRc4DecryptFunction); +} + +#else +void exsltCryptoRegister (void) { } + +#endif /* defined(HAVE_CRYPTO) */ |