數據加密與字符編碼的踩坑記錄

????上周在項目中需要對URL參數進行加密傳輸袜爪,實際過程中碰到了一些問題,在此對加密算法的Java實現及出現的編碼問題進行一個簡單的記錄婿屹。

一、加密算法

? ? ?這次分別對RSA(非對稱加密)和AES(對稱加密)進行了使用。這里也只對這兩種算法的Java實現進行簡單介紹,網上資料滿天飛,算法的具體內容和其他的算法自行查找吧。

? ? ?RSA承疲,通常使用公鑰加密、私鑰解密鸥咖,反之亦然燕鸽;而且大家肯定是不希望有人冒充我們發(fā)消息,可以通過只有我們自己掌握的私鑰來負責簽名扛或,公鑰負責驗證绵咱。通常私鑰長度有1024bit,2048bit熙兔,4096bit悲伶,長度越長,越安全住涉,但是生成密鑰越慢麸锉,加解密也越耗時(當然生成的加密串的長度也同所選秘鑰的長度一致)。

//生成秘鑰

public StringgenerateKey() {

try {

? ? ? ? KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");? ? ? //采用RSA算法

? ? ? ? kpg.initialize(1024);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //初始化KeyPairGenerator對象,密鑰長度采用1024bit

? ? ? ? KeyPair kp = kpg.genKeyPair();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?????????//生成秘鑰對

? ? ? ? RSAPublicKey pbkey = (RSAPublicKey) kp.getPublic();? ? ? ? ? ?????????//獲取公鑰

? ? ? ? RSAPrivateKey prkey = (RSAPrivateKey) kp.getPrivate();? ? ? ? ????????//獲取私鑰

? ? ? ? // 通過base64編碼得到公鑰字符串

? ? ? ? String publicKeyString = org.apache.tomcat.util.codec.binary.Base64.encodeBase64String(pbkey.getEncoded());

? ? ? ? // 通過base64編碼得到私鑰字符串

? ? ? ? String privateKeyString = org.apache.tomcat.util.codec.binary.Base64.encodeBase64String(prkey.getEncoded());

? ? ? ? return "publicKeyString:"+publicKeyString+"? privateKeyString:"+privateKeyString;

? ? }catch (Exception e) {

????????return null;

? ? }

}

//我這里是將之前生成的公鑰舆声、私鑰保存在配置文件中了花沉,現在通過@Value()注解來獲取秘鑰

@Value("${active.pbkey}")private String pbkey;

@Value("${active.prkey}")private String prkey;


//使用公鑰加密

public byte[](@RequestParam String accountName)throws Exception {

? ??//將base64編碼后的公鑰字符串轉成PublicKey實例(公鑰要通過X509編碼的key來獲取)

? ? byte[] buffer = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(pbkey);

? ? KeyFactory keyFactory = KeyFactory.getInstance("RSA");

? ? X509EncodedKeySpec keySpec =new X509EncodedKeySpec(buffer);

? ? RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

? ? //加密

? ? Cipher cipher =null;

? ? try {

? ? ? ? cipher = Cipher.getInstance("RSA");

? ? ? ? cipher.init(Cipher.ENCRYPT_MODE, publicKey);

? ? ? ? byte[]result = cipher.doFinal(accountName.getBytes());

? ? ? ? return result;

? ? }catch (Exception e) {

? ? ? ? log.error("參數加密失敗", e);

? ? ? ? return null;

? ? }

}


//使用私鑰進行解密

public String(@RequestParam byte[]url)throws Exception {

? ? //將base64編碼后的私鑰字符串轉成PrivateKey實例(私鑰要通過PKCS#8 編碼的key來獲认蔽铡)? ?

? ? byte[] buffer = Base64.decodeBase64(prkey);

? ? PKCS8EncodedKeySpec keySpec =new PKCS8EncodedKeySpec(buffer);

? ? KeyFactory keyFactory = KeyFactory.getInstance("RSA");

? ? RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);

? ? //解密

? ? Cipher cipher =null;

? ? try {

? ? ? ? cipher = Cipher.getInstance("RSA");

? ? ? ? cipher.init(Cipher.DECRYPT_MODE, privateKey);

? ? ? ? String accountName=new String(cipher.doFinal(url));

? ? ? ? return accountName;

? ? }catch (NoSuchPaddingException e) {

? ? ? ? log.error("參數解密失敗", e);

? ? ? ? return null;

? ? }

}

? ? ?AES碱屁,密鑰最長只有256個bit,執(zhí)行速度快蛾找。由于是對稱加密娩脾,是沒有公鑰和私鑰的區(qū)分的,雙方使用同一秘鑰進行加密打毛、解密柿赊,安全度相對非對稱加密較低俩功。基于以上特點碰声,通常使用RSA來首先傳輸AES的密鑰給對方(速度慢诡蜓,安全性高),然后再使用AES來進行加密通訊(速度快胰挑,安全性較低)蔓罚。

//生成AES秘鑰,AES沒有秘鑰對瞻颂,直接生成秘鑰即可

public StringgenerateKey() {

try {

????????KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

? ? ? ? keyGenerator.init(128);

? ? ? ? SecretKey secretKey = keyGenerator.generateKey();? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//生成秘鑰

? ? ? ? StringKeyString= Base64.encodeBase64String(secretKey.getEncoded());? ??????????????????????????????????????// 得到密鑰字符串

? ? ? ? return "KeyString:"+KeyString;

? ? }catch (Exception e) {

????????return null;

? ? }

}

//獲取存儲在配置文件中的秘鑰

@Value("${active.key}")

private String key;


// 加密.

public byte[]encrypt(String refer) {

????byte[] buffer = Base64.decodeBase64(key);

? ? SecretKey key=new SecretKeySpec(buffer, "AES");

????Cipher cipher =null;

? ? try {

????????cipher = Cipher.getInstance("AES");

? ? ? ? cipher.init(Cipher.ENCRYPT_MODE, key);

? ? ? ? byte[]result = cipher.doFinal(refer.getBytes("UTF-8"));

? ? ? ? return result;

? ? }catch (Exception e) {

????????log.error("參數加密失敗", e);

????????return null;

? ? }

}


//解密.

public String decrypt(byte[]refer) {

????byte[] buffer = Base64.decodeBase64(key);

? ? SecretKey key=new SecretKeySpec(buffer, "AES");

? ? Cipher cipher =null;

? ? try {

????????cipher = Cipher.getInstance("AES");

? ? ? ? cipher.init(Cipher.DECRYPT_MODE, key);

? ? ? ? String url = new String(cipher.doFinal(refer),"UTF-8");

? ? ? ? return url;

? ? }catch (Exception e) {

????????log.error("參數解密失敗脚粟,錯誤refer:"+refer, e);

????????return null;

? ? }

}

二、常見問題

? ? ?因為生成的密文為byte[ ]類型蘸朋,如果使用上面的代碼,直接對加密后的byte[ ]密文進行解密是完全沒有問題的扣唱。但我們實際使用中經常需要以Strring類型進行傳輸藕坯,需要通過url傳輸后再解密,這種情況下會出現很多問題噪沙。

byte[ ]炼彪、String轉換測試

? ? ?我們可以明顯的看出,經過String轉換得到的result已與初始的bytes不同了正歼。原因是轉換為String時是根據當前默認編碼類型(UTF-8)來生成的辐马,UTF-8是可變長度的編碼,有的字符需要用多個字節(jié)來表示局义,所以也就出現了在轉換之后byte[]數組長度喜爷、內容不一致的情況。

解決方案:

(1)Base64

?????Base64?是一種將二進制數據編碼的方式萄唇,正如UTF-8和UTF-16是將文本數據編碼的方式一樣檩帐,我們可以通過Base64將二進制數據編碼為文本數據。

//加密后將byte[ ]密文通過Base64轉為String

String str = Base64.encodeBase64String(bytes)另萤;

//解密前將String再通過Base64解碼為byte[ ]

byte[ ] bytes = Base64.decodeBase64(str)湃密;


***需要注意的是,Base64編碼后可能出現字符+和/四敞,在URL中就不能直接作為參數泛源,因為在urlEcode編碼中 “+” 會被解碼成空格。

解決方案一:拿到數據時將空格替換回“+”

解決方案二:預先進行urlEncode(但是如果該編碼后的密文在服務端獲取到之前經過微信忿危、QQ轉發(fā)或在瀏覽器中重定向后會被提前decode达箍,服務端拿到后仍不能正常解析)

//加密、Base64編碼后先encode再通過URL傳輸

String str = URLEncoder.encode(Base64.encodeBase64String(bytes),"UTF-8");

//解密前直接Base64解碼即可癌蚁,經過URL傳輸后獲得的鏈接已decode

byte[ ] bytes = Base64.decodeBase64(str)幻梯;

解決方案三:使用URL安全的Base64編碼兜畸,會把字符+和/分別變成-和_

//加密后使用URLSafeBase64

String str =?Base64.encodeBase64URLSafeString(bytes);

//解密前先解碼

byte[ ] bytes = Base64.decodeBase64(str)碘梢;

(2)轉換進制

? ? ?為了防止二進制直接轉為字符串String類型時出現數據缺失的現象咬摇,先byte[ ]密文轉換為十六進制,解密前再將十六進制轉回二進制煞躬。

? ? ?Java中的String對象是不需要指定編碼表的肛鹏,因為String里的字符信息是用UNICODE編碼的,并且Java使用char數據類型來對應UNICODE的字符恩沛,其大小為固定的兩個8位16進制數字在扰。Java中byte用二進制表示占用8位,而我們知道16進制的每個字符需要用4位二進制位來表示雷客。所以我們就可以把每個byte轉換成兩個相應的16進制字符芒珠,即把byte的高4位和低4位分別轉換成相應的16進制字符H和L,并組合起來得到byte轉換到16進制字符串的結果new String(H) + new String(L)搅裙。同理皱卓,相反的轉換也是將兩個16進制字符轉換成一個byte,原理同上部逮。根據以上原理娜汁,我們就可以將byte[] 數組轉換為16進制字符串了,當然也可以將16進制字符串轉換為byte[]數組了兄朋。

//加密后使用轉為十六進制

String str =??XXClass.parseByte2HexStr(?bytes?)掐禁;

//解密前先轉回二進制

byte[ ] bytes =?XXClass?.parseHexStr2Byte(str);


//2轉16

public static StringparseByte2HexStr(byte buf[]) {

????StringBuffer sb =new StringBuffer();

? ? for (int i =0; i < buf.length; i++) {

????????String hex = Integer.toHexString(buf[i] &0xFF);

? ? ? ? if (hex.length() ==1) {

????????hex ='0' + hex;

? ? ????}

????????sb.append(hex.toUpperCase());

? ? }

????return sb.toString();

}


//16轉2

public static byte[]parseHexStr2Byte(String hexStr) {

????if (hexStr.length() <1){

????????return null;

? ? }

????byte[] result =new byte[hexStr.length()/2];

? ? for (int i =0;i< hexStr.length()/2; i++) {

????????int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);

? ? ? ? int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);

? ? ? ? result[i] = (byte) (high *16 + low);

? ? }

????return result;

}


***附String的轉換使用:

public static void main(String[] args){

????String str ="ccha1994";

? ? byte[] strbyte = str.getBytes();

? ? System.out.println("toString:"+strbyte.toString());

? ? System.out.println("new String:"+new String(strbyte));

}

運行結果:

? ? ?toString():顯示的結果用的是父類Object的toString()方法,通常默認返回當前對象(c)的內存地址颅和,即hashCode傅事。

? ? ?new String():通過字節(jié)數組byte[]調用String對象中的toString(),是根據parameter是一個字節(jié)數組,使用java虛擬機默認的編碼格式或者參數指定的編碼格式峡扩,將這個字節(jié)數組decode為對應的字符享完。

使用:

? ? ?new?String()一般使用字符轉碼的時候,byte[ ]數組的時候。

? ? ?toString()將對象打印的時候使用 有额。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末般又,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子巍佑,更是在濱河造成了極大的恐慌茴迁,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萤衰,死亡現場離奇詭異堕义,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門倦卖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洒擦,“玉大人,你說我怎么就攤上這事怕膛∈炷郏” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵褐捻,是天一觀的道長掸茅。 經常有香客問我,道長柠逞,這世上最難降的妖魔是什么昧狮? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮板壮,結果婚禮上逗鸣,老公的妹妹穿的比我還像新娘。我一直安慰自己绰精,他們只是感情好慕购,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茬底,像睡著了一般。 火紅的嫁衣襯著肌膚如雪获洲。 梳的紋絲不亂的頭發(fā)上阱表,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音贡珊,去河邊找鬼最爬。 笑死,一個胖子當著我的面吹牛门岔,可吹牛的內容都是我干的爱致。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼寒随,長吁一口氣:“原來是場噩夢啊……” “哼糠悯!你這毒婦竟也來了?” 一聲冷哼從身側響起妻往,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤互艾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讯泣,有當地人在樹林里發(fā)現了一具尸體纫普,經...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年好渠,在試婚紗的時候發(fā)現自己被綠了昨稼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片节视。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖假栓,靈堂內的尸體忽然破棺而出寻行,到底是詐尸還是另有隱情,我是刑警寧澤但指,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布寡痰,位于F島的核電站,受9級特大地震影響棋凳,放射性物質發(fā)生泄漏拦坠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一剩岳、第九天 我趴在偏房一處隱蔽的房頂上張望贞滨。 院中可真熱鬧,春花似錦拍棕、人聲如沸晓铆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骄噪。三九已至,卻和暖如春蠢箩,著一層夾襖步出監(jiān)牢的瞬間链蕊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工谬泌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滔韵,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓掌实,卻偏偏與公主長得像陪蜻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贱鼻,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容

  • 概述 之前一直對加密相關的算法知之甚少宴卖,只知道類似DES、RSA等加密算法能對數據傳輸進行加密邻悬,且各種加密算法各有...
    Henryzhu閱讀 3,019評論 0 14
  • Base64.java public final class Base64 { static private ...
    BUG弄潮兒閱讀 795評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理嘱腥,服務發(fā)現,斷路器拘悦,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 當你迷茫時齿兔, 閱讀吧。 當你痛苦時, 閱讀吧分苇。 當你孤獨時添诉, 閱讀吧。 刷新所有的朋友圈医寿, 無法釋懷的情緒栏赴, 在閱...
    麻小寶007閱讀 213評論 0 0
  • 不知道為什么须眷,這幾天自己總是情緒低落,總是不由自主的想到老爸沟突,想到他在時對我的種種呵護花颗。或許我是累了惠拭,又或許我傷心...
    璽璽公主閱讀 360評論 0 1