Hill密码(Java)
Hill密码是一种传统的密码体系。加密原理:选择一个二阶可逆整数矩阵A称为密码的加密矩阵,也就是这个加密体系的密钥。加密过程:
明文字母依次逐对分组,例如加密矩阵为二阶矩阵,明文就两个字母一组,如果最后一组不足(明文长度为奇数),就补充任意字母凑个双,构成二维向量组a。计算矩阵A乘以向量组a,得到新的二维列向量b,反查字母表得到两个字母即为密文字母。
也就是说,加密过程为:明文-->映射为数字矩阵-->经过矩阵加密-->映射为字符串(密文)
解密过程也是同样的过程,只不过中间使用矩阵解密,Hill密码是一种传统的密码体系。
根据这个过程,每一阶段功能代码如下:
首先创建一个类,HillCrypto,
成员变量有加密密钥矩阵和解密密钥矩阵,字母转数值映射和数值转字母映射
初始化阶段,实例化以上成员变量,其中映射表较大,因此写在了本地文件中便于重用,创建映射时需要读取本地文件。
文件内容如下:
代码如下:
import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class HillCrypto { private Map<Character, Integer> table; private Map<Integer, Character> getPlainMap; private int[][] encryption = {{1, 1},{0, 3}}; private int[][] decryption; public HillCrypto(String tableFilePath) { // TODO Auto-generated constructor stub int mrow = encryption.length;int mcolumn = encryption[0].length; this.decryption = new int[mrow][mcolumn]; // 二阶矩阵的逆矩阵,如果是更高阶的,使用其他办法,比如通过余子式除以行列式得到逆矩阵,行列式的求法见鄙人的其他博客。 decryption[0][0] = (encryption[1][1] * 27 / (encryption[0][0]*encryption[1][1] - encryption[0][1]*encryption[1][0])) % 26; decryption[0][1] = - (encryption[0][1] * 27 / (encryption[0][0]*encryption[1][1] - encryption[0][1]*encryption[1][0])) % 26; decryption[1][0] = - (encryption[1][0] * 27 / (encryption[0][0]*encryption[1][1] - encryption[0][1]*encryption[1][0])) % 26; decryption[1][1] = (encryption[0][0] * 27 / (encryption[0][0]*encryption[1][1] - encryption[0][1]*encryption[1][0])) % 26; // 该算法的所有矩阵在求出之后都需要取余数 for (int i = 0; i < decryption.length; i++) { for (int j = 0; j < decryption[0].length; j++) { if (decryption[i][j] < 0) { decryption[i][j] += 26; } } } this.print(decryption); this.table = this.readFile(tableFilePath); this.getPlainMap = this.mapReverse(table); } private void print(int[][] matrix) { for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[0].length; j++) { System.out.print(matrix[i][j]+", "); } System.out.println(); } } // map的键值互换 private Map<Integer, Character> mapReverse(Map<Character, Integer> table){ Iterator<Character> it = table.keySet().iterator(); Map<Integer, Character> result = new HashMap<Integer, Character>(); while (it.hasNext()) { Character character = (Character) it.next(); result.put(table.get(character), character); } return result; } /** * 从本地读取一个文件以创建字母值表,例如A->0, B->1,...,Z->25 * @param tableFilePath * @return */ private Map<Character, Integer> readFile(String tableFilePath) { File file = new File(tableFilePath); FileReader fr; Map<Character, Integer> map = new HashMap<Character, Integer>(); try { fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); String line = ""; String[] kv = null; while ((line = br.readLine())!= null) { kv = line.split(","); // System.out.println("读取键值对:<"+kv[0]+", "+kv[1]+">"); map.put(kv[0].charAt(0), Integer.parseInt(kv[1])); } br.close(); fr.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; } }
密文是字符串,需要根据所读取的映射表,两两一组转换为数字矩阵:
/** * 由密文根据映射表转换为二维向量 * @return */ private int[][] getVectorFromString(String text){ int textLength = text.length(); // System.out.println("密文长度为:" + textLength); int row = textLength; if (row % 2 != 0) { row = (row + 1) / 2; int column = this.encryption.length; int[][] vector = new int[row][column]; for (int i = 0; i < row-1; i++) { for (int j = 0; j < column; j++) { vector[i][j] = this.table.get(text.charAt(i*column + j)); } } vector[row-1][column-2] = this.table.get(text.charAt((row-1)*column + column-2)); // this.print(vector); vector[row-1][column-1] = this.table.get('A'); return this.transpose(vector); }else { row = row / 2; int column = this.encryption.length; int[][] vector = new int[row][column]; for (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { vector[i][j] = this.table.get(text.charAt(i*column + j)); } } return this.transpose(vector); } } // 求矩阵转置 public int[][] transpose(int[][] matrix){ int row = matrix.length; int column = matrix[0].length; int[][] newmatrix = new int[column][row]; for (int i = 0; i < row; i++) { for (int j = 0; j < column; j++) { newmatrix[j][i] = matrix[i][j]; } } return newmatrix; }
加密或者解密都需要用到矩阵乘法
// 求矩阵乘以向量的结果 public int[][] transform(int[][] matrix, int[][] vector) { int mrow = matrix.length; int mcolumn = matrix[0].length; int column = vector[0].length; int[][] result = new int[mcolumn][column]; for (int k = 0; k < column; k++) { for (int i = 0; i < mcolumn; i++) { for (int j = 0; j < mrow; j++) { result[i][k] += matrix[i][j] * vector[j][k]; // System.out.printf("result[%d][%d] = %d\n", i, k, result[i][k]); } } } for (int i = 0; i < mcolumn; i++) { for (int j = 0; j < column; j++) { result[i][j] %= 26; } } return result; }
从数字矩阵中反查映射表得到字符串
public char[] getPlain(int[][] table) { int row = table.length; int column = table[0].length; char[] plaintext = new char[row*column]; for (int i = 0; i < column; i++) { for (int j = 0; j < row; j++) { plaintext[i*row+j] = this.getPlainMap.get(table[j][i]); } } return plaintext; }
然后用一个实例演示加密然后解密的过程:
public static void main(String[] args) { String tableFilePath = "src/zhaoke/table.csv"; // 加密密钥 String originPlain = "JAVAISTHEBESTLANGUAGEINTHEWORLD"; System.out.println("明文:"+originPlain); HillCrypto hc = new HillCrypto(tableFilePath); // 加密过程 // 首先字符串映射为数值 int[][] plainNum = hc.getVectorFromString(originPlain); // hc.print(plainNum); // 然后用加密矩阵进行加密 int[][] encryptedPlain = hc.transform(hc.encryption, plainNum); // 然后映射为字符串就是密文了 String cipherPlain = new String(hc.getPlain(encryptedPlain)); System.out.println("加密后的密文"+cipherPlain); // 解密过程 // 首先映射为数值 int[][] cipherNum = hc.getVectorFromString(cipherPlain); // hc.print(cipherNum); // 使用解密矩阵进行解密 int[][] newtable = hc.transform(hc.decryption, cipherNum); // 然后映射为明文 String plainText = new String(hc.getPlain(newtable)); System.out.println("解密所得明文为: "+plainText); }
执行结果:
明文:JAVAISTHEBESTLANGUAGEINTHEWORLD
加密后的密文KCWCBEBXGFXEFJOPBKHUNAHHMOLSDJEC
解密所得明文为: JAVAISTHEBESTLANGUAGEINTHEWORLDA
可以看到结果正确,说明算法是有效的,至于为什么最终会多出一个A,那是因为明文长度是奇数,为了能映射为二维向量需要凑整,查表阶段加上了一个随机字母凑双因此多了一个A,但是这不影响我们能看出明文就是"Java is the best language in the world"。