ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其余西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
html
看一个小故事 , 看看古人如何加密和解密:java
公元683年,唐中宗即位。随后,武则天废唐中宗,立第四子李旦为皇帝,但朝政大事均由她本身独断。 算法
裴炎、徐敬业和骆宾王等人对此很是不满。徐敬业聚兵十万,在江苏扬州起兵。裴炎作内应,欲以拆字手段为其传递秘密信息。后因有人告密,裴炎被捕,未发出的密信落到武则天手中。这封密信上只有“青鹅”二字,群臣对此大惑不解。 apache
武则天破解了“青鹅”的秘密:“青”字拆开来就是“十二月”,而“鹅”字拆开来就是“我自与”。密信的意思是让徐敬业、骆宾王等率兵于十二月进发,裴炎在内部接应。“青鹅”破译后,裴炎被杀。接着,武则天派兵击败了徐敬业和骆宾王。
以前有说过古代密码学主要是替换和移位两种操做,凯撒加密属性移位加密。
替换法通常有单表替换法和多表替换法。api
在密码学中,恺撒密码是一种最简单且最广为人知的加密技术。数组
凯撒密码最先由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。下面是位移1次的对比:
将明文字母表向后移动1位,A变成了B,B变成了C……,Z变成了A。同理,若将明文字母表向后移动3位:
则A变成了D,B变成了E……,Z变成了C。安全
字母表最多能够移动25位。凯撒密码的明文字母表向后或向前移动都是能够的,一般表述为向后移动,若是要向前移动1位,则等同于向后移动25位,位移选择为25便可。网络
它是一种替换加密的技术,明文中的全部字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。oracle
例如,当偏移量是3的时候,全部的字母A将被替换成D,B变成E,以此类推。app
这个加密方法是以恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。
恺撒密码一般被做为其余更复杂的加密方法中的一个步骤。
简单来讲就是当秘钥为n,其中一个待加密字符ch,加密以后的字符为ch+n,当ch+n超过’z’时,回到’a’计数。
建立类 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(); }
凯撒加密其实很容易从密文中猜想出明文所使用的秘钥,固然只适用于英文文本的状况,毕竟我们汉字作不到位移操做,最多拆字和组合。
若是是英文这难不倒解密者,以英文字母为例,为了肯定每一个英文字母的出现频率,分析一篇或者数篇普通的英文文章,英文字母出现频率最高的是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<Entry<Character, Integer>> mapList = getMaxCountChar(data); for (Entry<Character, Integer> 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<Entry<Character, Integer>> mapList = getMaxCountChar(input); for (Entry<Character, Integer> 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<Entry<Character, Integer>> getMaxCountChar(String data) { Map<Character, Integer> map = new HashMap<Character, Integer>(); 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<Character, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + "出现" + entry.getValue() + "次"); }*/ //获取获取最大值 int maxCount = 0; for (Entry<Character, Integer> entry : map.entrySet()) { //不统计空格 if (/*entry.getKey() != ' ' && */entry.getValue() > maxCount) { maxCount = entry.getValue(); } } //map转换成list便于排序 List<Entry<Character, Integer>> mapList = new ArrayList<Entry<Character,Integer>>(map.entrySet()); //根据字符出现次数排序 Collections.sort(mapList, new Comparator<Entry<Character, Integer>>(){ public int compare(Entry<Character, Integer> o1, Entry<Character, Integer> 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对密文进行一个解密,重复此步骤,直到获得正确的原文。
运行程序:
显然第二个猜想文件就已经正确了。
采用单钥密码系统的加密方法,同一个密钥能够同时用做信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
这里的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; }
这样一来密文也能够显示出正常符号了,不过好像没什么卵用,既然是密文看得懂与看不懂有什么区别?
运行程序:
/** * 解密 * @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到九、“+”和“/” 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是三个字节一组 ,若是当咱们的位数不够的时候,会使用等号来补齐
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个字节
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()对象打印的时候使用