CryptoByLeo/Src/Fernet/Main.mqh

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