Proyecto

General

Perfil

Acciones

Actividad #31

cerrada

Cifrar Datos Sensibles a Nivel de Aplicación

Añadido por Anibal Pendas Amador hace 4 días. Actualizado hace aproximadamente 23 horas.

Estado:
Cerrado
Prioridad:
Alto
Fecha de inicio:
02/08/2025
Fecha de vencimiento:
03/08/2025
% Completado:

100%

Tiempo estimado:
6:00 h

Descripción

Cifrar Datos Sensibles a Nivel de Aplicación

  • Descripción: Implementar cifrado a nivel de aplicación para datos sensibles, como correos electrónicos, números de teléfono de contactos, y claves API, antes de almacenarlos en la base de datos, proporcionando una capa adicional de protección más allá del cifrado de la base de datos.

  • Verificación: Validar que los datos sensibles almacenados son ilegibles sin descifrado y que se pueden recuperar correctamente en la aplicación.


Peticiones relacionadas 2 (0 abiertas2 cerradas)

Copiado de Actividad #30: Cifrar Datos Sensibles a Nivel de AplicaciónCerradoAdalberto Barcelo02/08/202503/08/2025

Acciones
Copiado a Actividad #32: Cifrar Datos Sensibles a Nivel de AplicaciónCerradoReynaldo León02/08/202503/08/2025

Acciones
Acciones #1

Actualizado por Anibal Pendas Amador hace 4 días

  • Copiado de Actividad #30: Cifrar Datos Sensibles a Nivel de Aplicación añadido
Acciones #2

Actualizado por Anibal Pendas Amador hace 4 días

  • Copiado a Actividad #32: Cifrar Datos Sensibles a Nivel de Aplicación añadido
Acciones #3

Actualizado por Airam Cuesta Dueñas hace 1 día

  • Estado cambiado de Nuevo a Resuelto
  • % Completado cambiado de 0 a 100

Guía: Implementación de Cifrado de Datos Sensibles en ABP Framework

Este documento describe los pasos que realicé para implementar cifrado a nivel de aplicación para datos sensibles (correos electrónicos, números de teléfono, claves API) en el Sistema CRM utilizando ABP Framework, antes de almacenarlos en la base de datos. Esto proporciona una capa adicional de protección, complementando el cifrado de la base de datos. Verifiqué que los datos almacenados fueran ilegibles sin descifrado y que se pudieran recuperar correctamente en la aplicación.

Descripción del Objetivo

Implementar cifrado a nivel de aplicación para proteger datos sensibles antes de almacenarlos, asegurando:

  • Cifrado automático de datos antes del almacenamiento.
  • Datos cifrados ilegibles sin descifrado.
  • Recuperación correcta de datos en la aplicación.
  • Sistema de verificación para confirmar el funcionamiento del cifrado.

Estructura del Proyecto

CRM.MVC.Capacitacion/
├── src/
│   ├── CRM.MVC.Capacitacion.Domain/
│   │   └── Services/
│   │       ├── IDataEncryptionService.cs
│   │       └── DataEncryptionService.cs
│   └── CRM.MVC.Capacitacion.HttpApi/
│       └── Controllers/
│           └── EncryptionTestController.cs

Pasos para Implementar el Cifrado

Paso 1: Creación de la Interfaz del Servicio de Cifrado

  • Archivo: src/CRM.MVC.Capacitacion.Domain/Services/IDataEncryptionService.cs
  • Contenido:
    using System;
    using Volo.Abp.DependencyInjection;
    
    namespace CRM.MVC.Capacitacion.Services
    {
        public interface IDataEncryptionService : ITransientDependency
        {
            string Encrypt(string plainText);
            string Decrypt(string encryptedText);
            bool IsEncrypted(string text);
        }
    }
    
  • Características:
    • Hereda de ITransientDependency para inyección automática de dependencias.
    • Define métodos para cifrar, descifrar y verificar el estado de cifrado.
    • Interfaz simple y clara.

Paso 2: Implementación del Servicio de Cifrado

  • Archivo: src/CRM.MVC.Capacitacion.Domain/Services/DataEncryptionService.cs
  • Contenido:
    using System;
    using Microsoft.Extensions.Logging;
    using Volo.Abp.Security.Encryption;
    
    namespace CRM.MVC.Capacitacion.Services
    {
        public class DataEncryptionService : IDataEncryptionService
        {
            private readonly IStringEncryptionService _stringEncryption;
            private readonly ILogger<DataEncryptionService> _logger;
            private const string ENCRYPTED_PREFIX = "ENC:";
    
            public DataEncryptionService(
                IStringEncryptionService stringEncryption,
                ILogger<DataEncryptionService> logger)
            {
                _stringEncryption = stringEncryption;
                _logger = logger;
            }
    
            public string Encrypt(string plainText)
            {
                if (string.IsNullOrEmpty(plainText))
                    return plainText;
    
                if (IsEncrypted(plainText))
                    return plainText;
    
                try
                {
                    var encrypted = _stringEncryption.Encrypt(plainText);
                    var result = ENCRYPTED_PREFIX + encrypted;
                    
                    _logger.LogInformation("Data encrypted successfully. Original length: {OriginalLength}, Encrypted length: {EncryptedLength}", 
                        plainText.Length, result.Length);
                    
                    return result;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error encrypting data");
                    throw;
                }
            }
    
            public string Decrypt(string encryptedText)
            {
                if (string.IsNullOrEmpty(encryptedText))
                    return encryptedText;
    
                if (!IsEncrypted(encryptedText))
                    return encryptedText;
    
                try
                {
                    var withoutPrefix = encryptedText.Substring(ENCRYPTED_PREFIX.Length);
                    var decrypted = _stringEncryption.Decrypt(withoutPrefix);
                    
                    _logger.LogInformation("Data decrypted successfully. Encrypted length: {EncryptedLength}, Decrypted length: {DecryptedLength}", 
                        encryptedText.Length, decrypted.Length);
                    
                    return decrypted;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error decrypting data");
                    throw;
                }
            }
    
            public bool IsEncrypted(string text)
            {
                return !string.IsNullOrEmpty(text) && text.StartsWith(ENCRYPTED_PREFIX);
            }
        }
    }
    
  • Características:
    • Utiliza IStringEncryptionService de ABP para el cifrado.
    • Añade el prefijo ENC: para identificar datos cifrados.
    • Evita el doble cifrado mediante IsEncrypted().
    • Incluye registro (logging) para auditoría.
    • Maneja errores y valida datos nulos o vacíos.

Paso 3: Controlador de Prueba para Verificación

  • Archivo: src/CRM.MVC.Capacitacion.HttpApi/Controllers/EncryptionTestController.cs
  • Contenido:
    using System;
    using Microsoft.AspNetCore.Mvc;
    using CRM.MVC.Capacitacion.Services;
    using Volo.Abp.AspNetCore.Mvc;
    
    namespace CRM.MVC.Capacitacion.Controllers
    {
        [Route("api/encryption-test")]
        [ApiController]
        public class EncryptionTestController : AbpController
        {
            private readonly IDataEncryptionService _encryptionService;
    
            public EncryptionTestController(IDataEncryptionService encryptionService)
            {
                _encryptionService = encryptionService;
            }
    
            [HttpPost("test")]
            public IActionResult TestEncryption([FromBody] TestDataRequest request)
            {
                try
                {
                    // Cifrar datos sensibles
                    var encryptedEmail = _encryptionService.Encrypt(request.Email);
                    var encryptedPhone = _encryptionService.Encrypt(request.Phone);
                    var encryptedApiKey = _encryptionService.Encrypt(request.ApiKey);
    
                    // Descifrar para verificar
                    var decryptedEmail = _encryptionService.Decrypt(encryptedEmail);
                    var decryptedPhone = _encryptionService.Decrypt(encryptedPhone);
                    var decryptedApiKey = _encryptionService.Decrypt(encryptedApiKey);
    
                    return Ok(new
                    {
                        Original = new
                        {
                            Email = request.Email,
                            Phone = request.Phone,
                            ApiKey = request.ApiKey
                        },
                        Encrypted = new
                        {
                            Email = encryptedEmail,
                            Phone = encryptedPhone,
                            ApiKey = encryptedApiKey,
                            EmailIsEncrypted = _encryptionService.IsEncrypted(encryptedEmail),
                            PhoneIsEncrypted = _encryptionService.IsEncrypted(encryptedPhone),
                            ApiKeyIsEncrypted = _encryptionService.IsEncrypted(encryptedApiKey)
                        },
                        Decrypted = new
                        {
                            Email = decryptedEmail,
                            Phone = decryptedPhone,
                            ApiKey = decryptedApiKey
                        },
                        Verification = new
                        {
                            EmailMatches = request.Email == decryptedEmail,
                            PhoneMatches = request.Phone == decryptedPhone,
                            ApiKeyMatches = request.ApiKey == decryptedApiKey,
                            AllDataIntact = request.Email == decryptedEmail && 
                                           request.Phone == decryptedPhone && 
                                           request.ApiKey == decryptedApiKey
                        }
                    });
                }
                catch (Exception ex)
                {
                    return BadRequest(new { Error = ex.Message });
                }
            }
        }
    
        public class TestDataRequest
        {
            public string Email { get; set; }
            public string Phone { get; set; }
            public string ApiKey { get; set; }
        }
    }
    
  • Funcionalidades:
    • Endpoint POST /api/encryption-test/test para probar el cifrado.
    • Acepta datos sensibles de prueba (email, teléfono, clave API).
    • Devuelve datos originales, cifrados, descifrados y verificaciones.
    • Valida la integridad de los datos.

Paso 4: Configuración de Referencias del Proyecto

  • Modifiqué el archivo src/CRM.MVC.Capacitacion.HttpApi/CRM.MVC.Capacitacion.HttpApi.csproj para incluir la referencia al proyecto Domain:
    <ItemGroup>
      <ProjectReference Include="..\CRM.MVC.Capacitacion.Application.Contracts\CRM.MVC.Capacitacion.Application.Contracts.csproj" />
      <ProjectReference Include="..\CRM.MVC.Capacitacion.Domain\CRM.MVC.Capacitacion.Domain.csproj" />
    </ItemGroup>
    

Paso 5: Compilación y Preparación del Entorno

  1. Compilé el proyecto:
    dotnet build
    
  2. Generé un certificado OpenIddict (si es necesario):
    dotnet dev-certs https -v -ep openiddict.pfx -p 9f6c661e-4a1d-4873-9e03-c8646c941e28
    
  3. Copié el certificado al proyecto web:
    copy openiddict.pfx src\CRM.MVC.Capacitacion.Web\openiddict.pfx
    

Paso 6: Ejecución y Pruebas

  1. Ejecuté la aplicación:
    dotnet run --project src/CRM.MVC.Capacitacion.Web --urls "https://localhost:44362"
    
  2. Probé el endpoint de cifrado:
    • Opción A: Usar Swagger UI:
      • Abrí el navegador en https://localhost:44362/swagger.
      • Localicé el endpoint EncryptionTest.
      • Ejecuté con datos de prueba:
        {
          "email": "usuario@ejemplo.com",
          "phone": "+34123456789",
          "apiKey": "mi-api-key-secreta-123"
        }
        
    • Opción B: Usar cURL:
      curl -X POST "https://localhost:44362/api/encryption-test/test" \
           -H "Content-Type: application/json" \
           -d "{\"email\":\"usuario@ejemplo.com\",\"phone\":\"+34123456789\",\"apiKey\":\"mi-api-key-secreta-123\"}" \
           -k
      

Paso 7: Verificación de Resultados

  • Respuesta esperada del API:
    {
      "original": {
        "email": "usuario@ejemplo.com",
        "phone": "+34123456789",
        "apiKey": "mi-api-key-secreta-123"
      },
      "encrypted": {
        "email": "ENC:uv22fKdTr5E4ThGfen@WuifPutl2H2zfb4N@cZDJdHw==",
        "phone": "ENC:t2V@kLDYn3Nu/nJ7YN8Zg==",
        "apiKey": "ENC:@PMgmysw5qEtxiOvaKUtSsJjGwTa5pbnVImTTMy@c=",
        "emailIsEncrypted": true,
        "phoneIsEncrypted": true,
        "apiKeyIsEncrypted": true
      },
      "decrypted": {
        "email": "usuario@ejemplo.com",
        "phone": "+34123456789",
        "apiKey": "mi-api-key-secreta-123"
      },
      "verification": {
        "emailMatches": true,
        "phoneMatches": true,
        "apiKeyMatches": true,
        "allDataIntact": true
      }
    }
    
  • Criterios de éxito:
    • Datos originales: Legibles.
    • Datos cifrados: Con prefijo ENC: y texto ilegible.
    • Datos descifrados: Idénticos a los originales.
    • Verificación: Todos los campos en true.

Paso 8: Implementación en Entidades Reales

  • Ejemplo de uso en una entidad (ContactoEntity):
    public class ContactoEntity : FullAuditedAggregateRoot<Guid>
    {
        private readonly IDataEncryptionService _encryptionService;
    
        public string Nombre { get; set; }
        private string _email;
        private string _telefono;
    
        public string Email 
        { 
            get => _encryptionService?.Decrypt(_email) ?? _email;
            set => _email = _encryptionService?.Encrypt(value) ?? value;
        }
    
        public string Telefono 
        { 
            get => _encryptionService?.Decrypt(_telefono) ?? _telefono;
            set => _telefono = _encryptionService?.Encrypt(value) ?? value;
        }
    }
    

Características de Seguridad Implementadas

  • Funcionalidades principales:
    • Cifrado automático antes del almacenamiento.
    • Descifrado transparente al recuperar datos.
    • Identificación de datos cifrados con prefijo ENC:.
    • Prevención de doble cifrado mediante IsEncrypted().
    • Registro (logging) para auditoría de operaciones.
    • Manejo robusto de excepciones.
  • Seguridad:
    • Capa adicional de protección más allá del cifrado de la base de datos.
    • Datos ilegibles sin descifrado.
    • Integración con el módulo de seguridad nativo de ABP.
    • Servicio disponible en toda la aplicación mediante inyección de dependencias.

Casos de Uso Recomendados

  • Datos sensibles a cifrar:
    • Correos electrónicos.
    • Números de teléfono.
    • Claves API.
    • Tokens de acceso.
    • Información personal identificable (PII).
    • Datos bancarios o financieros.
    • Direcciones físicas.
  • Consideraciones de rendimiento:
    • Impacto mínimo en el rendimiento para datos pequeños.
    • Los datos cifrados no son buscables directamente.
    • Los datos cifrados ocupan más espacio de almacenamiento.

Resolución de Problemas

  • Error de compilación (namespace no encontrado):
    • Solución: Verificar que se agregó la referencia al proyecto Domain en el archivo .csproj.
  • Certificado OpenIddict no encontrado:
    • Solución: Generar y copiar el certificado como se indica en el Paso 5.
  • MySQL no conecta:
    • Solución: Verificar que el servicio MySQL esté ejecutándose.
  • El endpoint no aparece en Swagger:
    • Solución: Asegurar que el controlador esté en el namespace correcto.

Conclusión

Implementé una solución robusta y segura para el cifrado de datos sensibles a nivel de aplicación en el Sistema CRM utilizando ABP Framework. El sistema es:

  • Fácil de implementar.
  • Transparente para el desarrollador.
  • Altamente seguro.
  • Completamente verificable.
  • Integrado con ABP.
    La verificación mediante el endpoint de prueba confirmó que los datos sensibles se cifran correctamente, son ilegibles sin descifrado y se recuperan correctamente en la aplicación.
Acciones #4

Actualizado por Anibal Pendas Amador hace aproximadamente 23 horas

  • Estado cambiado de Resuelto a Cerrado

Buen trabajo

Acciones

También disponible en: Atom PDF