详解Android使用Socket对大文件进行加密传输
前言
数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。
数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法。它利用密码技术对信息进行加密,实现信息隐蔽从而起到保护信息的安全的作用。
项目中使用Socket进行文件传输过程时,需要先进行加密。实现的过程中踏了一些坑,下面对实现过程进行一下总结。
DES加密
由于加密过程中使用的是DES加密算法,下面贴一下DES加密代码:
//秘钥算法
privatestaticfinalStringKEY_ALGORITHM="DES";
//加密算法:algorithm/mode/padding算法/工作模式/填充模式
privatestaticfinalStringCIPHER_ALGORITHM="DES/ECB/PKCS5Padding";
//秘钥
privatestaticfinalStringKEY="12345678";//DES秘钥长度必须是8位
publicstaticvoidmain(Stringargs[]){
Stringdata="加密解密";
KLog.d("加密数据:"+data);
byte[]encryptData=encrypt(data.getBytes());
KLog.d("加密后的数据:"+newString(encryptData));
byte[]decryptData=decrypt(encryptData);
KLog.d("解密后的数据:"+newString(decryptData));
}
publicstaticbyte[]encrypt(byte[]data){
//初始化秘钥
SecretKeysecretKey=newSecretKeySpec(KEY.getBytes(),KEY_ALGORITHM);
try{
Ciphercipher=Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE,secretKey);
byte[]result=cipher.doFinal(data);
returnBase64.getEncoder().encode(result);
}catch(Exceptione){
e.printStackTrace();
}
returnnull;
}
publicstaticbyte[]decrypt(byte[]data){
byte[]resultBase64=Base64.getDecoder().decode(data);
SecretKeysecretKey=newSecretKeySpec(KEY.getBytes(),KEY_ALGORITHM);
try{
Ciphercipher=Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE,secretKey);
byte[]result=cipher.doFinal(resultBase64);
returnresult;
}catch(Exceptione){
e.printStackTrace();
}
returnnull;
}
输出:
加密数据:加密解密
加密后的数据:rt6XE06pElmLZMaVxrbfCQ==
解密后的数据:加密解密
Socket客户端部分代码:
Socketsocket=newSocket(ApiConstants.HOST,ApiConstants.PORT);
OutputStreamoutStream=socket.getOutputStream();
InputStreaminStream=socket.getInputStream();
RandomAccessFilefileOutStream=newRandomAccessFile(file,"r");
fileOutStream.seek(0);
byte[]buffer=newbyte[1024];
intlen=-1;
while(((len=fileOutStream.read(buffer))!=-1)){
outStream.write(buffer,0,len);//这里进行字节流的传输
}
fileOutStream.close();
outStream.close();
inStream.close();
socket.close();
Socket服务端部分代码:
Socketsocket=server.accept();
InputStreaminStream=socket.getInputStream();
OutputStreamoutStream=socket.getOutputStream();
outStream.write(response.getBytes("UTF-8"));
RandomAccessFilefileOutStream=newRandomAccessFile(file,"rwd");
fileOutStream.seek(0);
byte[]buffer=newbyte[1024];
intlen;
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:padblockcorrupted
猜测错误原因是加密过程会对数据进行填充处理,然后在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:Handshakefailed
异常原因:SSLSocket签名算法默认为DSA,Android6.0(API23)以后KeyStore发生更改,不再支持DSA,但仍支持ECDSA。
所以如果想在Android6.0以上使用SSLSocket,需要将DSA改成ECDSA...org感觉坑越入越深看不到底呀...于是决定换个思路来解决socket加密这个问题。既然对文件边传边加密解密不好使,那能不能客户端传输文件前先对文件进行加密,然后进行传输,服务端成功接收文件后,再对文件进行解密呢。于是就有了下面这个方案。
方案五:先对文件进行加密,然后传输,服务端成功接收文件后再对文件进行解密
对文件进行加密解密代码如下:
publicclassFileDesUtil{
//秘钥算法
privatestaticfinalStringKEY_ALGORITHM="DES";
//加密算法:algorithm/mode/padding算法/工作模式/填充模式
privatestaticfinalStringCIPHER_ALGORITHM="DES/ECB/PKCS5Padding";
privatestaticfinalbyte[]KEY={56,57,58,59,60,61,62,63};//DES秘钥长度必须是8位或以上
/**
*文件进行加密并保存加密后的文件到指定目录
*
*@paramfromFile要加密的文件如c:/test/待加密文件.txt
*@paramtoFile加密后存放的文件如c:/加密后文件.txt
*/
publicstaticvoidencrypt(StringfromFilePath,StringtoFilePath){
KLog.i("encrypting...");
FilefromFile=newFile(fromFilePath);
if(!fromFile.exists()){
KLog.e("tobeencryptfilenoexist!");
return;
}
FiletoFile=getFile(toFilePath);
SecretKeysecretKey=newSecretKeySpec(KEY,KEY_ALGORITHM);
InputStreamis=null;
OutputStreamout=null;
CipherInputStreamcis=null;
try{
Ciphercipher=Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE,secretKey);
is=newFileInputStream(fromFile);
out=newFileOutputStream(toFile);
cis=newCipherInputStream(is,cipher);
byte[]buffer=newbyte[1024];
intr;
while((r=cis.read(buffer))>0){
out.write(buffer,0,r);
}
}catch(Exceptione){
KLog.e(e.toString());
}finally{
try{
if(cis!=null){
cis.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
try{
if(is!=null){
is.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
try{
if(out!=null){
out.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
}
KLog.i("encryptcompleted");
}
@NonNull
privatestaticFilegetFile(StringfilePath){
FilefromFile=newFile(filePath);
if(!fromFile.getParentFile().exists()){
fromFile.getParentFile().mkdirs();
}
returnfromFile;
}
/**
*文件进行解密并保存解密后的文件到指定目录
*
*@paramfromFilePath已加密的文件如c:/加密后文件.txt
*@paramtoFilePath解密后存放的文件如c:/test/解密后文件.txt
*/
publicstaticvoiddecrypt(StringfromFilePath,StringtoFilePath){
KLog.i("decrypting...");
FilefromFile=newFile(fromFilePath);
if(!fromFile.exists()){
KLog.e("tobedecryptfilenoexist!");
return;
}
FiletoFile=getFile(toFilePath);
SecretKeysecretKey=newSecretKeySpec(KEY,KEY_ALGORITHM);
InputStreamis=null;
OutputStreamout=null;
CipherOutputStreamcos=null;
try{
Ciphercipher=Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE,secretKey);
is=newFileInputStream(fromFile);
out=newFileOutputStream(toFile);
cos=newCipherOutputStream(out,cipher);
byte[]buffer=newbyte[1024];
intr;
while((r=is.read(buffer))>=0){
cos.write(buffer,0,r);
}
}catch(Exceptione){
KLog.e(e.toString());
}finally{
try{
if(cos!=null){
cos.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
try{
if(out!=null){
out.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
try{
if(is!=null){
is.close();
}
}catch(IOExceptione){
e.printStackTrace();
}
}
KLog.i("decryptcompleted");
}
}
使用如上这个方案就完美的绕开了上面提到的一些问题,成功的实现了使用Socket对文件进行加密传输。
总结
对于任何技术的使用,底层原理的理解还是很有必要的。不然遇到问题很容易就是一头雾水不知道Why!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。