Proyecto

General

Perfil

Actividad #30 » GUIA_CIFRADO_DATOS_SENSIBLES.md

Guia con los pasos a seguir - Adalberto Barcelo, 03/08/2025 22:37

 

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

📋 Descripción del Objetivo

Implementar cifrado a nivel de aplicación para datos sensibles (correos electrónicos, números de teléfono, claves API) antes de almacenarlos en la base de datos, proporcionando una capa adicional de protección.

🎯 Resultado Esperado

  • Los datos sensibles se cifran automáticamente antes del almacenamiento
  • Los datos cifrados son ilegibles sin descifrado
  • Los datos se pueden recuperar correctamente en la aplicación
  • Sistema de verificación para comprobar que el cifrado funciona

📁 Estructura del Proyecto

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

🛠️ Paso 1: Creación de la Interface del Servicio de Cifrado

Archivo: src/CRM.MVC.Capacitacion.Domain/Services/IDataEncryptionService.cs

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
  • ✅ Métodos para cifrar, descifrar y verificar estado de cifrado
  • ✅ Interface simple y limpia

🔐 Paso 2: Implementación del Servicio de Cifrado

Archivo: src/CRM.MVC.Capacitacion.Domain/Services/DataEncryptionService.cs

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 Clave:

  • ✅ Utiliza Volo.Abp.Security.Encryption.IStringEncryptionService
  • ✅ Prefijo "ENC:" para identificar datos cifrados
  • ✅ Previene doble cifrado con IsEncrypted()
  • ✅ Logging detallado para auditoría
  • ✅ Manejo robusto de errores
  • ✅ Validación de datos nulos/vacíos

🌐 Paso 3: Controlador de Prueba para Verificación

Archivo: src/CRM.MVC.Capacitacion.HttpApi/Controllers/EncryptionTestController.cs

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 del Controlador:

  • ✅ Endpoint para probar el cifrado: POST /api/encryption-test/test
  • ✅ Acepta datos sensibles de prueba
  • ✅ Demuestra el proceso completo: cifrado → descifrado → verificación
  • ✅ Respuesta detallada con todos los estados de los datos
  • ✅ Validación de integridad de datos

🔗 Paso 4: Configuración de Referencias del Proyecto

Modificar src/CRM.MVC.Capacitacion.HttpApi/CRM.MVC.Capacitacion.HttpApi.csproj

Agregar 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

5.1 Compilar el Proyecto

dotnet build

5.2 Generar Certificado OpenIddict (si es necesario)

dotnet dev-certs https -v -ep openiddict.pfx -p 9f6c661e-4a1d-4873-9e03-c8646c941e28

5.3 Copiar Certificado al Proyecto Web

copy openiddict.pfx src\CRM.MVC.Capacitacion.Web\openiddict.pfx

🚀 Paso 6: Ejecución y Pruebas

6.1 Ejecutar la Aplicación

dotnet run --project src/CRM.MVC.Capacitacion.Web --urls "https://localhost:44362"

6.2 Probar el Endpoint de Cifrado

Opción A: Usar Swagger UI

  1. Abrir navegador en: https://localhost:44362/swagger
  2. Localizar el endpoint EncryptionTest
  3. Ejecutar 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\":\"test@example.com\",\"phone\":\"+1234567890\",\"apiKey\":\"secret-api-key-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: Claramente legibles
  • Datos cifrados: Prefijo "ENC:" + 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:

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: Los datos se cifran antes del almacenamiento
  • Descifrado Transparente: Los datos se descifran al recuperarse
  • Identificación de Estado: Prefijo "ENC:" para datos cifrados
  • Prevención de Doble Cifrado: Detecta datos ya cifrados
  • Logging de Auditoría: Registra operaciones de cifrado/descifrado
  • Manejo de Errores: Gestión robusta de excepciones

🛡️ Seguridad:

  • Capa Adicional de Protección: Más allá del cifrado de base de datos
  • Datos Ilegibles: Sin descifrado, los datos son completamente ilegibles
  • Integración ABP: Utiliza el módulo de seguridad nativo de ABP
  • Inyección de Dependencias: Servicio disponible en toda la aplicación

🎯 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/financieros
  • Direcciones físicas

Consideraciones de Rendimiento:

  • Impacto mínimo: El cifrado es rápido para datos pequeños
  • 🔍 Búsquedas: Los datos cifrados no son buscables directamente
  • 💾 Almacenamiento: Los datos cifrados ocupan más espacio

🔧 Troubleshooting

Problemas Comunes:

  1. Error de Compilación - Namespace no encontrado:

    • Solución: Verificar que se agregó la referencia al proyecto Domain
  2. Certificado OpenIddict no encontrado:

    • Solución: Generar y copiar el certificado como se indica en el Paso 5
  3. MySQL no conecta:

    • Solución: Verificar que el servicio MySQL esté ejecutándose
  4. El endpoint no aparece en Swagger:

    • Solución: Verificar que el controlador esté en el namespace correcto

📝 Conclusión

Esta implementación proporciona una solución robusta y segura para el cifrado de datos sensibles a nivel de aplicación en proyectos 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 garantiza que el sistema funciona correctamente antes de implementarlo en entidades de producción.

(2-2/2)