国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

詳解Android使用Socket對大文件進行加密傳輸

2019-12-12 03:58:49
字體:
來源:轉載
供稿:網友

前言

數據加密,是一門歷史悠久的技術,指通過加密算法和加密密鑰將明文轉變為密文,而解密則是通過解密算法和解密密鑰將密文恢復為明文。它的核心是密碼學。

數據加密目前仍是計算機系統對信息進行保護的一種最可靠的辦法。它利用密碼技術對信息進行加密,實現信息隱蔽從而起到保護信息的安全的作用。

項目中使用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庫。

當以上所有的一切都準備完畢后,如果在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!

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 北宁市| 大石桥市| 区。| 乌鲁木齐县| 思茅市| 桑植县| 正定县| 麻栗坡县| 安阳市| 右玉县| 明光市| 内江市| 陇川县| 出国| 屏南县| 常德市| 信宜市| 内乡县| 黔西县| 巩留县| 兴业县| 福清市| 微山县| 朝阳区| 恩平市| 张家口市| 东平县| 武陟县| 东乡族自治县| 赤水市| 石家庄市| 禹城市| 介休市| 习水县| 沂源县| 岗巴县| 桦南县| 梧州市| 珲春市| 闸北区| 正安县|