当前位置: 移动技术网 > 移动技术>移动开发>Android > 详解Android使用Socket对大文件进行加密传输

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

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言

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

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

项目中使用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!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网