Android 使用 Socket 对大文件进行加密传输前言数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复


阅读1497推荐理由项目中使用Socket进行文件传输过...Android使用Socket
实现信息隐蔽从而起到保护信息的安全的作用。项目中使用Socket进行文件传输
本篇文章主要介绍了"Android使用Socket对大文件进行加密传输",主要涉
好东西『Android使用Socket对大文件进行加密传输-...好东西『Android
/hey_buddy_iamback/9666863Android使用Socket对大文件进行加密传输-CS
Android手机与PC之间通过Socket进行通信怕'。手机端的按键通过脚本文件定义
Android使用Socket对大文件进行加密传输前言数据加...站长博客网的设计与
7.Android使用Socket对大文件进行加密传输数据加密,是一门历史悠久的技术,
Android应用市场上各种文件传输软件的优缺点,使用WiFi直连和SOCKET通信
key/secret,使用AndroidKeyStore存储运行时动态获取到的私密内容。而

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/
客户端的代码参考:http://blog.sina.com.cn/s/blog_792cc4290100syyt/

当以上所有的一切都准备完毕后,如果在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底层原理的理解。


可客空间(gh_ff02f1bc28ce)

 查看原文  分享到微信  文章为作者独立观点,不代表大不六文章网立场
可客空间
gh_ff02f1bc28ce
一起奋斗,创业之家

相关

    Android 使用 Socket 对大文件进行加密传输-可客空间-大不六文章网(wtoutiao.com)

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

    AndroidSocket加密

    可客空间 · 2016-12-27 17:46

    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/
    客户端的代码参考:http://blog.sina.com.cn/s/blog_792cc4290100syyt/

    当以上所有的一切都准备完毕后,如果在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底层原理的理解。


    可客空间(gh_ff02f1bc28ce)

     查看原文  分享到微信  文章为作者独立观点,不代表大不六文章网立场

最新更新

本站所有信息来源于互联网,用于学习参考使用,版权归原作者所有!
微信人生 分享最新微信头条新闻