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

来源:互联网 发布:广州525孕妇摄影淘宝网 编辑:程序博客网 时间:2024/04/30 10:22

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

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

加密方式

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

  • AES/DES加密速度快,适合大量数据,DES容易破解,一般用3重DES,后来又出现了更快更安全的AES
  • RSA是公钥加密,速度慢,只能处理少量数据,优点是公钥即使在不安全的网络上公开,也能保证安全

由此加密算法选定了AES加密算法,在网上搜索AES加密实现的时候发现几乎都是对byte[]的加密解密操作,考虑到Android机上对图片做解密操作可能对内存消耗大,尝试找有没有基于Stream的加密解密方式,经过了一番资料查找找到了支持AES加密解密的CipherInputStreamCipherOutputStream借助这两个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;    ······}

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

@Overridepublic 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重新加密。

主要代码如下

@Overridepublic 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;    }}@Overridepublic 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;    }}
2 0