Implementación del patrón Factory Method en un sistema de cifrado

Supongamos que se requiere consrtuir una aplicación que pueda cifrar y descifrar datos empleando DES o IDEA. En lugar de crear directamente instancias de DesEngine o IdeaEngine en el código cliente, se define una interfaz común y una familia de fábricas especializadas.

Diseño de la arquitectura Java

La interfaz CryptoEngine representa el producto abstracto. Cada algoritmo concreto la implementa y provee su propia lógica de cifrado. La interfaz CryptoFactory actúa como creador abstracto y declara el método de fabricación.

public interface CryptoEngine {
    byte[] transform(byte[] input, byte[] key);
}

public interface CryptoFactory {
    CryptoEngine createEngine();
}

Implementación de los algoritmos

La clase DesEngine utiliza el proveedor estándar de Java para realizar el cifrado y descifrado con Cipher. Se simplifica la gestión de claves generando automáticamente un secreto de 112 bits para el modo DESede.

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class DesEngine implements CryptoEngine {
    private static final String ALGORITHM = "DESede";

    @Override
    public byte[] transform(byte[] input, byte[] key) {
        try {
            KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
            generator.init(112);
            SecretKey secretKey = generator.generateKey();

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encrypted = cipher.doFinal(input);

            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            byte[] decrypted = cipher.doFinal(encrypted);

            return decrypted;
        } catch (Exception e) {
            throw new RuntimeException("Error en operación DES", e);
        }
    }
}

La clase IdeaEngine aprovecha Bouncy Castle para el soporte de IDEA y gestiona la clave de 128 bits requerida por este algoritmo.

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.Security;

public class IdeaEngine implements CryptoEngine {
    private static final String KEY_ALGORITHM = "IDEA";
    private static final String CIPHER_ALGORITHM = "IDEA/ECB/ISO10126Padding";

    public IdeaEngine() {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Override
    public byte[] transform(byte[] input, byte[] key) {
        try {
            byte[] rawKey = initKey();
            Key k = new SecretKeySpec(rawKey, KEY_ALGORITHM);

            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, k);
            byte[] encrypted = cipher.doFinal(input);

            cipher.init(Cipher.DECRYPT_MODE, k);
            return cipher.doFinal(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("Error en operación IDEA", e);
        }
    }

    private byte[] initKey() throws Exception {
        KeyGenerator generator = KeyGenerator.getInstance(KEY_ALGORITHM);
        generator.init(128);
        return generator.generateKey().getEncoded();
    }
}

Fábricas concretas

Cada fábrica se encarga únicamente de instanciar el motor que le corresponde. De este modo, el cliente solo interactúa con la abstracción.

public class DesFactory implements CryptoFactory {
    @Override
    public CryptoEngine createEngine() {
        System.out.println("Creando motor DES");
        return new DesEngine();
    }
}

public class IdeaFactory implements CryptoFactory {
    @Override
    public CryptoEngine createEngine() {
        System.out.println("Creando motor IDEA");
        return new IdeaEngine();
    }
}

Código cliente

El cliente selecciona el algoritmo y la fábrica correspondiente. No existe acoplamiento directo con DesEngine ni IdeaEngine.

import java.util.Scanner;

public class CryptoClient {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("Seleccione algoritmo: 1. DES  2. IDEA  3. Salir");
        int option = scanner.hasNextInt() ? scanner.nextInt() : -1;

        CryptoFactory factory = resolveFactory(option);
        if (factory == null) {
            System.out.println("Opción no válida");
            return;
        }

        CryptoEngine engine = factory.createEngine();
        byte[] result = engine.transform("Texto de prueba".getBytes(), null);
        System.out.println("Resultado: " + new String(result));
    }

    private static CryptoFactory resolveFactory(int option) {
        return switch (option) {
            case 1 -> new DesFactory();
            case 2 -> new IdeaFactory();
            default -> null;
        };
    }
}

Equivalente en C++

En C++ se puede modelar el mismo patrón mediante una jerarquía de clases con métodos virtuales. El ejemplo siguiente conserva la idea central del Factory Method y utiliza std::unique_ptr para gestionar la memoria.

#include <iostream>
#include <memory>
#include <string>

class CryptoEngine {
public:
    virtual ~CryptoEngine() = default;
    virtual std::string process(const std::string& text) = 0;
};

class DesCrypto : public CryptoEngine {
public:
    std::string process(const std::string& text) override {
        // Lógica de cifrado/descifrado DES
        return "DES(" + text + ")";
    }
};

class IdeaCrypto : public CryptoEngine {
public:
    std::string process(const std::string& text) override {
        // Lógica de cifrado/descifrado IDEA
        return "IDEA(" + text + ")";
    }
};

class CryptoFactory {
public:
    virtual ~CryptoFactory() = default;
    virtual std::unique_ptr<CryptoEngine> buildEngine() = 0;
};

class DesFactory : public CryptoFactory {
public:
    std::unique_ptr<CryptoEngine> buildEngine() override {
        return std::make_unique<DesCrypto>();
    }
};

class IdeaFactory : public CryptoFactory {
public:
    std::unique_ptr<CryptoEngine> buildEngine() override {
        return std::make_unique<IdeaCrypto>();
    }
};

int main() {
    std::unique_ptr<CryptoFactory> factory = std::make_unique<DesFactory>();
    std::unique_ptr<CryptoEngine> engine = factory->buildEngine();
    std::cout << engine->process("mensaje secreto") << std::endl;
    return 0;
}

Con esta organización, agregar un nuevo algoritmo de cifrado solo requiere implementar una nueva clase de motor y su fábrica asociada, sin modificar el código que consume el servicio.

Etiquetas: Factory-Method cifrado DES idea java

Publicado el 8-10 07:12