Cifrado de Vigenère con el alfabeto español
Implementa el cifrado de Vigenère sobre las 27 letras del alfabeto español (con la Ñ): claves validadas, cifrar y descifrar conservando mayúsculas, minúsculas y signos, y un análisis de frecuencias de letras. Cadenas, char, aritmética modular y métodos.
- String y StringBuilder
- char y Character
- indexOf
- Aritmética modular
- Métodos con parámetros
- Arrays de contadores
Enunciado
El cifrado de Vigenère se usó durante siglos para mensajes militares y diplomáticos. Funciona como el de César (desplazar cada letra unas posiciones en el alfabeto), pero el desplazamiento cambia en cada letra según una palabra clave: con la clave LIMON, la primera letra se desplaza 11 posiciones (la L), la segunda 8 (la I), y así sucesivamente, volviendo a empezar la clave cuando se acaba.
La mayoría de las implementaciones usan el alfabeto inglés de 26 letras. Aquí se usa el español, con la Ñ entre la N y la O: 27 letras en total, de la A (posición 0) a la Z (posición 26). Al desplazar más allá de la Z se vuelve a empezar por la A.
Los espacios, los números, los signos y las vocales con tilde no se cifran: se copian tal cual y no gastan letra de la clave. Las minúsculas se cifran como su mayúscula y siguen siendo minúsculas.
Qué tiene que hacer el programa
- Cada línea es una orden seguida de su argumento (todo lo que hay tras el primer espacio). La orden puede ir en mayúsculas o minúsculas; las líneas vacías se ignoran. Una orden desconocida escribe
Orden desconocida: ORDEN(en mayúsculas). CLAVE palabraguarda la clave si todas sus letras son del alfabeto (en mayúsculas o minúsculas, la Ñ incluida) y escribeClave guardada (N letras)(«letra» si es una). Si no,Clave no válida: solo letras de la A a la Z y la Ñy se mantiene la clave anterior.CIFRAR textoescribeCifrado: …: cada letra del alfabeto se desplaza hacia delante tantas posiciones como la posición de la letra de la clave que le toca. La clave vuelve a empezar en cada orden y solo avanza con las letras que se cifran.DESCIFRAR textoescribeDescifrado: …deshaciendo el desplazamiento.- Si no hay clave guardada,
CIFRARyDESCIFRARescribenFalta la clave; si hay clave pero no texto,Falta el texto. ANALIZAR textoescribeLetras: N · Más frecuentes: A 5, B 3, C 2: el total de letras del alfabeto del texto (sin distinguir mayúsculas) y las tres más repetidas con su número de apariciones, de más a menos (si hay empate, en el orden del alfabeto; si hay menos de tres letras distintas, las que haya). Si no hay ninguna letra, soloLetras: 0.
Entrada
Una orden por línea: CLAVE, CIFRAR, DESCIFRAR o ANALIZAR, seguida de su argumento.
Datos de referencia
| A | B | C | D | E | F | G | H | I | J | K | L | M | N | Ñ | O | P | Q | R | S | T | U | V | W | X | Y | Z |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Cifrar, descifrar y analizar
Entrada
CIFRAR hola CLAVE Limón CLAVE Limon CIFRAR Ataque al amanecer, ¡ya! DESCIFRAR Lbmfho iw oylupqqc, ¡gm! ANALIZAR El pingüino Ñoño come ñoquis
Salida por consola
Falta la clave Clave no válida: solo letras de la A a la Z y la Ñ Clave guardada (5 letras) Cifrado: Lbmfho iw oylupqqc, ¡gm! Descifrado: Ataque al amanecer, ¡ya! Letras: 23 · Más frecuentes: O 5, I 3, Ñ 3
Órdenes en minúsculas
Entrada
clave sol cifrar descifrar Zzz zzz analizar ab ba analizar 1234 ¿? borrar todo
Salida por consola
Clave guardada (3 letras) Falta el texto Descifrado: Hlo hlo Letras: 4 · Más frecuentes: A 2, B 2 Letras: 0 Orden desconocida: BORRAR
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Separar la orden del argumento
El texto a cifrar puede tener espacios, así que no sirve split(" "): busca el primer espacio con indexOf(' ') y corta con substring. Lo que queda antes es la orden y lo de después, el argumento.
2. La posición de cada letra
Guarda el alfabeto en una constante String y usa indexOf para pasar de letra a número y charAt para volver. indexOf devuelve -1 si el carácter no es una letra del alfabeto: así sabes qué hay que copiar tal cual.
int pos = ALFABETO.indexOf(Character.toUpperCase(c));
if (pos < 0) { sb.append(c); continue; }3. Desplazar con módulo
La letra cifrada es (pos + desplazamiento) % 27. Al descifrar restas, y el resultado puede ser negativo: en Java -3 % 27 vale -3, no 24. Sumar 27 antes del último módulo lo arregla en los dos sentidos con la misma fórmula.
char nueva = ALFABETO.charAt(((pos + sentido * desplazamiento) % N + N) % N);4. La clave solo avanza con letras
Lleva un contador k aparte del índice del texto y súmale 1 solo cuando cifres una letra. La letra de la clave que toca es clave.charAt(k % clave.length()).
5. Las tres más frecuentes
Cuenta las apariciones en un int[27]. Para sacar las tres mayores sin ordenar, repite tres veces: busca la mayor que no hayas usado todavía (con > estricto gana la primera del alfabeto en un empate) y márcala como usada.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
CIFRAR hola CLAVE Limón CLAVE Limon CIFRAR Ataque al amanecer, ¡ya! DESCIFRAR Lbmfho iw oylupqqc, ¡gm! ANALIZAR El pingüino Ñoño come ñoquis
Falta la clave Clave no válida: solo letras de la A a la Z y la Ñ Clave guardada (5 letras) Cifrado: Lbmfho iw oylupqqc, ¡gm! Descifrado: Ataque al amanecer, ¡ya! Letras: 23 · Más frecuentes: O 5, I 3, Ñ 3
Solución explicada
Ver la solución completa
1import java.util.Scanner;
2
3public class Main {
4 static final String ALFABETO = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ";
5 static final int N = ALFABETO.length(); // 27 letras
6
7 /** La clave en mayúsculas, o null si tiene algo que no sea una letra del alfabeto. */
8 static String validarClave(String texto) {
9 String clave = texto.toUpperCase();
10 if (clave.isEmpty()) return null;
11 for (char c : clave.toCharArray()) {
12 if (ALFABETO.indexOf(c) < 0) return null;
13 }
14 return clave;
15 }
16
17 /** Cifra (sentido = 1) o descifra (sentido = -1). Solo cambian las letras del alfabeto, que conservan su caja. */
18 static String vigenere(String texto, String clave, int sentido) {
19 StringBuilder sb = new StringBuilder();
20 int k = 0; // posición en la clave: solo avanza con las letras
21 for (char c : texto.toCharArray()) {
22 int pos = ALFABETO.indexOf(Character.toUpperCase(c));
23 if (pos < 0) {
24 sb.append(c);
25 continue;
26 }
27 int desplazamiento = ALFABETO.indexOf(clave.charAt(k % clave.length()));
28 char nueva = ALFABETO.charAt(((pos + sentido * desplazamiento) % N + N) % N);
29 sb.append(Character.isLowerCase(c) ? Character.toLowerCase(nueva) : nueva);
30 k++;
31 }
32 return sb.toString();
33 }
34
35 /** «Letras: 23 · Más frecuentes: O 5, Ñ 3, E 2». */
36 static String analizar(String texto) {
37 int[] veces = new int[N];
38 int letras = 0;
39 for (char c : texto.toCharArray()) {
40 int pos = ALFABETO.indexOf(Character.toUpperCase(c));
41 if (pos >= 0) {
42 veces[pos]++;
43 letras++;
44 }
45 }
46 if (letras == 0) return "Letras: 0";
47 StringBuilder sb = new StringBuilder("Letras: " + letras + " · Más frecuentes: ");
48 boolean[] usada = new boolean[N];
49 for (int vuelta = 0; vuelta < 3; vuelta++) {
50 int mejor = -1;
51 for (int i = 0; i < N; i++) {
52 // Con «>» gana la primera en el alfabeto si hay empate
53 if (!usada[i] && veces[i] > 0 && (mejor < 0 || veces[i] > veces[mejor])) mejor = i;
54 }
55 if (mejor < 0) break;
56 usada[mejor] = true;
57 if (vuelta > 0) sb.append(", ");
58 sb.append(ALFABETO.charAt(mejor)).append(' ').append(veces[mejor]);
59 }
60 return sb.toString();
61 }
62
63 public static void main(String[] args) {
64 Scanner sc = new Scanner(System.in);
65 String clave = null;
66 while (sc.hasNextLine()) {
67 String linea = sc.nextLine().trim();
68 if (linea.isEmpty()) continue;
69 int espacio = linea.indexOf(' ');
70 String orden = (espacio < 0 ? linea : linea.substring(0, espacio)).toUpperCase();
71 String argumento = espacio < 0 ? "" : linea.substring(espacio + 1).trim();
72
73 switch (orden) {
74 case "CLAVE" -> {
75 String nueva = validarClave(argumento);
76 if (nueva == null) {
77 System.out.println("Clave no válida: solo letras de la A a la Z y la Ñ");
78 } else {
79 clave = nueva;
80 System.out.println("Clave guardada (" + clave.length() + (clave.length() == 1 ? " letra)" : " letras)"));
81 }
82 }
83 case "CIFRAR", "DESCIFRAR" -> {
84 if (clave == null) System.out.println("Falta la clave");
85 else if (argumento.isEmpty()) System.out.println("Falta el texto");
86 else if (orden.equals("CIFRAR")) System.out.println("Cifrado: " + vigenere(argumento, clave, 1));
87 else System.out.println("Descifrado: " + vigenere(argumento, clave, -1));
88 }
89 case "ANALIZAR" -> System.out.println(analizar(argumento));
90 default -> System.out.println("Orden desconocida: " + orden);
91 }
92 }
93 }
94}Toda la lógica del cifrado está en un único método, vigenere, que cifra o descifra según el signo del parámetro sentido. Descifrar es desplazar hacia atrás, así que no hace falta escribir dos algoritmos.
El alfabeto como cadena convierte el problema en aritmética: indexOf da la posición, la suma con módulo hace la vuelta de la Z a la A, y charAt devuelve la letra. Con la Ñ dentro, el módulo es 27 en lugar de 26; nada más cambia.
La expresión ((x % N) + N) % N es la forma segura de calcular un módulo que nunca sea negativo en Java, C o JavaScript, donde el operador % conserva el signo del dividendo.
El análisis de frecuencias es la herramienta con la que se rompen estos cifrados: en español la E y la A son las letras más frecuentes, y en un texto largo cifrado con una clave corta esas frecuencias se pueden recuperar letra a letra de la clave.
Para ir más allá
- Añade
CESAR n texto(Vigenère con una clave de una sola letra) y comprueba que es un caso particular. - Rompe un cifrado de César por fuerza bruta: prueba los 27 desplazamientos y elige el que más letras E y A produzca.
- Admite vocales con tilde cifrándolas como su vocal sin tilde y conservando la tilde al descifrar.