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; } }
相关文章推荐
- Android清空应用内部文件缓存
- 关于Activity
- android ndk开发支持64位时遇到undefined reference to `__system_property_get'
- android:visibility
- Android中asset文件夹和raw文件夹区别
- Android中轻松显示Gif图片
- android 自定义双击事件
- 解决android数据库并发访问异常
- android 截图实现
- Android类参考---Fragment
- Android下各个按键对应的key code
- android触碰事件
- Android中调节Activity变亮变暗(屏幕亮度)
- Android 应用开发
- android ios主流浏览器User-Agent
- Android图片上传实现预览效果
- Android 6.0: 动态权限管理的解决方案
- [置顶] android开发之 listview中的item去掉分割线 隐藏分割线
- android 动态设置TextView值,例:金额添加
- Android 自己自定义监听模式套路