diff --git a/packages/crypto-node/README.md b/packages/crypto-node/README.md index d4f363a0..cec1634e 100644 --- a/packages/crypto-node/README.md +++ b/packages/crypto-node/README.md @@ -3,10 +3,9 @@ 📖 [API Reference](https://ref.mtcute.dev/modules/_mtcute_crypto_node.html) Native extension for NodeJS that improves performance of the most used -cryptographic mode in Telegram (IGE), which is not implemented by OpenSSL. +cryptographic mode in Telegram (IGE), which is not implemented directly by OpenSSL. -Other modes used (i.e. CBC, CTR) and hashes are supported natively by OpenSSL, -and they *are* faster than the custom implementation, so OpenSSL will be used for them. +Uses OpenSSL under the hood to provide maximum performance ## Installation You will need all the pre-requisites for [node-gyp](https://github.com/nodejs/node-gyp#installation). @@ -28,5 +27,6 @@ const tg = new TelegramClient({ > **Tip**: When using `@mtcute/node`, this will be done automatically for you. -## Acknowledgments -Based on [pyrogram/tgcrypto](https://github.com/pyrogram/tgcrypto) +## Benchmarks + +See https://github.com/mtcute/benchmarks \ No newline at end of file diff --git a/packages/crypto-node/binding.gyp b/packages/crypto-node/binding.gyp index 4ae333e0..8a494191 100644 --- a/packages/crypto-node/binding.gyp +++ b/packages/crypto-node/binding.gyp @@ -3,11 +3,8 @@ { "target_name": "crypto", "sources": [ - "../crypto/aes256.c", - "../crypto/aes256.h", - "../crypto/ige256.c", - "../crypto/ige256.h", "lib/entry.cpp", + "lib/ige256.cpp", ], "cflags_cc": [ "-std=c++17" diff --git a/packages/crypto-node/lib/entry.cpp b/packages/crypto-node/lib/entry.cpp index a97b0b26..08575c82 100644 --- a/packages/crypto-node/lib/entry.cpp +++ b/packages/crypto-node/lib/entry.cpp @@ -1,6 +1,6 @@ #include #include -#include "../../crypto/ige256.h" +#include "ige256.h" #ifndef NODE_GYP_MODULE_NAME #define NODE_GYP_MODULE_NAME crypto @@ -74,7 +74,7 @@ napi_value ret; \ CALL_ASSERT_OK(napi_create_buffer(env, size, (void**) &output_buf, &ret)); \ \ - name(input_buf, size, key_buf, iv_buf, output_buf); \ + name(env, input_buf, size, key_buf, iv_buf, output_buf); \ \ return ret; \ } diff --git a/packages/crypto-node/lib/ige256.cpp b/packages/crypto-node/lib/ige256.cpp new file mode 100644 index 00000000..7124b129 --- /dev/null +++ b/packages/crypto-node/lib/ige256.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include "ige256.h" + +#define THROW_OPENSSL_ERROR \ + napi_throw_error(env, NULL, ERR_error_string(ERR_get_error(), NULL)); \ + return; + +void ige256_encrypt(napi_env env, uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { + EVP_CIPHER_CTX *ctx; + uint32_t i, j; + + uint8_t* iv1; + uint8_t* iv2; + uint8_t* block; + + int written; + + iv1 = &iv[0]; + iv2 = &iv[16]; + + if (!(ctx = EVP_CIPHER_CTX_new())) { + THROW_OPENSSL_ERROR + } + + if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, NULL)) { + THROW_OPENSSL_ERROR + } + + for (i = 0; i < length; i += AES_BLOCK_SIZE) { + block = &out[i]; + + for (j = 0; j < AES_BLOCK_SIZE; ++j) + block[j] = in[i + j] ^ iv1[j]; + + if (1 != EVP_EncryptUpdate(ctx, block, &written, block, AES_BLOCK_SIZE)) { + THROW_OPENSSL_ERROR + } + + for (j = 0; j < AES_BLOCK_SIZE; ++j) + block[j] ^= iv2[j]; + + iv1 = block; + iv2 = &in[i]; + } + + EVP_CIPHER_CTX_free(ctx); +} + +void ige256_decrypt(napi_env env, uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { + EVP_CIPHER_CTX *ctx; + uint32_t i, j; + + uint8_t* iv1; + uint8_t* iv2; + uint8_t* block; + + int written; + + iv1 = &iv[16]; + iv2 = &iv[0]; + + if (!(ctx = EVP_CIPHER_CTX_new())) { + THROW_OPENSSL_ERROR + } + + if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, NULL)) { + THROW_OPENSSL_ERROR + } + + if (1 != EVP_CIPHER_CTX_set_padding(ctx, 0)) { + THROW_OPENSSL_ERROR + } + + for (i = 0; i < length; i += AES_BLOCK_SIZE) { + block = &out[i]; + + for (j = 0; j < AES_BLOCK_SIZE; ++j) + block[j] = in[i + j] ^ iv1[j]; + + if (1 != EVP_DecryptUpdate(ctx, block, &written, block, AES_BLOCK_SIZE)) { + THROW_OPENSSL_ERROR + } + + for (j = 0; j < AES_BLOCK_SIZE; ++j) + block[j] ^= iv2[j]; + + iv1 = block; + iv2 = &in[i]; + } +} diff --git a/packages/crypto-node/lib/ige256.h b/packages/crypto-node/lib/ige256.h new file mode 100644 index 00000000..ebf6ca68 --- /dev/null +++ b/packages/crypto-node/lib/ige256.h @@ -0,0 +1,12 @@ +#include +#include + +#ifndef IGE256_H +#define IGE256_H + +#define AES_BLOCK_SIZE 16 + +void ige256_encrypt(napi_env env, uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out); +void ige256_decrypt(napi_env env, uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out); + +#endif // IGE256_H diff --git a/packages/crypto-node/package.json b/packages/crypto-node/package.json index e51d14c8..9e90b091 100644 --- a/packages/crypto-node/package.json +++ b/packages/crypto-node/package.json @@ -26,6 +26,10 @@ ".": { "import": "./esm/index.js", "require": "./cjs/index.js" + }, + "./native": { + "import": "./esm/native.cjs", + "require": "./cjs/native.cjs" } } }, diff --git a/scripts/build-package.js b/scripts/build-package.js index c8a2722b..f368a4f3 100644 --- a/scripts/build-package.js +++ b/scripts/build-package.js @@ -67,8 +67,7 @@ const BUILD_CONFIGS = { customScript(packageDir, outDir) { // copy native sources and binding.gyp file - fs.mkdirSync(path.join(outDir, 'lib'), { recursive: true }) - fs.mkdirSync(path.join(outDir, 'crypto'), { recursive: true }) + fs.cpSync(path.join(packageDir, 'lib'), path.join(outDir, 'lib'), { recursive: true }) const bindingGyp = fs.readFileSync(path.join(packageDir, 'binding.gyp'), 'utf8') fs.writeFileSync( @@ -78,21 +77,6 @@ const BUILD_CONFIGS = { .replace(/"\.\.\/crypto/g, '"crypto'), ) - for (const f of fs.readdirSync(path.join(packageDir, 'lib'))) { - const content = fs.readFileSync(path.join(packageDir, 'lib', f), 'utf8') - - fs.writeFileSync( - path.join(outDir, 'lib', f), - content - // replace paths to crypto - .replace(/#include "\.\.\/\.\.\/crypto/g, '#include "../crypto'), - ) - } - - for (const f of fs.readdirSync(path.join(packageDir, '../crypto'))) { - fs.copyFileSync(path.join(packageDir, '../crypto', f), path.join(outDir, 'crypto', f)) - } - // for some unknown fucking reason ts doesn't do this fs.copyFileSync(path.join(packageDir, 'src/native.cjs'), path.join(outDir, 'cjs/native.cjs')) fs.copyFileSync(path.join(packageDir, 'src/native.cjs'), path.join(outDir, 'esm/native.cjs'))