395 lines
14 KiB
MQL5
395 lines
14 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Main.mqh |
|
|
//| Copyright 2026, Niquel Mendoza |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, Niquel Mendoza"
|
|
#property link "https://www.mql5.com"
|
|
#property strict
|
|
|
|
#ifndef CRYPTOBYLEO_SRC_FERNET_MAIN_MQH
|
|
#define CRYPTOBYLEO_SRC_FERNET_MAIN_MQH
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
#include "Def.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
namespace TSN
|
|
{
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
/*
|
|
Notas imporantes:
|
|
- Los archivos como maximo pueden pesar menos bytes que INT_MAX lo ideal quizas 1.5gb a menos o 1.7gb
|
|
la cosa e sque debe sobra algo de 64 bytes para el IV, KEY etc...
|
|
Esto por limitaciones del lengauje en el numero de elemtnos INT_MAX sobre un array que podemos procesar
|
|
|
|
- DecryptRaw y Encrypt RAW el array "out" su tamaño ArraySize() es ligeralmetne mas grande o igual que el tamaño "en si"
|
|
Se le da la elecccion al usuario para que este pueda recortar su tamaño (trim) o quizas si trabaj
|
|
con un "int size" pueda setearlo..
|
|
Aparte estas funciones retornan -1 en caso de error
|
|
|
|
*/
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CFernet
|
|
{
|
|
private:
|
|
//---
|
|
CAes m_aes;
|
|
CHMac m_hmac;
|
|
|
|
//---
|
|
ENUM_CRYPTOBYLEO_FERNAT_ERR m_last_err;
|
|
|
|
|
|
public:
|
|
CFernet(void);
|
|
~CFernet(void) {}
|
|
|
|
//--- Seteo de key
|
|
bool Key(const uchar& key[]);
|
|
// auto solo eliges el generador y ya se hace todo auto
|
|
// CORRECCION INLINE POR BUG DEL COMPILADOR
|
|
template <typename TCryptoRandomFunc>
|
|
void Key()
|
|
{
|
|
uchar key[CRYPTOBYLEO_FERNET_KEYBYTES];
|
|
TCryptoRandomFunc::RandomCryptoBytes(key, CRYPTOBYLEO_FERNET_KEYBYTES, 0);
|
|
|
|
//---
|
|
// m_hmac.CheckKeyReserve(16);
|
|
// opt: Dado que por defecto la reserva es de 128 no hace falta vamos d esobra
|
|
m_hmac.m_key_s = ArrayCopy(m_hmac.m_key, key, 0, 0, CRYPTOBYLEO_FERNET_HMAC_KEY_BYTES);
|
|
m_hmac.OnKeyUpdate();
|
|
// ahora toca copiar para la parte alta (empezamos desde el final de hmac)
|
|
m_aes.Init(key, CRYPTOBYLEO_FERNET_HMAC_KEY_BYTES);
|
|
}
|
|
|
|
//--- EncryptRaw \ DecryptRaw (genericos basicos)
|
|
template <typename TCryptoRandomFunc>
|
|
int EncryptRaw(const uchar& in[], uchar& token[], datetime time)
|
|
{
|
|
// La idea con este es esquema es zero copyes la copia de in es invetiable dado que neceistmao mutar en aes
|
|
//---
|
|
const int data_t = ArraySize(in);
|
|
|
|
//--- estimacion de tamaoño
|
|
const int fs = 1 + 8 + AES_BLOCKLEN + data_t + AES_BLOCKLEN + CRYPTOBYLEO_SHA256_SIZET_FHASH;
|
|
if(fs > ArraySize(token))
|
|
ArrayResize(token, fs);
|
|
// armado
|
|
int w = 0;
|
|
token[w++] = CRYPTOBYLEO_FERNET_VER;
|
|
w = CNumberUtils::SValueToBytesBE(time, token, w); // -1defautl exacto, timstap
|
|
TCryptoRandomFunc::RandomCryptoBytes(m_aes.m_iv, AES_BLOCKLEN, 0); // El iv lo copiamos al CBC de aes
|
|
w += ArrayCopy(token, m_aes.m_iv, w, 0, AES_BLOCKLEN); // y tambien lo copiamos el token
|
|
|
|
|
|
// Copiamos los datos al final
|
|
ArrayCopy(token, in, w, 0, data_t); // #1 copia de los datos
|
|
|
|
|
|
//---
|
|
const int t = CAes::PKCS7_CorrectPadding(token, data_t, w) - w; // tamaño exacto
|
|
m_aes.CbcCifrado(token, t, w); // ciframos [w(inicio de datos) : t (datos con el padding)]
|
|
w += t; // movemos el puntero
|
|
Print(w);
|
|
m_hmac.Update(token, w); // hmac
|
|
w += ArrayCopy(token, m_hmac.m_out, w, 0, CRYPTOBYLEO_SHA256_SIZET_FHASH); // Copiamos el digest luego (append al final)
|
|
return w; // retorna cuando a encryptado (w) tamaño final
|
|
}
|
|
|
|
//---
|
|
// Retorna el tamaño final de msg
|
|
int DecryptRaw(const uchar& in[], int t, uchar& msg[], int ttl);
|
|
|
|
//--- Extra (Agregados para facilidad)
|
|
template <typename TCryptoRandomFunc>
|
|
bool EncryptFile(const string& file_name, bool common_flag, bool delete_prev_file, datetime time, string ext_encrypt = "");
|
|
bool DecryptFile(const string& file_name, bool common_flag, bool delete_prev_file, int ttl, string ext_decrypt = "");
|
|
bool DecryptFile(const string& file_name, bool common_flag, bool delete_prev_file, uchar& res_data[], int ttl);
|
|
|
|
//--- Extract timestap
|
|
datetime ExtractTimestap(const uchar& token[]);
|
|
|
|
//---
|
|
__forceinline ENUM_CRYPTOBYLEO_FERNAT_ERR LastErr() const { return m_last_err; }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
// AES: 128
|
|
// HMAC-256 (SHA2 256)
|
|
CFernet::CFernet(void)
|
|
: m_aes(AES_CRYPTH_128), m_hmac(CCryptoHashMeta::TSN_CRYPTO_HASH_TYPE_SHA256)
|
|
{
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
// Se asume key de 32 bits
|
|
bool CFernet::Key(const uchar &key[])
|
|
{
|
|
if(ArraySize(key) < CRYPTOBYLEO_FERNET_KEYBYTES)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_KEY_INVALID_SIZE;
|
|
return false;
|
|
}
|
|
|
|
//---
|
|
// m_hmac.CheckKeyReserve(16);
|
|
// opt: Dado que por defecto la reserva es de 128 no hace falta vamos d esobra
|
|
m_hmac.m_key_s = ArrayCopy(m_hmac.m_key, key, 0, 0, CRYPTOBYLEO_FERNET_HMAC_KEY_BYTES);
|
|
m_hmac.OnKeyUpdate();
|
|
// ahora toca copiar para la parte alta (empezamos desde el final de hmac)
|
|
m_aes.Init(key, CRYPTOBYLEO_FERNET_HMAC_KEY_BYTES);
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int CFernet::DecryptRaw(const uchar &in[], int t, uchar &msg[], int ttl)
|
|
{
|
|
int r = 0;
|
|
if(in[r++] != CRYPTOBYLEO_FERNET_VER)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_INVALID_VER;
|
|
return -1;
|
|
}
|
|
// tiempo
|
|
datetime time;
|
|
r = CNumberUtils::SVAlaueFromBytesBE(time, in, r);
|
|
if(TimeGMT() - time >= ttl)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_EXPIRED;
|
|
return -1;
|
|
}
|
|
// ahora obteenmos el HMAC de
|
|
int msi = t - CRYPTOBYLEO_SHA256_SIZET_FHASH;
|
|
m_hmac.Update(in, msi);
|
|
// ahora si comparammos
|
|
// msi tambien detecta el inicio
|
|
if(!m_hmac.CompareRaw(in, CRYPTOBYLEO_SHA256_SIZET_FHASH, msi))
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_HMAC_CORRUPTED;
|
|
return -1;
|
|
}
|
|
// descifrado
|
|
r += ArrayCopy(m_aes.m_iv, in, 0, r, AES_BLOCKLEN); // ahora r apunta justo en chiper text
|
|
msi -= AES_BLOCKLEN + 8 + 1; // (iv) - (datetime) - (version)
|
|
|
|
// aqui ocurre el resize exacto
|
|
ArrayCopy(msg, in, 0, r, msi); // solo copiamos desde [r:msi, osea desde donde empiza chiper hasta donde temrina]
|
|
m_aes.CbcDescifra(msg, msi);
|
|
|
|
//---
|
|
r = CAes::PKCS7_QuitarPadding(msg, msi);
|
|
if(r == -1) // padding invalido
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_INVALID_PADDING;
|
|
return -1;
|
|
}
|
|
return r; // termino
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
datetime CFernet::ExtractTimestap(const uchar &token[])
|
|
{
|
|
//---
|
|
if(token[0] != CRYPTOBYLEO_FERNET_VER)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_INVALID_VER;
|
|
return 0;
|
|
}
|
|
// tiempo
|
|
datetime time;
|
|
CNumberUtils::SVAlaueFromBytesBE(time, token, 1);
|
|
return time;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
template <typename TCryptoRandomFunc>
|
|
bool CFernet::EncryptFile(const string &file_name, bool common_flag, bool delete_prev_file, datetime time, string ext_encrypt = "")
|
|
{
|
|
//----- file
|
|
::ResetLastError();
|
|
const int common_file_flag = (common_flag ? FILE_COMMON : 0);
|
|
int fh = FileOpen(file_name, FILE_BIN | FILE_READ | common_file_flag);
|
|
if(fh == INVALID_HANDLE)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_OPEN_FILE;
|
|
return false;
|
|
}
|
|
|
|
//---
|
|
uchar token[];
|
|
const int data_t = (int)FileSize(fh);
|
|
|
|
//----- fernat start
|
|
|
|
//--- estimacion de tamaoño
|
|
const int fs = 1 + 8 + AES_BLOCKLEN + data_t + AES_BLOCKLEN + CRYPTOBYLEO_SHA256_SIZET_FHASH;
|
|
if(fs > ArraySize(token))
|
|
ArrayResize(token, fs);
|
|
// armado
|
|
int w = 0;
|
|
token[w++] = CRYPTOBYLEO_FERNET_VER;
|
|
w = CNumberUtils::SValueToBytesBE(time, token, w); // -1defautl exacto, timstap
|
|
TCryptoRandomFunc::RandomCryptoBytes(m_aes.m_iv, AES_BLOCKLEN, 0); // El iv lo copiamos al CBC de aes
|
|
w += ArrayCopy(token, m_aes.m_iv, w, 0, AES_BLOCKLEN); // y tambien lo copiamos el token
|
|
|
|
|
|
// Copiamos los datos al final
|
|
FileReadArray(fh, token, w, data_t); // #1 copia de los datos
|
|
FileClose(fh);
|
|
|
|
//---
|
|
const int t = CAes::PKCS7_CorrectPadding(token, data_t, w) - w; // tamaño exacto
|
|
m_aes.CbcCifrado(token, t, w); // ciframos
|
|
w += t; // movemos el puntero
|
|
m_hmac.Update(token, w); // hmac
|
|
w += ArrayCopy(token, m_hmac.m_out, w, 0, CRYPTOBYLEO_SHA256_SIZET_FHASH); // Copiamos el digest luego (append al final)
|
|
|
|
//--- trim
|
|
ArrayResize(token, w);
|
|
|
|
|
|
//----- fin fermat
|
|
const string fn_final = file_name + (ext_encrypt.Length() > 2 ? ext_encrypt : "");
|
|
if(!FileSave(fn_final, token, common_file_flag))
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_SAVE_CRYPTHF_ILE;
|
|
return false;
|
|
}
|
|
|
|
//--- solo eleiminos si se nos pide y el archivo cambio de nombre no tiene sentido eliminiarlo
|
|
// si se mantine (Eliminamso el encriptado)
|
|
if(delete_prev_file && fn_final != file_name && !FileDelete(file_name, common_file_flag))
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEL_FILE_IN_CRYPT;
|
|
|
|
//--- ahora si
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CFernet::DecryptFile(const string &file_name, bool common_flag, bool delete_prev_file, uchar &res_data[], int ttl)
|
|
{
|
|
uchar in[];
|
|
const int common_file_flag = (common_flag ? FILE_COMMON : 0);
|
|
::ResetLastError();
|
|
const int bytes = (int)FileLoad(file_name, in, common_file_flag);
|
|
if(bytes == -1)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_FILE_LOAD_ERR;
|
|
return false;
|
|
}
|
|
|
|
//---
|
|
if(DecryptRaw(in, bytes, res_data, ttl) == -1)
|
|
return false;
|
|
|
|
//---
|
|
::ResetLastError();
|
|
if(delete_prev_file && !FileDelete(file_name, common_file_flag))
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_FILE_DEL_ERR;
|
|
}
|
|
|
|
//---
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Desencripta un archivo |
|
|
//| file_name : ruta del archivo encriptado |
|
|
//| common_flag : true = carpeta Common, false = carpeta Files\ |
|
|
//| delete_prev_file: true = elimina el archivo encriptado original |
|
|
//| ext_decrypt : controla el nombre del archivo de salida: |
|
|
//| - "" = sobreescribe el mismo archivo |
|
|
//| - ".ext" = agrega extension file.enc → file.enc.ext |
|
|
//| - "-" = quita la ultima extension file.enc → file |
|
|
//| - ".ext-" = reemplaza ultima ext file.enc → file.ext |
|
|
//+------------------------------------------------------------------+
|
|
bool CFernet::DecryptFile(const string &file_name, bool common_flag, bool delete_prev_file, int ttl, string ext_decrypt = "")
|
|
{
|
|
uchar in[];
|
|
const int common_file_flag = (common_flag ? FILE_COMMON : 0);
|
|
::ResetLastError();
|
|
const int bytes = (int)FileLoad(file_name, in, common_file_flag);
|
|
if(bytes == -1)
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_FILE_LOAD_ERR;
|
|
return false;
|
|
}
|
|
|
|
//---
|
|
uchar res_data[];
|
|
const int t = DecryptRaw(in, bytes, res_data, ttl);
|
|
if(t == -1)
|
|
return false;
|
|
//---
|
|
ArrayResize(res_data, t);
|
|
|
|
//--- Creamos el archivo destino (o sobreescribimos si ext = "")
|
|
// si ext_decrypt contiene el . entonces es exntesion si no el nombre del archivo out
|
|
string out_file_name = "";
|
|
if(StringFindCharWRef(ext_decrypt, '.'))
|
|
{
|
|
const uint len = (ext_decrypt.Length());
|
|
if(ext_decrypt[len - 1] == '-') // -. quiere decir elimina la extension previa y añade esta nueva
|
|
{
|
|
ext_decrypt.Truncate(len - 1); // quitamos el - final eg si tiewnmaos extdecurp como .txt- queda .txt
|
|
out_file_name = FileRemoveExtension(file_name) + ext_decrypt;
|
|
}
|
|
else
|
|
{
|
|
out_file_name = file_name + ext_decrypt;
|
|
}
|
|
}
|
|
else
|
|
if(StringFindCharWRef(ext_decrypt, '-')) // quiere decir solo quita la extension que ya tenia
|
|
{
|
|
out_file_name = FileRemoveExtension(file_name); // Removes la primera extension (volvemos al extado original)
|
|
}
|
|
else
|
|
{
|
|
out_file_name = file_name; // lo mismo sobrescribe
|
|
}
|
|
|
|
//--- primero guardamos
|
|
::ResetLastError();
|
|
if(!FileSave(out_file_name, res_data, common_file_flag))
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_FILE_SAVE_ERR;
|
|
return false;
|
|
}
|
|
|
|
//--- Ahora si eliminamos el archivo previo si es que se pide
|
|
::ResetLastError();
|
|
if(delete_prev_file && !FileDelete(file_name, common_file_flag))
|
|
{
|
|
m_last_err = CRYPTOBYLEO_FERNAT_ERR_DEC_FILE_DEL_ERR;
|
|
}
|
|
|
|
//---
|
|
return true;
|
|
}
|
|
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
#endif // CRYPTOBYLEO_SRC_FERNET_MAIN_MQH
|