古典密码学虽然现在已经不再使用,但其反映了密码设计和破译的基本思想,是学习密码学的入口。
古典密码学主要有两种体制:置换密码和代换密码。
置换密码
根据一定的规则重新排列明文,以便打破原有的结构特性。即改变字符的原始位置,但字符还是那些字符。
- 列置换:比如明文m = “Beijing 2008 Olympic Games”,
密钥=(1 4 3)(5 6)。
该密钥表示:一个括号一个循环,f(1) = 4,f(4) = 3 , f(3) = 1,这里3是括号的末尾,所以f(3)的值就循环到了括号的最前面,即1。(5 6)类似,f(5) = 6 , f(6) = 5。少了2,未表示出来的等于其自身,所以f(2) = 2。其中f(a) = b 表示 a 列和 b 列的数据交换。由于它最大的数是6,则表示分组长度是6,那么将明文m写成如下形式:
B e i j i n
g 2 0 0 8 O
l y m p i c
G a m e s *
‘*’号为填充,经过加密后结果为:
j e B i n i
0 2 g 0 O 8
p y l m c i
e a G m * s
获得密文c=“jeBini02g0O8pylmcieaGm*s”
解密时需要变换密钥,(1 4 3)的逆置换为(1 3 4),变换规则:对于每一个括号,第一个数字不变,后面反转。例:(1 2 3 4 5) -》 (1 5 4 3 2)。是不是很简单。那么上一个解密密钥=(1 3 4)(5 6),容易由密文再根据解密密钥获得明文。
写了代码试了下,仅供参考:
package Classical;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Permutation {
// private static final int MAXCOL = 1024;
public static void main(String[] args) {
// TODO Auto-generated method stub
String c = String.valueOf(columnEncry("Beijing 2008 Olympic Games", "(1 4 3)(5 6)"));
System.out.println("密文:"+c.replace(" ",""));
String m = String.valueOf(columnEncry(c,"(1 3 4)(5 6)"));
System.out.println("明文:"+m.replace(" ",""));
}
/*
*@author 孤舟一叶
*@param in表示要加密的数据,key表示密钥,形如(1 4 3)(5 6)
*@return 返回密文
*/
public static StringBuilder columnEncry(String in, String key){
int colNum = 0;
Map<Integer,Integer> relation = new HashMap<Integer,Integer>();
String[] keySplited = key.split("[(]");
for(int i = 1 ; i < keySplited.length ; i++){
int len = keySplited[i].length()-1;
keySplited[i] = keySplited[i].substring(0, len);
String[] block = keySplited[i].split(" ");
int[] intBlock = new int[block.length];
for(int j = 0 ; j < block.length ; j++){
intBlock[j] = Integer.valueOf(block[j]);
if(colNum < intBlock[j]){
colNum = intBlock[j];
}
}
int lenOfBlock = block.length;
for(int m =0 ; m < lenOfBlock-1 ; m++){
relation.put(intBlock[m], intBlock[m+1]);
}
relation.put(intBlock[lenOfBlock-1],intBlock[0]);
}
String inNoSpace = in.replace(" ", "");
StringBuilder inNoSpaceAddEle = new StringBuilder(inNoSpace);
int supplement = (colNum - inNoSpace.length()%colNum)%colNum;
for(int i = 0 ; i < supplement ; i++){
inNoSpaceAddEle.append("*");//如果添加“”,长度不会增加
}
StringBuilder out = new StringBuilder();
for(int i = 0 ; i < inNoSpaceAddEle.length() ; i++){
int from = 0;
if(relation.get(i % colNum + 1)!= null){
from = relation.get(i % colNum + 1)-1;
out.append(inNoSpaceAddEle.charAt(i/colNum*colNum+from));
}
else{
out.append(inNoSpaceAddEle.charAt(i));
}
}
return out;
}
}
运行结果:
密文:jeBini02g0O8pylmcieaGm*s
明文:Beijing2008OlympicGames*
- 周期置换
我认为其原理和列置换完全相同,真的不理解书中为什么将其叫做两个名字。如果非要说不同,那只有列置换将明文竖向排列,周期置换将明文横向排列了。还是上个例子中的明文,周期置换这么处理:
(Beijin)(g2008O)(lympic)(Games*)经过密钥=(1 4 3)(5 6)处理后,密文:
(jeBini)(02g0O8) (pylmci)(eaGm*s)
代换密码
使用其他字符代替明文字符,密文的字符不是由原来那些字符组成了。
单表代换
- 基于密钥的单表代换:最简单查表,找出明文对应的密文。
- 仿射密码:加密时,明文经过一个线性变换 y = e(x) = ax + b (mod 26),将明文字符变换为其他字符。解密时,需要先求解 a mod 26 的乘法逆元 a^-1,然后 x = a^-1[e(x)-b] (mod26) 求解明文。相关需要的数学知识包括:欧几里得求解最大公约数,扩展欧几里得算法求解乘法逆元。
代码如下:
package Classical;
public class Substitution {
public static void main(String[] args) {
// TODO Auto-generated method stub
String c = affine("sorcery",11,6);
System.out.println("密文:"+c);
int inverseOfa = mulInverse(26,11);
String m = affine(c,inverseOfa,((-6*inverseOfa)%26+26)%26);
System.out.println("明文:"+m);
}
/*
* @author 孤舟一叶
* @param m:明文;a,b仿射函数参数,其中a和26互素
* @return 返回密文
*/
public static String affine(String m,int a,int b){
if(!isPrimeWith26(a)){
return "仿射函数必须满足gcd(26,a)=1";
}
m = m.toUpperCase();
StringBuilder out = new StringBuilder();
for(int i = 0; i < m.length(); i++){
int ch = m.charAt(i);
if(ch >= 65 && ch <= 90){
ch = ch - 65;
out.append((char)((ch * a + b)%26+65));
}
else
out.append((char)ch);
}
return String.valueOf(out);
}
private static boolean isPrimeWith26(int a){
if(a == 1)return true;
int m = 26 ,n = a;
while(m%n > 1){
int temp = m%n;
m = n;
n = temp;
}
if(m%n == 1)return true;
return false;
}
/*
* @求解乘法逆元
* @param 需要求解b mod a的乘法逆元
* @return 乘法逆元
*/
private static int mulInverse(int a , int b){
if(0 == a || 0 == b){
return -1;
}
int x1 = 1 , x2 = 0 , x3 = a;
int y1 = 0 , y2 = 1 , y3 = b;
int t1 = 0 , t2 = 0 , t3 = 0;
int n;
for(t3 = x3 % y3 ; t3 != 0 ; t3 = x3 % y3){
n = x3 / y3;
t1 = x1 ; t2 = x2;
x1 = y1; x2 = y2 ; x3 = y3;
y1 = t1 - n * y1 ; y2 = t2 - n * y2 ; y3 = t3;
}
if(1 == y3){
return (y2%a+a)%a;
}
return -1;
}
}
运行结果:
密文:WELCYLK
明文:SORCERY
多表代换
多表代换典型的有三种:Playfair、维吉尼亚密码、Hill密码。
这里给出维吉尼亚的代码,其他的读者可自行学习。
package Classical;
public class Vigenere {
public static void main(String[] args) {
// TODO Auto-generated method stub
String c = enCry("cyber greatwall","iscbupt");
System.out.println("密文:"+c);
String m = deCry(c,"iscbupt");
System.out.println("明文:"+m);
}
public static String enCry(String in , String key){
in = in.replace(" ", "");
in = in.toLowerCase();
key = key.toLowerCase();
StringBuilder out = new StringBuilder();
for(int i =0 ; i < in.length() ;i++){
out.append((char)((in.charAt(i)-97+key.charAt(i%key.length())-97)%26+97));
}
return String.valueOf(out);
}
public static String deCry(String in , String key){
in = in.replace(" ", "");
in = in.toLowerCase();
key = key.toLowerCase();
StringBuilder out = new StringBuilder();
for(int i =0 ; i < in.length() ;i++){
out.append((char)(((in.charAt(i)-key.charAt(i%key.length()))%26+26)%26+97));
}
return String.valueOf(out);
}
}
运行结果:
密文:kqdflvkmsvxuae
明文:cybergreatwall
分析方法
统计分析法:某种语言各个字符出现的频率不同,比如英语中 E 的出行频率最高。可以根据字母密文中各个字符出现的频率不同,分析出该密文字符代表的明文字符。对单表代换有用。
重合指数法:利用随机文本和英文文本的统计概率差别分析密钥长度。
明文-密文对分析法:由于 Hill密码 对重合指数法和统计分析法具有抵抗性,可使用明文-密文对分析法破译。
至此,古典密码介绍告一段落,接下来我们将进入现代密码的学习。