密码学DAY1_02
- 1.1 ASCII编码
- 1.2 凯撒加密
- 1.2.1 中国古代加密
- 1.2.2 外国加密
- 1.2.3 凯撒位移加密--JAVA代码实现
- 1.2.4 频度分析法破解恺撒加密
- 1.3现代常用的加密方式
- 1.3.1 对称加密
- 1.3.2 DES加密
- 1.3.3 DES解密
- 1.3.4 AES加密解密
- 1.4 toString()与new String ()用法区别
1.1 ASCII编码
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
1.2 凯撒加密
1.2.1 中国古代加密
看一个小故事 , 看看古人如何加密和解密:
公元683年,唐中宗即位。随后,武则天废唐中宗,立第四子李旦为皇帝,但朝政大事均由她自己专断。
裴炎、徐敬业和骆宾王等人对此非常不满。徐敬业聚兵十万,在江苏扬州起兵。裴炎做内应,欲以拆字手段为其传递秘密信息。后因有人告密,裴炎被捕,未发出的密信落到武则天手中。这封密信上只有“青鹅”二字,群臣对此大惑不解。
武则天破解了“青鹅”的秘密:“青”字拆开来就是“十二月”,而“鹅”字拆开来就是“我自与”。密信的意思是让徐敬业、骆宾王等率兵于十二月进发,裴炎在内部接应。“青鹅”破译后,裴炎被杀。接着,武则天派兵击败了徐敬业和骆宾王。
之前有说过古代密码学主要是替换和移位两种操作,凯撒加密属性移位加密。
替换法一般有单表替换法和多表替换法。
1.2.2 外国加密
在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。
凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:
将明文字母表向后移动1位,A变成了B,B变成了C……,Z变成了A。同理,若将明文字母表向后移动3位:
则A变成了D,B变成了E……,Z变成了C。
字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。
它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。
例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推。
这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。
恺撒密码通常被作为其他更复杂的加密方法中的一个步骤。
简单来说就是当秘钥为n,其中一个待加密字符ch,加密之后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。
1.2.3 凯撒位移加密--JAVA代码实现
创建类 KaiserDemo,把 hello world 往右边移动3位
public static void main(String[] args) {
// 定义原文
String input = "Hello World";
// 把原文右边移动3位
int key = 3;
// 凯撒加密
String s = encrypt(input,key);
System.out.println("加密==" + s);
String s1 = decrypt(s,key);
System.out.println("明文=="+s1);
}
加入自定义的秘钥加密和解密过程:
/**
* 解密
* @param s 密文
* @param key 密钥
* @return
*/
public static String decrypt(String s, int key) {
char[] chars = s.toCharArray();
StringBuilder sb = new StringBuilder();
for (char aChar : chars) {
int b = aChar;
// 偏移数据
b -= key;
char newb = (char) b;
sb.append(newb);
}
return sb.toString();
}
/**
* 加密
* @param input 原文
* @return
*/
public static String encrypt(String input,int key) {
// 抽取快捷键 ctrl + alt + m
// 把字符串变成字节数组
char[] chars = input.toCharArray();
StringBuilder sb = new StringBuilder();
for (char aChar : chars) {
int b = aChar;
// 往右边移动3位
b = b + key;
char newb = (char) b;
sb.append(newb);
}
// System.out.println("密文==="+sb.toString());
return sb.toString();
}
1.2.4 频度分析法破解恺撒加密
凯撒加密其实很容易从密文中猜测出明文所使用的秘钥,当然只适用于英文文本的情况,毕竟咱们汉字做不到位移操作,最多拆字和组合。
如果是英文这难不倒解密者,以英文字母为例,为了确定每个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是e,接下来是t,然后是a……,然后检查要破解的密文,也将每个字母出现的频率整理出来,假设密文中出现频率最高的字母是j,那么就可能是e的替身,如果密码文中出现频率次高的但是P,那么可能是t的替身,以此类推便就能解开加密信息的内容。这就是频率分析法。
1.将明文字母的出现频率与密文字母的频率相比较的过程
2.通过分析每个符号出现的频率而轻易地破译代换式密码
3.在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率。
4.e是英语中最常用的字母,其出现频率为八分之一
有了这份频率分析那么我们就可以对密文所对应的KEY进行一个猜测。
我们这里写了一个频率分析类FrequencyAnalysis,具体如下:
/**
* 频率分析法破解凯撒密码
*/
public class FrequencyAnalysis {
//英文里出现次数最多的字符
private static final char MAGIC_CHAR = 'e';
//破解生成的最大文件数
private static final int DE_MAX_FILE = 4;
public static void main(String[] args) throws Exception {
//测试1,统计字符个数
// printCharCount("article_en.txt");
//加密文件
int key = 3;
// encryptFile("article.txt", "article_en.txt", key);
//读取加密后的文件
String artile = Util.file2String("article_en.txt");
//解密(会生成多个备选文件)
decryptCaesarCode(artile, "article_de.txt");
}
public static void printCharCount(String path) throws IOException{
String data = Util.file2String(path);
List> mapList = getMaxCountChar(data);
for (Entry entry : mapList) {
//输出前几位的统计信息
System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");
}
}
public static void encryptFile(String srcFile, String destFile, int key) throws IOException {
String artile = Util.file2String(srcFile);
//加密文件
String encryptData = KaiserDemo.encrypt(artile, key);
//保存加密后的文件
Util.string2File(encryptData, destFile);
}
/**
* 破解凯撒密码
* @param input 数据源
* @return 返回解密后的数据
*/
public static void decryptCaesarCode(String input, String destPath) {
int deCount = 0;//当前解密生成的备选文件数
//获取出现频率最高的字符信息(出现次数越多越靠前)
List> mapList = getMaxCountChar(input);
for (Entry entry : mapList) {
//限制解密文件备选数
if (deCount >= DE_MAX_FILE) {
break;
}
//输出前几位的统计信息
System.out.println("字符'" + entry.getKey() + "'出现" + entry.getValue() + "次");
++deCount;
//出现次数最高的字符跟MAGIC_CHAR的偏移量即为秘钥
int key = entry.getKey() - MAGIC_CHAR;
System.out.println("猜测key = " + key + ", 解密生成第" + deCount + "个备选文件" + "\n");
String decrypt = KaiserDemo.decrypt(input, key);
String fileName = "de_" + deCount + destPath;
Util.string2File(decrypt, fileName);
}
}
//统计String里出现最多的字符
public static List> getMaxCountChar(String data) {
Map map = new HashMap();
char[] array = data.toCharArray();
for (char c : array) {
if(!map.containsKey(c)) {
map.put(c, 1);
}else{
Integer count = map.get(c);
map.put(c, count + 1);
}
}
//输出统计信息
/*for (Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + "出现" + entry.getValue() + "次");
}*/
//获取获取最大值
int maxCount = 0;
for (Entry entry : map.entrySet()) {
//不统计空格
if (/*entry.getKey() != ' ' && */entry.getValue() > maxCount) {
maxCount = entry.getValue();
}
}
//map转换成list便于排序
List> mapList = new ArrayList>(map.entrySet());
//根据字符出现次数排序
Collections.sort(mapList, new Comparator>(){
public int compare(Entry o1,
Entry o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
return mapList;
}
}
这里用到的原文如下:
My father was a self-taught mandolin player. He was one of the best string instrument players in our town. He could not read music, but if he heard a tune a few times, he could play it. When he was younger, he was a member of a small country music band. They would play at local dances and on a few occasions would play for the local radio station. He often told us how he had auditioned and earned a position in a band that featured Patsy Cline as their lead singer. He told the family that after he was hired he never went back. Dad was a very religious man. He stated that there was a lot of drinking and cursing the day of his audition and he did not want to be around that type of environment.
运行 FrequencyAnalysis.java 里面 main 函数里面的 encryptFile 方法 对程序进行加密。在根目录会生成一个 article_en.txt 文件,然后我们统计这个文件当中每个字符出现的次数。
这就是密文中的频率分析情况,进行猜测的原则是从高到低,先从“#”开始,假设它对应的字母就是’e'。那么密文所对应的秘钥就是-66(参照ASCII表)
我们就可以以key=-66对密文进行一个解密,重复此步骤,直到得到正确的原文。
运行程序:
显然第二个猜测文件就已经正确了。
1.3现代常用的加密方式
1.3.1 对称加密
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
- 示例:
- 我们现在有一个原文3要发送给B
- 设置密钥为108, 3 * 108 = 324, 将324作为密文发送给B
- B拿到密文324后, 使用324/108 = 3 得到原文
- 常见加密算法:
- DES : Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
- AES : Advanced Encryption Standard, 高级加密标准 .在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
- 特点:
- 加密速度快, 可以加密大文件
- 密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露
- 加密后编码表找不到对应字符, 出现乱码
- 一般结合Base64使用
1.3.2 DES加密
这里的JAVA代码展示用到了一个类Cipher.
Cipher :文档 https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html#getInstance-java.lang.String-
public class DesAesDemo {
public static void main(String[] args) throws Exception{
// 原文
String input = "硅谷";
// des加密必须是8位
String key = "12345678";
// 算法
String algorithm = "DES";
String transformation = "DES";
// Cipher:密码,获取加密对象
// transformation:参数表示使用什么类型加密
Cipher cipher = Cipher.getInstance(transformation);
// 指定秘钥规则
// 第一个参数表示:密钥,key的字节数组
// 第二个参数表示:算法
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
// 对加密进行初始化
// 第一个参数:表示模式,有加密模式和解密模式
// 第二个参数:表示秘钥规则
cipher.init(Cipher.ENCRYPT_MODE,sks);
// 进行加密
byte[] bytes = cipher.doFinal(input.getBytes());
// 打印字节,因为ascii码有负数,解析不出来,所以乱码
// for (byte b : bytes) {
// System.out.println(b);
// }
// 打印密文
System.out.println(new String(bytes));
}
}
首先无论是加密还是解密过程,期间用到的对象都是这个Cipher。
代码涉及到的主要东西有:1.原文2.秘钥key3.加密模式和加密算法(我感觉这两一样的。。。但官方文档说不一样,可能是我太菜了)
这里需要注意的是如果使用des进行加密,那么密钥必须是8个字节 如果使用的是AES加密,那么密钥必须是16个字节
运行:
可以看到这里的密文是乱码,出现乱码是因为对应的字节出现负数,但负数,没有出现在 ascii 码表里面,所以出现乱码,需要配合base64进行转码。
使用 base64 进行编码
base64 导包的时候,需要注意 ,别导错了,需要导入 apache 包:
private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {
// 获取加密对象
Cipher cipher = Cipher.getInstance(transformation);
// 创建加密规则
// 第一个参数key的字节
// 第二个参数表示加密算法
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
// ENCRYPT_MODE:加密模式
// DECRYPT_MODE: 解密模式
// 初始化加密模式和算法
cipher.init(Cipher.ENCRYPT_MODE,sks);
// 加密
byte[] bytes = cipher.doFinal(input.getBytes());
// 输出加密后的数据
String encode = Base64.encode(bytes);
return encode;
}
这样一来密文也可以显示出正常符号了,不过好像没什么卵用,既然是密文看得懂与看不懂有什么区别?
运行程序:
1.3.3 DES解密
/**
* 解密
* @param encryptDES 密文
* @param key 密钥
* @param transformation 加密算法
* @param algorithm 加密类型
* @return
*/
private static String decryptDES(String encryptDES, String key, String transformation, String algorithm) throws Exception{
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithm);
//Cipher.DECRYPT_MODE:表示解密
// 解密规则
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
// 解密,传入密文
byte[] bytes = cipher.doFinal(Base64.decode(encryptDES));
return new String(bytes);
}
解密过程和加密过程是类似的,只是在选择Cipher模式的时候选择解密模式。Cilpher还有其他几种模式,可以去官方文档看看~~最后传入密文的时候,如果之前用Base64转码一次了,这里需要再转码一次。
运行程序:
Base64 算法简介
Base64是网络上最常见的用于传输8Bit字节码的可读性编码算法之一
可读性编码算法不是为了保护数据的安全性,而是为了可读性
可读性编码不改变信息内容,只改变信息内容的表现形式
所谓Base64,即是说在编码过程中使用了64种字符:大写A到Z、小写a到z、数字0到9、“+”和“/”
Base58是Bitcoin(比特币)中使用的一种编码方式,主要用于产生Bitcoin的钱包地址
相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"i",以及"+"和"/"符号
Base64 算法原理
base64 是 3个字节为一组,一个字节 8位,一共 就是24位 ,然后,把3个字节转成4组,每组6位,
3 * 8 = 4 * 6 = 24 ,每组6位,缺少的2位,会在高位进行补0 ,这样做的好处在于 ,base取的是后面6位,去掉高2位 ,那么base64的取值就可以控制在0-63位了,所以就叫base64,111 111 = 32 + 16 + 8 + 4 + 2 + 1 =
base64 构成原则
① 小写 a - z = 26个字母
② 大写 A - Z = 26个字母
③ 数字 0 - 9 = 10 个数字
④ + / = 2个符号
大家可能发现一个问题,咱们的base64有个 = 号,但是在映射表里面没有发现 = 号 , 这个地方需要注意,等号非常特殊,因为base64是三个字节一组 ,如果当我们的位数不够的时候,会使用等号来补齐
1.3.4 AES加密解密
AES 加密解密和 DES 加密解密代码一样,只需要修改加密算法就行,拷贝 ESC 代码
public class AesDemo {
// DES加密算法,key的大小必须是8个字节
public static void main(String[] args) throws Exception {
String input ="硅谷";
// AES加密算法,比较高级,所以key的大小必须是16个字节
String key = "1234567812345678";
String transformation = "AES"; // 9PQXVUIhaaQ=
// 指定获取密钥的算法
String algorithm = "AES";
// 先测试加密,然后在测试解密
String encryptDES = encryptDES(input, key, transformation, algorithm);
System.out.println("加密:" + encryptDES);
String s = dncryptDES(encryptDES, key, transformation, algorithm);
System.out.println("解密:" + s);
}
/**
* 使用DES加密数据
*
* @param input : 原文
* @param key : 密钥(DES,密钥的长度必须是8个字节)
* @param transformation : 获取Cipher对象的算法
* @param algorithm : 获取密钥的算法
* @return : 密文
* @throws Exception
*/
private static String encryptDES(String input, String key, String transformation, String algorithm) throws Exception {
// 获取加密对象
Cipher cipher = Cipher.getInstance(transformation);
// 创建加密规则
// 第一个参数key的字节
// 第二个参数表示加密算法
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
// ENCRYPT_MODE:加密模式
// DECRYPT_MODE: 解密模式
// 初始化加密模式和算法
cipher.init(Cipher.ENCRYPT_MODE,sks);
// 加密
byte[] bytes = cipher.doFinal(input.getBytes());
// 输出加密后的数据
String encode = Base64.encode(bytes);
return encode;
}
/**
* 使用DES解密
*
* @param input : 密文
* @param key : 密钥
* @param transformation : 获取Cipher对象的算法
* @param algorithm : 获取密钥的算法
* @throws Exception
* @return: 原文
*/
private static String dncryptDES(String input, String key, String transformation, String algorithm) throws Exception {
// 1,获取Cipher对象
Cipher cipher = Cipher.getInstance(transformation);
// 指定密钥规则
SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
cipher.init(Cipher.DECRYPT_MODE, sks);
// 3. 解密
byte[] bytes = cipher.doFinal(Base64.decode(input));
return new String(bytes);
}
}
运行程序:AES 加密的密钥key , 需要传入16个字节
1.4 toString()与new String ()用法区别
public class TestBase64 {
public static void main(String[] args) {
String str="TU0jV0xBTiNVYys5bEdiUjZlNU45aHJ0bTdDQStBPT0jNjQ2NDY1Njk4IzM5OTkwMDAwMzAwMA==";
String rlt1=new String(Base64.decode(str));
String rlt2=Base64.decode(str).toString();
System.out.println(rlt1);
System.out.println(rlt2);
}
}
结果是:
MM#WLAN#Uc+9lGbR6e5N9hrtm7CA+A==#646465698#399900003000
[B@1540e19d
哪一个是正确的?为什么?
这里应该用new String()的方法,因为Base64加解密是一种转换编码格式的原理
toString()与new String ()用法区别
str.toString是调用了这个object对象的类的toString方法。一般是返回这么一个String:[class name]@[hashCode]
new String(str)是根据parameter是一个字节数组,使用java虚拟机默认的编码格式,将这个字节数组decode为对应的字符。若虚拟机默认的编码格式是ISO-8859-1,按照ascii编码表即可得到字节对应的字符。
什么时候用什么方法呢?
new String()一般使用字符转码的时候,byte[]数组的时候
toString()对象打印的时候使用