Add Tests for HillCipher (#5562)

This commit is contained in:
Benjamin Burstein
2024-10-04 13:18:51 -04:00
committed by GitHub
parent 389d1d70d5
commit 393337fa8e
2 changed files with 105 additions and 144 deletions

View File

@ -1,178 +1,103 @@
package com.thealgorithms.ciphers; package com.thealgorithms.ciphers;
import java.util.Scanner; public class HillCipher {
/* // Encrypts the message using the key matrix
* Java Implementation of Hill Cipher public String encrypt(String message, int[][] keyMatrix) {
* Hill cipher is a polyalphabetic substitution cipher. Each letter is represented by a number message = message.toUpperCase().replaceAll("[^A-Z]", "");
* belonging to the set Z26 where A=0 , B=1, ..... Z=25. To encrypt a message, each block of n int matrixSize = keyMatrix.length;
* letters (since matrix size is n x n) is multiplied by an invertible n × n matrix, against
* modulus 26. To decrypt the message, each block is multiplied by the inverse of the matrix used
* for encryption. The cipher key and plaintext/ciphertext are user inputs.
* @author Ojasva Jain
*/
public final class HillCipher {
private HillCipher() {
}
static Scanner userInput = new Scanner(System.in);
/* Following function encrypts the message
*/
static void encrypt(String message) {
message = message.toUpperCase();
// Get key matrix
System.out.println("Enter key matrix size");
int matrixSize = userInput.nextInt();
System.out.println("Enter Key/encryptionKey matrix ");
int[][] keyMatrix = new int[matrixSize][matrixSize];
for (int i = 0; i < matrixSize; i++) {
for (int j = 0; j < matrixSize; j++) {
keyMatrix[i][j] = userInput.nextInt();
}
}
// check if det = 0
validateDeterminant(keyMatrix, matrixSize); validateDeterminant(keyMatrix, matrixSize);
int[][] messageVector = new int[matrixSize][1]; StringBuilder cipherText = new StringBuilder();
String cipherText = ""; int[] messageVector = new int[matrixSize];
int[][] cipherMatrix = new int[matrixSize][1]; int[] cipherVector = new int[matrixSize];
int j = 0; int index = 0;
while (j < message.length()) {
while (index < message.length()) {
for (int i = 0; i < matrixSize; i++) { for (int i = 0; i < matrixSize; i++) {
if (j >= message.length()) { if (index < message.length()) {
messageVector[i][0] = 23; messageVector[i] = message.charAt(index++) - 'A';
} else { } else {
messageVector[i][0] = (message.charAt(j)) % 65; messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
} }
System.out.println(messageVector[i][0]);
j++;
} }
int x;
int i;
for (i = 0; i < matrixSize; i++) {
cipherMatrix[i][0] = 0;
for (x = 0; x < matrixSize; x++) { for (int i = 0; i < matrixSize; i++) {
cipherMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0]; cipherVector[i] = 0;
for (int j = 0; j < matrixSize; j++) {
cipherVector[i] += keyMatrix[i][j] * messageVector[j];
} }
System.out.println(cipherMatrix[i][0]); cipherVector[i] = cipherVector[i] % 26;
cipherMatrix[i][0] = cipherMatrix[i][0] % 26; cipherText.append((char) (cipherVector[i] + 'A'));
}
for (i = 0; i < matrixSize; i++) {
cipherText += (char) (cipherMatrix[i][0] + 65);
} }
} }
System.out.println("Ciphertext: " + cipherText);
return cipherText.toString();
} }
// Following function decrypts a message // Decrypts the message using the inverse key matrix
static void decrypt(String message) { public String decrypt(String message, int[][] inverseKeyMatrix) {
message = message.toUpperCase(); message = message.toUpperCase().replaceAll("[^A-Z]", "");
// Get key matrix int matrixSize = inverseKeyMatrix.length;
System.out.println("Enter key matrix size"); validateDeterminant(inverseKeyMatrix, matrixSize);
int n = userInput.nextInt();
System.out.println("Enter inverseKey/decryptionKey matrix ");
int[][] keyMatrix = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
keyMatrix[i][j] = userInput.nextInt();
}
}
// check if det = 0
validateDeterminant(keyMatrix, n);
// solving for the required plaintext message StringBuilder plainText = new StringBuilder();
int[][] messageVector = new int[n][1]; int[] messageVector = new int[matrixSize];
String plainText = ""; int[] plainVector = new int[matrixSize];
int[][] plainMatrix = new int[n][1]; int index = 0;
int j = 0;
while (j < message.length()) { while (index < message.length()) {
for (int i = 0; i < n; i++) { for (int i = 0; i < matrixSize; i++) {
if (j >= message.length()) { if (index < message.length()) {
messageVector[i][0] = 23; messageVector[i] = message.charAt(index++) - 'A';
} else { } else {
messageVector[i][0] = (message.charAt(j)) % 65; messageVector[i] = 'X' - 'A'; // Padding with 'X' if needed
} }
System.out.println(messageVector[i][0]);
j++;
} }
int x;
int i;
for (i = 0; i < n; i++) {
plainMatrix[i][0] = 0;
for (x = 0; x < n; x++) { for (int i = 0; i < matrixSize; i++) {
plainMatrix[i][0] += keyMatrix[i][x] * messageVector[x][0]; plainVector[i] = 0;
for (int j = 0; j < matrixSize; j++) {
plainVector[i] += inverseKeyMatrix[i][j] * messageVector[j];
} }
plainVector[i] = plainVector[i] % 26;
plainMatrix[i][0] = plainMatrix[i][0] % 26; plainText.append((char) (plainVector[i] + 'A'));
}
for (i = 0; i < n; i++) {
plainText += (char) (plainMatrix[i][0] + 65);
} }
} }
System.out.println("Plaintext: " + plainText);
return plainText.toString();
} }
// Determinant calculator // Validates that the determinant of the key matrix is not zero modulo 26
public static int determinant(int[][] a, int n) { private void validateDeterminant(int[][] keyMatrix, int n) {
int det = determinant(keyMatrix, n) % 26;
if (det == 0) {
throw new IllegalArgumentException("Invalid key matrix. Determinant is zero modulo 26.");
}
}
// Computes the determinant of a matrix recursively
private int determinant(int[][] matrix, int n) {
int det = 0; int det = 0;
int sign = 1;
int p = 0;
int q = 0;
if (n == 1) { if (n == 1) {
det = a[0][0]; return matrix[0][0];
} else { }
int[][] b = new int[n - 1][n - 1]; int sign = 1;
for (int x = 0; x < n; x++) { int[][] subMatrix = new int[n - 1][n - 1];
p = 0; for (int x = 0; x < n; x++) {
q = 0; int subI = 0;
for (int i = 1; i < n; i++) { for (int i = 1; i < n; i++) {
for (int j = 0; j < n; j++) { int subJ = 0;
if (j != x) { for (int j = 0; j < n; j++) {
b[p][q++] = a[i][j]; if (j != x) {
if (q % (n - 1) == 0) { subMatrix[subI][subJ++] = matrix[i][j];
p++;
q = 0;
}
}
} }
} }
det = det + a[0][x] * determinant(b, n - 1) * sign; subI++;
sign = -sign;
} }
det += sign * matrix[0][x] * determinant(subMatrix, n - 1);
sign = -sign;
} }
return det; return det;
} }
// Function to implement Hill Cipher
static void hillCipher(String message) {
System.out.println("What do you want to process from the message?");
System.out.println("Press 1: To Encrypt");
System.out.println("Press 2: To Decrypt");
short sc = userInput.nextShort();
if (sc == 1) {
encrypt(message);
} else if (sc == 2) {
decrypt(message);
} else {
System.out.println("Invalid input, program terminated.");
}
}
static void validateDeterminant(int[][] keyMatrix, int n) {
if (determinant(keyMatrix, n) % 26 == 0) {
System.out.println("Invalid key, as determinant = 0. Program Terminated");
}
}
// Driver code
public static void main(String[] args) {
// Get the message to be encrypted
System.out.println("Enter message");
String message = userInput.nextLine();
hillCipher(message);
}
} }

View File

@ -0,0 +1,36 @@
package com.thealgorithms.ciphers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class HillCipherTest {
HillCipher hillCipher = new HillCipher();
@Test
void hillCipherEncryptTest() {
// given
String message = "ACT"; // Plaintext message
int[][] keyMatrix = {{6, 24, 1}, {13, 16, 10}, {20, 17, 15}}; // Encryption key matrix
// when
String cipherText = hillCipher.encrypt(message, keyMatrix);
// then
assertEquals("POH", cipherText);
}
@Test
void hillCipherDecryptTest() {
// given
String cipherText = "POH"; // Ciphertext message
int[][] inverseKeyMatrix = {{8, 5, 10}, {21, 8, 21}, {21, 12, 8}}; // Decryption (inverse key) matrix
// when
String plainText = hillCipher.decrypt(cipherText, inverseKeyMatrix);
// then
assertEquals("ACT", plainText);
}
}