您的位置:首页 > 移动开发 > Android开发

Android中使用加密图片的解决方案

2016-01-26 15:06 721 查看

Android中使用加密图片的解决方案

产品要求资源zip包中的图片资源要加密,于是在需求的驱动下,在网上搜寻解决方案。

加密方式

加密方式很多种,从最简单的交换字节顺序,到各种加密算法。下面简单列一下网上摘录的加密算法的对比。

AES/DES加密速度快,适合大量数据,DES容易破解,一般用3重DES,后来又出现了更快更安全的AES

RSA是公钥加密,速度慢,只能处理少量数据,优点是公钥即使在不安全的网络上公开,也能保证安全

由此加密算法选定了AES加密算法,在网上搜索AES加密实现的时候发现几乎都是对byte[]的加密解密操作,考虑到Android机上对图片做解密操作可能对内存消耗大,尝试找有没有基于Stream的加密解密方式,经过了一番资料查找找到了支持AES加密解密的
CipherInputStream
CipherOutputStream
借助这两个Stream可以实现将加密的图片文件读取成解密后的Bitmap。方法找到了,下面来看下关键代码

加密关键代码

/**
* 加密
*
* @param file   待加密数据
* @param key    密钥
* @param cipherAlgorithm    加密算法/工作模式/填充方式
* @return byte[]    加密数据
* @throws Exception
*/
private static OutputStream encrypt(File file, Key key) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行操作
CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(file), cipher);
return cos;
}


解密关键代码

/**
* 解密
*
* @param file   待解密文件
* @param key    密钥
* @param cipherAlgorithm    加密算法/工作模式/填充方式
* @return byte[]    解密数据
* @throws Exception
*/
private static InputStream decrypt(File file, Key key) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
//执行操作
CipherInputStream cis = new CipherInputStream(new FileInputStream(file), cipher);
return cis;
}


构造Key

/**
* 转换密钥
*
* @param key    二进制密钥
* @return 密钥
*/
private static Key toKey(byte[] key){
//生成密钥
return new SecretKeySpec(key, "AES");
}


生成128位二进制密钥

/**
* 初始化密钥
*
* @return byte[] 密钥
* @throws Exception
*/
public static byte[] initSecretKey() {
//返回生成指定算法的秘密密钥的 KeyGenerator 对象
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new byte[0];
}
//初始化此密钥生成器,使其具有确定的密钥大小
//AES 要求密钥长度为 128
kg.init(128);
//生成一个密钥
SecretKey  secretKey = kg.generateKey();
return secretKey.getEncoded();
}


至此,就可以通过生成的128为密钥加密解密文件了。那么接下来要解决下一个问题,将加密解密融入到项目中去。

与现有项目融合

项目的图片加载采用的是Universal-Image-Loader,最开始想到的方式是,下载U-I-L的源码将加密解密代码插入其中,问题可以解决。单后来考虑到U-I-L是一个相当成熟的框架,一定有通过扩展的方式来实现目的。

解密图片

U-I-L有提供设置自定义的ImageDownloader,让我们来看下ImageDownloader接口

他有一个接口方法
getStream


public interface ImageDownloader {

InputStream getStream(String uri, Object var2) throws IOException;

······
}


接下来让我们实现这个接口

@Override
public InputStream getStream(String s, Object o) throws IOException {
if (s == null){
return null;
}
if (s.toLowerCase(Locale.US).startsWith("file://")){
String crop = Scheme.FILE.crop(s);
try {
return AESCoder.decrypt(new File(crop), AESCoder.get(context));
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}


到此为止就可以实现,App读取加密后的资源图片。但是此时还有一个很大的漏洞,加载图片的时候开启了
cacheOnDisk(true)
,这就会导致,解密后的图片缓存到sdcard的时候以解密形态存储,没有达到最初的目的,接下来要解决一下缓存加密的问题。

加密缓存

加密缓存,目的是从手机sdcard中不能拿到解密的图片,这里就要考虑对缓存逻辑动手脚了,此时我发现U-I-L的配置中可以配置
DiskCache


我们来看一下DiskCache接口

public interface DiskCache {

boolean save(String imageUri, InputStream imageStream, CopyListener listener) throws IOException;

boolean save(String imageUri, Bitmap bitmap) throws IOException;

......
}


我们看到
UnlimitedDiskCache
实现啊了
DiskCache
接口,而项目中正好用的是这种缓存机制,所以我们可以重写
UnlimitedDiskCache
的这两个save方法,将解密的bitmap重新加密。

主要代码如下

@Override
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
File imageFile = this.getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + ".tmp");
try {
OutputStream encrypt = AESCoder.encrypt(tmpFile, AESCoder.get(context));
BufferedOutputStream os = new BufferedOutputStream(encrypt, this.bufferSize);
boolean savedSuccessfully = false;

try {
savedSuccessfully = bitmap.compress(this.compressFormat, this.compressQuality, os);
} finally {
IoUtils.closeSilently(os);
if(savedSuccessfully && !tmpFile.renameTo(imageFile)) {
savedSuccessfully = false;
}
if(!savedSuccessfully) {
tmpFile.delete();
}
}

bitmap.recycle();
return savedSuccessfully;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
File imageFile = this.getFile(imageUri);
File tmpFile = new File(imageFile.getAbsolutePath() + ".tmp");
boolean loaded = false;

try {
OutputStream encrypt = AESCoder.encrypt(tmpFile, AESCoder.get(context));
BufferedOutputStream os = new BufferedOutputStream(encrypt, this.bufferSize);

try {
loaded = IoUtils.copyStream(imageStream, os, listener, this.bufferSize);
} finally {
IoUtils.closeSilently(os);
}
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if(loaded && !tmpFile.renameTo(imageFile)) {
loaded = false;
}
if(!loaded) {
tmpFile.delete();
}
}
return loaded;
}


最后附上AES加密类的实现

package util;

import android.content.Context;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.Key;

public class AESCoder {

/**
* 密钥算法
*/
private static final String KEY_ALGORITHM = "AES";

private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

public static byte[] get(Context context){
try {
InputStream is = context.getAssets().open("aes.key");
byte[] k = new byte[16];
is.read(k);
return k;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

/**
* 加密
*
* @param file  待加密数据
* @param key   二进制密钥
* @return byte[]   加密数据
* @throws Exception
*/
public static OutputStream encrypt(File file, byte[] key) throws Exception{
return encrypt(file, key, DEFAULT_CIPHER_ALGORITHM);
}

/**
* 解密
*
* @param file  待解密数据
* @param key   二进制密钥
* @return byte[]   解密数据
* @throws Exception
*/
public static InputStream decrypt(File file, byte[] key) throws Exception{
return decrypt(file, key,DEFAULT_CIPHER_ALGORITHM);
}

/**
* 转换密钥
*
* @param key   二进制密钥
* @return 密钥
*/
private static Key toKey(byte[] key){
//生成密钥
return new SecretKeySpec(key, KEY_ALGORITHM);
}

/**
* 加密
*
* @param file  待加密数据
* @param key   二进制密钥
* @param cipherAlgorithm   加密算法/工作模式/填充方式
* @return byte[]   加密数据
* @throws Exception
*/
private static OutputStream encrypt(File file, byte[] key, String cipherAlgorithm) throws Exception{
//还原密钥
Key k = toKey(key);
return encrypt(file, k, cipherAlgorithm);
}

/**
* 加密
*
* @param file  待加密数据
* @param key   密钥
* @param cipherAlgorithm   加密算法/工作模式/填充方式
* @return byte[]   加密数据
* @throws Exception
*/
private static OutputStream encrypt(File file, Key key, String cipherAlgorithm) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行操作
CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(file), cipher);
return cos;
}

/**
* 解密
*
* @param file  待解密数据
* @param key   二进制密钥
* @param cipherAlgorithm   加密算法/工作模式/填充方式
* @return byte[]   解密数据
* @throws Exception
*/
private static InputStream decrypt(File file, byte[] key,String cipherAlgorithm) throws Exception{
//还原密钥
Key k = toKey(key);
return decrypt(file, k, cipherAlgorithm);
}

/**
* 解密
*
* @param file  待解密文件
* @param key   密钥
* @param cipherAlgorithm   加密算法/工作模式/填充方式
* @return byte[]   解密数据
* @throws Exception
*/
private static InputStream decrypt(File file, Key key, String cipherAlgorithm) throws Exception{
//实例化
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
//使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
//执行操作
CipherInputStream cis = new CipherInputStream(new FileInputStream(file), cipher);
return cis;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: