Android 使用 Socket 對大檔案進行加密傳輸
前言
資料加密,是一門歷史悠久的技術,指通過加密演算法和加密金鑰將明文轉變為密文,而解密則是通過解密演算法和解密金鑰將密文恢復為明文。它的核心是密碼學。
資料加密目前仍是計算機系統對資訊進行保護的一種最可靠的辦法。它利用密碼技術對資訊進行加密,實現資訊隱蔽從而起到保護資訊的安全的作用。
專案中使用Socket進行檔案傳輸過程時,需要先進行加密。實現的過程中踏了一些坑,下面對實現過程進行一下總結。
DES加密
由於加密過程中使用的是DES加密演算法,下面貼一下DES加密程式碼:
//祕鑰演算法 private static final String KEY_ALGORITHM = "DES"; //加密演算法:algorithm/mode/padding 演算法/工作模式/填充模式 private static final String CIPHER_ALGORITHM = "DES/ECB/PKCS5Padding"; //祕鑰 private static final String KEY = "12345678";//DES祕鑰長度必須是8位 public static void main(String args[]) { String data = "加密解密"; KLog.d("加密資料:" + data); byte[] encryptData = encrypt(data.getBytes()); KLog.d("加密後的資料:" + new String(encryptData)); byte[] decryptData = decrypt(encryptData); KLog.d("解密後的資料:" + new String(decryptData)); } public static byte[] encrypt(byte[] data) { //初始化祕鑰 SecretKey secretKey = new SecretKeySpec(KEY.getBytes(), KEY_ALGORITHM); try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] result = cipher.doFinal(data); return Base64.getEncoder().encode(result); } catch (Exception e) { e.printStackTrace(); } return null; } public static byte[] decrypt(byte[] data) { byte[] resultBase64 = Base64.getDecoder().decode(data); SecretKey secretKey = new SecretKeySpec(KEY.getBytes(), KEY_ALGORITHM); try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] result = cipher.doFinal(resultBase64); return result; } catch (Exception e) { e.printStackTrace(); } return null; }
輸出:
加密資料:加密解密 加密後的資料:rt6XE06pElmLZMaVxrbfCQ== 解密後的資料:加密解密
Socket客戶端部分程式碼:
Socket socket = new Socket(ApiConstants.HOST, ApiConstants.PORT); OutputStream outStream = socket.getOutputStream(); InputStream inStream = socket.getInputStream(); RandomAccessFile fileOutStream = new RandomAccessFile(file, "r"); fileOutStream.seek(0); byte[] buffer = new byte[1024]; int len = -1; while (((len = fileOutStream.read(buffer)) != -1)) { outStream.write(buffer, 0, len); // 這裡進行位元組流的傳輸 } fileOutStream.close(); outStream.close(); inStream.close(); socket.close();
Socket服務端部分程式碼:
Socket socket = server.accept(); InputStream inStream = socket.getInputStream(); OutputStream outStream = socket.getOutputStream(); outStream.write(response.getBytes("UTF-8")); RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd"); fileOutStream.seek(0); byte[] buffer = new byte[1024]; int len; while ((len = inStream.read(buffer)) != -1) { // 從位元組輸入流中讀取資料寫入到檔案中 fileOutStream.write(buffer, 0, len); } fileOutStream.close(); inStream.close(); outStream.close(); socket.close();
資料加密傳輸
下面對傳輸資料進行加密解密
方案一:直接對io流進行加密解密
客戶端變更如下:
while (((len = fileOutStream.read(buffer)) != -1)) { outStream.write(DesUtil.encrypt(buffer) ,0, len); // 對位元組陣列進行加密 }
服務端變更程式碼:
while ((len = inStream.read(buffer)) != -1) { fileOutStream.write(DesUtil.decrypt(buffer), 0, len); // 對位元組陣列進行解密 }
執行程式碼後,服務端解密時會報如下異常:
javax.crypto.BadPaddingException: pad block corrupted
猜測錯誤原因是加密過程會對資料進行填充處理,然後在io流傳輸的過程中,資料有丟包現象發生,所以解密會報異常。
加密後的結果是位元組陣列,這些被加密後的位元組在碼錶(例如UTF-8 碼錶)上找不到對應字元,會出現亂碼,當亂碼字串再次轉換為位元組陣列時,長度會變化,導致解密失敗,所以轉換後的資料是不安全的。
於是嘗試了使用NOPadding填充模式,這樣雖然可以成功解密,測試中發現對於一般檔案,如.txt檔案可以正常顯示內容,但是.apk等檔案則會有解析包出現異常等錯誤提示。
方案二:使用字元流
使用Base64 對位元組陣列進行編碼,任何位元組都能對映成對應的Base64 字元,之後能恢復到位元組陣列,利於加密後資料的儲存於傳輸,所以轉換是安全的。同樣,位元組陣列轉換成16 進位制字串也是安全的。
由於客戶端從輸入檔案中讀取的是位元組流,需要先將位元組流轉換成字元流,而服務端接收到字元流後需要先轉換成位元組流,再將其寫入到檔案。測試中發現可以對字元流成功解密,但是將檔案轉化成字元流進行傳輸是個連續的過程,而檔案的寫出和寫入又比較繁瑣,操作過程中會出現很多問題。
方案三:使用CipherInputStream、CipherOutputStream
使用過程中發現只有當CipherOutputStream流close時,CipherInputStream才會接收到資料,顯然這個方案也只好pass掉。
方案四:使用SSLSocket
在Android上使用SSLSocket的會稍顯複雜,首先客戶端和服務端需要生成祕鑰和證照。生成方法可以參考這篇。Android證照的格式還必須是bks格式(Java使用jks格式)。一般來說,我們使用jdk的keytool只能生成jks的證照庫,如果生成bks的則需要下載BouncyCastle庫。具體方法可以參考這裡
服務端的程式碼參考:http://blog.sina.com.cn/s/blog_792cc4290100syyf.html
客戶端的程式碼參考:http://blog.sina.com.cn/s/blog_792cc4290100syyt.html
當以上所有的一切都準備完畢後,如果在Android6.0以上使用你會悲催的發現下面這個異常:
javax.net.ssl.SSLHandshakeException: Handshake failed
異常原因:SSLSocket簽名演算法預設為DSA,Android6.0(API 23)以後KeyStore發生更改,不再支援DSA,但仍支援ECDSA。
所以如果想在Android6.0以上使用SSLSocket,需要將DSA改成ECDSA…org感覺坑越入越深看不到底呀…於是決定換個思路來解決socket加密這個問題。既然對檔案邊傳邊加密解密不好使,那能不能客戶端傳輸檔案前先對檔案進行加密,然後進行傳輸,服務端成功接收檔案後,再對檔案進行解密呢。於是就有了下面這個方案。
方案五:先對檔案進行加密,然後傳輸,服務端成功接收檔案後再對檔案進行解密
對檔案進行加密解密程式碼如下:
public class FileDesUtil { //祕鑰演算法 private static final String KEY_ALGORITHM = "DES"; //加密演算法:algorithm/mode/padding 演算法/工作模式/填充模式 private static final String CIPHER_ALGORITHM = "DES/ECB/PKCS5Padding"; private static final byte[] KEY = {56, 57, 58, 59, 60, 61, 62, 63};//DES 祕鑰長度必須是8 位或以上 /** * 檔案進行加密並儲存加密後的檔案到指定目錄 * * @param fromFile 要加密的檔案 如c:/test/待加密檔案.txt * @param toFile 加密後存放的檔案 如c:/加密後檔案.txt */ public static void encrypt(String fromFilePath, String toFilePath) { KLog.i("encrypting..."); File fromFile = new File(fromFilePath); if (!fromFile.exists()) { KLog.e("to be encrypt file no exist!"); return; } File toFile = getFile(toFilePath); SecretKey secretKey = new SecretKeySpec(KEY, KEY_ALGORITHM); InputStream is = null; OutputStream out = null; CipherInputStream cis = null; try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); is = new FileInputStream(fromFile); out = new FileOutputStream(toFile); cis = new CipherInputStream(is, cipher); byte[] buffer = new byte[1024]; int r; while ((r = cis.read(buffer)) > 0) { out.write(buffer, 0, r); } } catch (Exception e) { KLog.e(e.toString()); } finally { try { if (cis != null) { cis.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } KLog.i("encrypt completed"); } @NonNull private static File getFile(String filePath) { File fromFile = new File(filePath); if (!fromFile.getParentFile().exists()) { fromFile.getParentFile().mkdirs(); } return fromFile; } /** * 檔案進行解密並儲存解密後的檔案到指定目錄 * * @param fromFilePath 已加密的檔案 如c:/加密後檔案.txt * @param toFilePath 解密後存放的檔案 如c:/ test/解密後檔案.txt */ public static void decrypt(String fromFilePath, String toFilePath) { KLog.i("decrypting..."); File fromFile = new File(fromFilePath); if (!fromFile.exists()) { KLog.e("to be decrypt file no exist!"); return; } File toFile = getFile(toFilePath); SecretKey secretKey = new SecretKeySpec(KEY, KEY_ALGORITHM); InputStream is = null; OutputStream out = null; CipherOutputStream cos = null; try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); is = new FileInputStream(fromFile); out = new FileOutputStream(toFile); cos = new CipherOutputStream(out, cipher); byte[] buffer = new byte[1024]; int r; while ((r = is.read(buffer)) >= 0) { cos.write(buffer, 0, r); } } catch (Exception e) { KLog.e(e.toString()); } finally { try { if (cos != null) { cos.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } KLog.i("decrypt completed"); } }
使用如上這個方案就完美的繞開了上面提到的一些問題,成功的實現了使用Socket對檔案進行加密傳輸。
總結
對於任何技術的使用,底層原理的理解還是很有必要的。不然遇到問題很容易就是一頭霧水不知道Why!接下來準備看一下《圖解加密技術》和《圖解TCP/IP》這兩本書,以便加深對密碼學和Socket底層原理的理解。
相關文章
- Android使用Socket(Tcp/Udp)協議進行資料傳輸(傳輸大檔案)AndroidTCPUDP協議
- 使用MFT進行加密檔案傳輸的7個好處加密
- Jmeter使用beanshell對資料進行加密傳輸JMeterBean加密
- 使用GnuPG對檔案進行加密(轉)加密
- 企業經常需要進行傳輸檔案,大檔案傳輸有哪些方法?
- C# Socket 檔案傳送傳輸C#
- Java Socket圖片檔案傳輸Java
- 什麼是極速檔案傳輸,極速檔案傳輸如何進行大檔案傳輸
- 如何有序高效地進行大檔案跨國傳輸?
- 用Socket實現點對點的檔案傳輸 (轉)
- python3 socket檔案傳輸Python
- 大檔案如何傳輸,大檔案的傳輸方式有哪些?
- scp不輸入密碼進行檔案傳輸密碼
- 哪裡有免費大檔案傳輸平臺?透過這4個網站免費來進行大檔案傳輸網站
- 有了大檔案傳輸加速功能,不再擔憂傳輸大檔案
- 如何快速傳輸大檔案:4 種大檔案傳輸有效的方法
- Mac下利用SSH進行傳輸檔案(轉)Mac
- Android Socket連線,使用Socket進行通訊(Android)Android
- C#如何使用HttpClient對大檔案進行斷點上傳和下載C#HTTPclient斷點
- 鐳速傳輸:如何快速傳輸大檔案?
- wtfpd檔案傳輸使用
- 鐳速傳輸針對汽車行業的檔案傳輸解決方案行業
- node使用multer進行檔案上傳
- Socket 傳送檔案
- 還在使用檔案傳輸協議傳輸機密檔案?你可能是對檔案傳輸協議有什麼誤會協議
- 快速傳輸大檔案,怎麼透過網路傳大檔案給對方(1G以上)
- 【多檔案自平衡雲傳輸】使用展示 —— 檔案傳輸系統
- 使用WinSCP軟體在windows和Linux中進行檔案傳輸WindowsLinux
- 在Linux環境下使用OpenSSL對訊息和檔案進行加密Linux加密
- Android File Transfer for mac(強大的安卓檔案傳輸工具)AndroidMac安卓
- 如何分發大檔案、大檔案傳輸解決方案
- 如何快速傳輸大檔案,介紹大檔案快速方法
- 醫療記錄可以不加密進行傳輸嗎?加密
- 怎麼遠端傳輸大檔案?
- 大檔案傳輸的9種方法
- 使用ftp自動傳輸檔案FTP
- 基於序列化技術(Protobuf)的socket檔案傳輸
- 安卓檔案傳輸工具Android File Transfer安卓Android