Tarea técnica

Cree un programa que le permita cifrar y descifrar texto utilizando el cifrado César. El programa debe admitir varios modos de funcionamiento, manejar archivos de gran tamaño e incluir la validación de los datos de entrada. Opcionalmente, puede añadir una interfaz gráfica de usuario y un análisis estadístico para descifrar automáticamente el cifrado

Objetivos principales:

Implementación del cifrado César:

  • Creación y uso del alfabeto.
  • Algoritmo de desplazamiento de caracteres según una clave dada.

Tratamiento de ficheros:

  • Funcionalidad para trabajar con ficheros (lectura, escritura).
  • Tratamiento de ficheros de texto de gran tamaño.

Validación de datos de entrada:

  • Comprobación de la existencia de ficheros.
  • Validez de las claves.

Modos de funcionamiento:

Cifrado de texto:

  • Función de cifrado que toma un fichero, una clave y escribe el texto cifrado en un nuevo fichero.

Descifrado de texto:

  • Una función de descifrado que utiliza una clave conocida.

Descifrado mediante el método de brute force (opcional):

  • Implementación del método de brute force para probar todas las claves hasta que el descifrado tenga éxito.

Descifrado por el método de análisis estadístico (opcional):

  • Desarrollo de un algoritmo de análisis estadístico para el descifrado automático sin clave utilizando las características del lenguaje.

Desarrollo de la interfaz de usuario:

  • Menú de texto o interfaz gráfica (opcional).

Tareas adicionales:

  • Gestión de errores y excepciones.
  • Optimización del rendimiento.
  • Documentación y pruebas.

Criptología, criptografía y criptoanálisis

Empecemos analizando la teoría que necesitarás para el proyecto final. Vamos a aprender más sobre la criptología y sus componentes y, al mismo tiempo, a explorar el código que utilizarás al escribir el proyecto.

Tarea: Escribe un programa que funcione con el cifrado César.

El cifrado César es uno de los métodos de cifrado más sencillos y famosos. Debe su nombre al emperador Cayo Julio César, que lo utilizaba para mantener correspondencia secreta con sus generales.

El cifrado César es un cifrado por sustitución: sustituye cada carácter del texto en claro por un carácter que está un número constante de posiciones a la izquierda o a la derecha del mismo en el alfabeto.

Por ejemplo, supongamos que fijamos el desplazamiento en 3 (clave = 3). En este caso, A se sustituirá por D, B se convertirá en E, y así sucesivamente.

Para visualizarlo, veamos una herramienta de cifrado para el cifrado César:

Esta herramienta consiste en dos círculos que giran uno respecto al otro. A lo largo del perímetro de cada círculo hay letras escritas en orden alfabético. Cuando se desplazan, todas las letras del círculo interior están desplazadas la misma distancia con respecto a las letras del círculo exterior.

Para cifrar, sustituimos las letras del círculo exterior por las letras correspondientes del círculo interior. Por ejemplo, si los círculos están desplazados como en la imagen, sustituimos A por C, y R por T. Para descifrar, hacemos lo contrario: sustituimos las letras del círculo interior por las del círculo exterior; es decir, C por A, y T por R.

Supongamos que el perro Spot decide escribir un mensaje secreto a su amiga Alice. Cada uno de ellos dispone de una herramienta de este tipo. Para cifrar y descifrar correctamente el mensaje, deben ponerse de acuerdo sobre cuántas posiciones deben desplazarse los círculos entre sí. En la imagen de la herramienta, puedes ver que A se ha convertido en C, y B en D, lo que significa que el círculo interior se ha desplazado 2 posiciones. Observa que Y se ha convertido en A (y no ha desaparecido). Cuando acordaron desplazar los círculos 2 posiciones y Spot envió a Alice el mensaje «NGVU RNCA NGIQ», Alice lo entendió inmediatamente y fue a buscar una caja de Lego.

Como el alfabeto inglés sólo tiene 26 letras, se pueden desplazar los círculos 1, 2,..., 25 posiciones. Si nos desplazamos 26 (o 0) posiciones, las letras de los círculos coincidirán. Para más detalles, puede ver la conferencia sobre Criptografía CS50, que trata el cifrado César y el cifrado Vigenere.

Criptoanálisis: descifrar la clave

Criptografía: algoritmos de cifrado de datos (y más)

Un alfabeto es un conjunto completo de símbolos que pueden aparecer en un texto que se puede cifrar. En el cifrado César, el orden de estos símbolos es importante. Puede que no sea el orden clásico de las letras del alfabeto, pero tanto Spot (el que cifra) como Alice (la que descifra) deben conocerlo.

El criptoanálisis de datos estadísticos se basa en el hecho de que cada idioma tiene sus propias estadísticas sobre el uso de símbolos. Por ejemplo, en este texto (mientras lo escribo), ya hay 14 apariciones de la palabra «y» y 24 apariciones de la palabra «en». Si mantienes los espacios, es fácil adivinar que las palabras de una sola letra más comunes probablemente sean A o I. Si utilizaras el cifrado César con el orden clásico de las letras en el alfabeto, descifrar este texto sería sencillo (prueba con un desplazamiento para A - ilegible - luego prueba con un desplazamiento para B, y así sucesivamente).

Vale, podrías eliminar espacios para ocultar palabras de una sola letra. Pero entonces podrías identificar fácilmente las vocales (son mucho más comunes que las consonantes) o palabras frecuentes como «o». Incluso una búsqueda simple no sería difícil, ya que hay muy pocas opciones que probar (para el alfabeto inglés, sólo hay 25). Por lo tanto, Spot y Alice deberían plantearse métodos de cifrado más complejos.

Es posible desviarse de la tarea literal. Lo principal es captar la esencia.

Estos son los datos teóricos mínimos que necesitarás para completar el proyecto final. Pasemos a la descripción de la tarea.

Por dónde empezar: arquitectura del programa

Antes de empezar a escribir código, es esencial dividir la tarea en subtareas y pensar en la arquitectura general del proyecto. Dado que este es tu primer gran proyecto, puedes tener la tentación de poner todo en una gran clase y crear varios métodos dentro de ella, llamándolos a todos en el método main.

Llamemos a nuestra clase CaesarCipher.

En esta clase, definiremos el alfabeto con el que trabajaremos, así como los métodos para cifrar, descifrar, y un método main para manejar el menú de llamada.


public class CaesarCipher {
    
    // Alphabet
    private static final String ALPHABET = "our alphabet will be here";
        
    // Methods for encryption, decryption, brute force, statistical analysis
        
    public void encrypt(String inputFile, String outputFile, int key) {
        // Implement encryption
    }

    public void decrypt(String inputFile, String outputFile, int key) {
        // Implement decryption
    }

    public void bruteForce(String inputFile, String 
outputFile, String optionalSampleFile) {
        // Implementation of brute force
    }

    public void statisticalAnalysis(String inputFile, 
String outputFile, String optionalSampleFile) {
        // Implement statistical analysis
    }

    // Helper methods: validateInput(), createAlphabet(), shiftCharacter(), readFile(), writeFile()

public static void main(String[] args) {
    CaesarCipher cipher = new CaesarCipher();
    // Menu logic
    // 1. Encryption
    // 2. Decryption with key
    // 3. Brute force
    // 4. Statistical analysis
    // 0. Exit
                
    // Example of calling the encryption method:
    // cipher.encrypt("input.txt", "output.txt", 3);;
    }
}
}

Pero imagina si tu programa crece. No será muy cómodo escribirlo todo en una sola clase. Para organizar mejor el código y aumentar la legibilidad, considera dividir el programa en varias clases.

Por ejemplo:

1. MainApp: Esta es la clase principal donde comienza la ejecución del programa. Se encarga de procesar los comandos del usuario, llamar a los métodos apropiados y controlar el flujo del programa. Este enfoque le ayudará a gestionar el programa de manera más eficaz a medida que se vuelve más complejo.

public class MainApp {
    public static void main(String[] args) {
        // Logic for selecting the operating mode, calling the appropriate methods
    }
}

2. Cipher: Clase que implementa la funcionalidad de cifrado y descifrado César.


public class Cipher {
    private char[] alphabet;
    public Cipher(char[] alphabet) {
    this.alphabet = alphabet;
    }
    public String encrypt(String text, int shift) {
        // Encryption logic
    }
    public String decrypt(String encryptedText, int shift) {
        // Decryption logic
    }
}

3. FileManager: Responsable de la lectura y escritura de ficheros.


public class FileManager {
    public String readFile(String filePath) {
        // File reading logic
    }
    public void writeFile(String content, String filePath) {
        // Logic for writing a file
    }
}

4. Validator: Validación de los datos de entrada, como la existencia de archivos o la validez de las claves.


public class Validator {
    public boolean isValidKey(int key, char[] alphabet) {
        // Key check
    }
    public boolean isFileExists(String filePath) {
        // Check if the file exists
    }
}

5. BruteForce: Implementación de un método de enumeración de todas las claves para crackear.


public class BruteForce {
    public String decryptByBruteForce(String encryptedText, char[] alphabet) {
        // Brute force logic
    }
}

6. StatisticalAnalyzer: Para el análisis estadístico durante la transcripción.


public class StatisticalAnalyzer {
    public int findMostLikelyShift(String encryptedText, 
char[] alphabet, String representativeText) {
        // Statistical analysis logic to determine the shift
    }
}

Enfoque general del uso de las clases:

En MainApp,, el usuario selecciona el modo de operación, por ejemplo, a través de la línea de comandos o de una simple GUI.

Entonces, usando instancias de las clases FileManager, Cipher, Validator, BruteForce, y StatisticalAnalyzer, se realizan las operaciones apropiadas como lectura de ficheros, encriptación/desencriptación, validación y análisis.

Los resultados se escriben en un fichero o se muestran al usuario.

Estas son sólo recomendaciones. Usted puede dividir su programa en clases de manera diferente, idear diferentes nombres, alfabeto, etc.

Resultado

Resultado: el programa funciona en varios modos

Modos:

  • Cifrado de texto
  • Descifrado de texto utilizando una clave
  • Descifrado de texto mediante brute force (búsqueda en todas las opciones)
  • (opcional) Descifrado mediante análisis estadístico de texto

El programa debe abrir un fichero de texto especificado por el usuario y realizar una de las acciones anteriores con él. Después, creará un nuevo fichero con el resultado.

El programa debe realizar las siguientes funciones

1. Cifrar texto de un fichero dado:

  • Entrada: La dirección del fichero con el texto original, la dirección del fichero donde hay que escribir el texto cifrado y un desplazamiento alfabético (la clave de cifrado César).
  • Asegúrese de que
    • a) El fichero original existe en la dirección dada.
    • b) La clave está entre 0 y (tamaño del alfabeto - 1) (o puedes tomar el resto de la división por el tamaño del alfabeto).
  • Descifrado con una clave conocida:

  • Entrada: La dirección del fichero cifrado, la dirección donde debe escribirse el fichero descifrado y el desplazamiento alfabético utilizado durante el cifrado (clave).
  • Descifrado con el método de brute force:

  • Entrada: Ladirección del fichero cifrado, (opcional) la dirección de un fichero con texto que sea un ejemplo del texto que fue cifrado (por ejemplo, otra obra del mismo autor), y la dirección del fichero que debe contener el texto descifrado.
  • Descodificación mediante análisis estadístico: - Entrada: La misma que para el descifrado por brute force.

No olvide validar los datos de entrada.

El texto fuente para el cifrado debe estar en un archivo, preferiblemente en formato .txt. El programa debe ser capaz de manejar grandes textos de cientos de páginas. Debe ser capaz de cifrar este archivo y escribir el texto cifrado en otro archivo.

Pistas

Alfabeto

Cree un alfabeto en el que exista el problema. Por convención, es el alfabeto inglés y la puntuación. , « ' : - ! ? SPACE ¡No te olvides del espacio! ¿Cómo hacerlo? Por ejemplo, puede almacenar el alfabeto en una String, varias cadenas o una matriz de String.

O... puedes crear un alfabeto basado en un Set, una matriz o una List. También puede utilizar una tabla ASCII.

Recuerde que el alfabeto no cambia, por lo que es lógico hacer de dicha variable una constante public static final. El nombre de este tipo de variables suele escribirse en mayúsculas.


    private static final List ALPHABET = Arrays.asList('а', 'б',
    'в','г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у',
    'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'я', '.', ',', '«', '»',
    ':', '!', '?', ' ');

Mejor aún, utilice un array normal (como el alfabeto no cambia, no tiene sentido ponerlo en una lista). Los arrays ocupan menos memoria y, por lo tanto, siempre que sea posible, deben utilizarse arrays, especialmente cuando hablamos de tipos primitivos (en las listas, los tipos primitivos se envuelven en objetos y ocupan bastante más memoria).


private static final char[] ALPHABET = {'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з',
'и','к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',
'ъ', 'ы', 'ь', 'э', 'я', '.', ',', '«', '»', '"', '\'', ':', '!', '?', ' '};
    

Cifrado

Para ello necesitas conocer el turno (clave) y el alfabeto.

Para cada carácter del texto original necesitas:

  • comprobar que está en tu alfabeto. Si no está, omite este símbolo.
  • encontrar su posición en el alfabeto. Piense qué estructura de datos debe utilizar para acelerar este proceso (cada 15 veces), ya que no es necesario escanear toda la biblioteca en busca de un libro que empiece por la letra Y (Vale, P).
  • encontrar un carácter en una posición desplazada por un offset dado. Y recuerde que en el ejemplo del juguete, Y se convirtió en A (y no voló al espacio). ¿Cómo se puede garantizar esto? (se puede hacer (posición de la letra + desplazamiento) %( tamaño del alfabeto). Porcentaje es el operador para obtener el resto de la división).
  • Sustituir el carácter original por el cifrado.

Guarda el resultado en un archivo (para evitar que un mal usuario intente meterse con tu .bash_profile o hosts, ¡valida el nombre del archivo de salida!)

Crear una interfaz de programa / menú de usuario

La GUI puede ser creada usando JavaFX o Swing. Es aconsejable molestarse con esto después de crear el programa principal. Sin embargo, si usted no tiene tiempo o no quiere gastar tiempo en esto, puede crear un menú de texto simple y mostrarlo en la consola. Por ejemplo:


import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Spec;
import java.util.Locale;
import java.io.File;
                    
@Command(name = "cypher", subcommands = 
{CommandLine.HelpCommand.class },
        description = "Caesar cypher command")
        public class Cypher implements Runnable {
            @Spec CommandSpec spec;
            @Command(name = "encrypt", description = "Encrypt from 
        file to file using key")
            void encrypt(
                        @Parameters(paramLabel = "", description = 
        "source file with text to encrypt") File src,
                        @Parameters(paramLabel = "", description = 
        "dest file which should have encrypted text") File dest,
                        @Parameters(paramLabel = "", description = 
        "key for encryption") int key) {
                // TODO
            }
                    
            @Command(name = "brute force", description = "Decrypt 
        from file to file using brute force") // |3|
            void bruteForce(
                        @Parameters(paramLabel = "", description = 
        "source file with encrypted text") File src,
                        @Option(names = {"-r",
        "--representative"}, description = "file with unencrypted 
        representative text") File representativeFile,
                        @Parameters(paramLabel = "", description = 
        "dest file which should have decrypted text") File dest) {
                // TODO
            }
                    
            @Command(name = "statistical decryption", description = 
        "Decrypt from file to file using statistical analysis") // |3|
            void statisticalDecrypt(
                        @Parameters(paramLabel = "", description = 
        "source file with encrypted text") File src,
                        @Option(names = {"-r", 
        "--representative"}, description = "file with unencrypted 
        representative text") File representativeFile,
                        @Parameters(paramLabel = "", description = 
        "dest file which should have decrypted text") File dest) {
                // TODO
        }
                    
            @Command(name = "decrypt", description = "Decrypt from 
        file to file using statistical analysis") // |3|
            void decrypt(
                        @Parameters(paramLabel = "", description = 
        "source file with encrypted text") File src,
                        @Parameters(paramLabel = "", description = 
        "dest file which should have decrypted text") File dest,
                        @Parameters(paramLabel = "", description = 
        "key for encryption") int key) {
                // TODO
            }
                    
            @Override
            public void run() {
                throw new ParameterException(spec.commandLine(),
        "Specify a subcommand");
            }
                    
            public static void main(String[] args) {
                int exitCode = new CommandLine(new 
        Cypher()).execute(args);
                System.exit(exitCode);
            }
        }
        

Si no quieres lidiar con la biblioteca Picocli, puedes hacerlo de la forma más sencilla posible: crea un menú utilizando bucles o una sentencia switch. También es necesario que el programa termine a petición del usuario (por ejemplo, introduciendo la palabra «exit»).

Trabajar con ficheros

Para trabajar con ficheros, se recomienda utilizar la librería NIO de Java, ya que:

  • Es una API más moderna y con mejores prestaciones que IO.
  • Soporta el procesamiento asíncrono de ficheros.
  • Garantiza el correcto trabajo con ficheros de gran tamaño.

Principales clases NIO para trabajar con ficheros:

Path: Representa la ruta a un fichero o directorio.

Files: Proporciona métodos estáticos para trabajar con ficheros y directorios (leer, escribir, copiar, borrar, etc.).

Charset: Representa la codificación de caracteres utilizada al leer/escribir ficheros de texto.

Ejemplo de lectura de un fichero grande utilizando NIO:


    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.nio.charset.StandardCharsets;
    
    public class FileHandler {
    
        public static String readFile(String filePath) throws IOException {
        Path path = Paths.get(filePath);
        byte[] bytes = Files.readAllBytes(path);
        return new String(bytes, StandardCharsets.UTF_8);
        }
    
        // ... other methods for working with files ...
    
    }

Recomendaciones para trabajar con archivos de gran tamaño:

  • Leer/escribir por partes. Para archivos muy grandes que no quepan en la RAM, utilice los métodos Files.lines() para leer línea por línea o Files.newInputStream() para leer bloque por bloque.
  • Almacenamiento en búfer. Utiliza BufferedReader y BufferedWriter para amortiguar las operaciones de E/S y mejorar el rendimiento.

Decodificación

Para ello necesitas conocer el turno (clave) y el alfabeto.

Para cada carácter del texto cifrado necesitas:

  • comprobar que está en tu alfabeto. Si no es así, los hackers te están descifrando. Entra en pánico (o devuelve el error).
  • encuentra su posición en el alfabeto.
  • encuentra un carácter en una posición desplazada por un desplazamiento dado (pero recuerda: no estás intentando cifrar el cifrado de nuevo, así que lo desplazamos en la otra dirección).
  • sustituir el carácter cifrado por el descifrado.

Utilizarás este código en las siguientes subtareas, para poder enviar el resultado al flujo.

Necesitas guardar el resultado en un archivo.

Pirateo (fuerza bruta)

Puedes utilizar el código que escribiste para descifrar con una clave conocida, sustituyendo todos los valores de clave posibles.

Pero, ¿cómo saber si fue posible descifrarlo? Utiliza un texto de ejemplo (texto representativo del autor o del mismo estilo). Puede compilar un diccionario de palabras y crear una métrica basada en cuántas palabras coinciden y qué longitud tienen; u otra métrica que estudie la longitud de las palabras y las frases, o mirar qué letras preceden más a menudo a qué letras o un diccionario de los comienzos más comunes de una palabra (3 letras), puede no utilizar ningún archivo representativo y comprobar la corrección de la puntuación y los espacios; Guarde la opción con los mejores resultados en un archivo de salida.

Hacking (Análisis estadístico)

Requisitos adicionales(opcional)

Utilice un texto de ejemplo (texto representativo del autor o del mismo estilo) y elabore estadísticas de las letras (por ejemplo, la frecuencia con que aparecen por cada 1000 caracteres). Por cierto. Es fácil descifrar el cifrado sin un archivo y un análisis de este tipo: intenta adivinar el espacio, probablemente el carácter más común en el texto plano.

A continuación, recopila las mismas estadísticas para el texto cifrado. Ten en cuenta que no basta con contar los caracteres, ya que los textos pueden tener distintas longitudes.

A continuación, calcula la desviación de cada desplazamiento posible de la estadística cifrada con respecto a la representativa (para ello puedes utilizar la suma de cuadrados de la desviación o el producto punto de vectores). Encuentra el desplazamiento que dé la desviación mínima y descifra utilizando este desplazamiento.

Se comprueba el proyecto a medida que el grupo lo va realizando