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()對象打印的時候使用