详解Android使用Socket对大文件进行加密传输

详解Android使用Socket对大文件进行加密传输,第1张

概述前言数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。

前言

数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。

数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密,实现信息隐蔽从而起到保护信息的安全的作用。

项目中使用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,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,len); } fileOutStream.close(); inStream.close(); outStream.close(); socket.close();

数据加密传输

下面对传输数据进行加密解密

方案一:直接对io流进行加密解密

客户端变更如下:

 while (((len = fileOutStream.read(buffer)) != -1)) {  outStream.write(DesUtil.encrypt(buffer),len); // 对字节数组进行加密 }

服务端变更代码:

 while ((len = inStream.read(buffer)) != -1) {  fileOutStream.write(DesUtil.decrypt(buffer),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,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,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!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的详解Android使用Socket对大文件进行加密传输全部内容,希望文章能够帮你解决详解Android使用Socket对大文件进行加密传输所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1147219.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存