Glide加载AES加密的网络图片
1. AES加密工具
public class CXAESUtil {
public static final String AES_KEY ="thisisaeskey";
private final static String HEX = "0123456789ABCDEF";
private static final int keyLenght = 16;
private static final String defaultV = "0";
/**
* 加密
*
* @param key
* 密钥
* @param src
* 加密文本
* @return
* @throws Exception
*/
public static String encrypt(String key, String src) throws Exception {
// /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
byte[] result = encrypt(rawKey, src.getBytes("utf-8"));
// result = Base64.encode(result, Base64.DEFAULT);
return toHex(result);
}
/**
* 加密
*
* @param key
* 密钥
* @param src
* 加密文本
* @return
* @throws Exception
*/
public static String encrypt2Java(String key, String src) throws Exception {
// /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
byte[] result = encrypt2Java(rawKey, src.getBytes("utf-8"));
// result = Base64.encode(result, Base64.DEFAULT);
return toHex(result);
}
/**
* 解密
*
* @param key
* 密钥
* @param encrypted
* 待揭秘文本
* @return
* @throws Exception
*/
public static String decrypt(String key, String encrypted) throws Exception {
byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
byte[] enc = toByte(encrypted);
// enc = Base64.decode(enc, Base64.DEFAULT);
byte[] result = decrypt(rawKey, enc);
// /result = Base64.decode(result, Base64.DEFAULT);
return new String(result, "utf-8");
}
/**
* 密钥key ,默认补的数字,补全16位数,以保证安全补全至少16位长度,android和ios对接通过
* @param str
* @param strLength
* @param val
* @return
*/
private static String toMakekey(String str, int strLength, String val) {
int strLen = str.length();
if (strLen < strLength) {
while (strLen < strLength) {
StringBuffer buffer = new StringBuffer();
buffer.append(str).append(val);
str = buffer.toString();
strLen = str.length();
}
}
return str;
}
/**
* 真正的加密过程
* 1.通过密钥得到一个密钥专用的对象SecretKeySpec
* 2.Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES");
* @param key
* @param src
* @return
* @throws Exception
*/
private static byte[] encrypt(byte[] key, byte[] src) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
// TODO: 2021/7/19
// cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(src);
return encrypted;
}
/**
* 真正的加密过程
*
* @param key
* @param src
* @return
* @throws Exception
*/
private static byte[] encrypt2Java(byte[] key, byte[] src) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
byte[] encrypted = cipher.doFinal(src);
return encrypted;
}
/**
* 真正的解密过程
*
* @param key
* @param encrypted
* @return
* @throws Exception
*/
private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
// TODO: 2021/7/19
// cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
/**
* 把16进制转化为字节数组
* @param hexString
* @return
*/
public static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
return result;
}
/**
* 二进制转字符,转成了16进制
* 0123456789abcdefg
* @param buf
* @return
*/
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
/**
* 初始化 AES Cipher
* @param sKey
* @param cipherMode
* @return
*/
public static Cipher initAESCipher(String sKey, int cipherMode) {
// 创建Key gen
// KeyGenerator keyGenerator = null;
Cipher cipher = null;
try {
/*
* keyGenerator = KeyGenerator.getInstance("AES");
* keyGenerator.init(128, new SecureRandom(sKey.getBytes()));
* SecretKey secretKey = keyGenerator.generateKey(); byte[]
* codeFormat = secretKey.getEncoded(); SecretKeySpec key = new
* SecretKeySpec(codeFormat, "AES"); cipher =
* Cipher.getInstance("AES"); //初始化 cipher.init(cipherMode, key);
*/
byte[] rawKey = toMakekey(sKey, keyLenght, defaultV).getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
cipher = Cipher.getInstance("AES");
// TODO: 2021/7/19
// cipher.init(cipherMode, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
cipher.init(cipherMode, skeySpec);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
} catch (NoSuchPaddingException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
} catch (InvalidKeyException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
}
// catch (InvalidAlgorithmParameterException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
return cipher;
}
/**
* 对文件进行AES加密
* @param sourceFile
* @param toFilePath 加密到文件的全路径
* @param sKey
* @return
*/
public static File encryptFile(File sourceFile, String toFilePath, String sKey) {
// 新建临时加密文件
File encrypfile = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(sourceFile);
encrypfile = new File(toFilePath);
outputStream = new FileOutputStream(encrypfile);
Cipher cipher = initAESCipher(sKey, Cipher.ENCRYPT_MODE);
// 以加密流写入文件
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
byte[] cache = new byte[1024];
int nRead = 0;
while ((nRead = cipherInputStream.read(cache)) != -1) {
outputStream.write(cache, 0, nRead);
outputStream.flush();
}
cipherInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
}
}
return encrypfile;
}
public static InputStream decryptFileToInputStream(File sourceFile,String sKey){
InputStream inputStream = null;
//创建cipher对象,
Cipher cipher = initAESCipher(sKey, Cipher.DECRYPT_MODE);
//从源文件中读取,生成输入流
try {
inputStream = new FileInputStream(sourceFile);
//根据输出流创建可以加密的输出流
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
return cipherInputStream;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return inputStream;
}
/**
* AES方式解密文件
* @param sourceFile
* @param toFilePath
* @return
*/
public static File decryptFile(File sourceFile, String toFilePath, String sKey) {
File decryptFile = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
//解密后存放的目标文件
decryptFile = new File(toFilePath);
//创建cipher对象,
Cipher cipher = initAESCipher(sKey, Cipher.DECRYPT_MODE);
//从源文件中读取,生成输入流
inputStream = new FileInputStream(sourceFile);
//从目标文件中获取输出流
outputStream = new FileOutputStream(decryptFile);
//根据输出流创建可以加密的输出流
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
//创建缓存
byte[] buffer = new byte[1024];
int r;
//从输入流读取
while ((r = inputStream.read(buffer)) >= 0) {
//写入到输出流中
cipherOutputStream.write(buffer, 0, r);
}
cipherOutputStream.close();
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use File |
// Settings | File Templates.
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace(); // To change body of catch statement use
// File | Settings | File Templates.
}
}
return decryptFile;
}
}
2. 自定义MyGlideModule
- 前提是安装了glide依赖
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
- 开始自定义
@GlideModule
public class MyGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
//这是关键
registry.append(MyDptFile.class, InputStream.class,new DptModelLoader.DptLoaderFactory());
}
/**
* 这里不开启,避免添加相同的modules两次
* @return
*/
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
3. 自定义模型 MyDptFile
public class MyDptFile {
private File file;
public MyDptFile(File file) {
this.file = file;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
}
4. 自定义 DptModelLoader
public class DptModelLoader implements ModelLoader<MyDptFile, InputStream> {
private static final String TAG="mylog_DptModelLoader";
@Nullable
@Override
public LoadData<InputStream> buildLoadData(@NonNull MyDptFile myDptFile, int width, int height, @NonNull Options options) {
// Log.i(TAG,"buildLoadData:"+myDptFile.getFile().getAbsolutePath());
return new LoadData<InputStream>(new ObjectKey(myDptFile),new DptModelLoader.DPtDataFetcher(myDptFile));
}
@Override
public boolean handles(@NonNull MyDptFile myDptFile) {
return true;
}
public static class DptLoaderFactory implements ModelLoaderFactory<MyDptFile, InputStream> {
@NonNull
@Override
public ModelLoader<MyDptFile, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new DptModelLoader();
}
@Override
public void teardown() {
}
}
public static class DPtDataFetcher implements DataFetcher<InputStream> {
private MyDptFile file;
public DPtDataFetcher(MyDptFile file) {
this.file = file;
}
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
// Log.i(TAG,"图片解密--1, file= "+file.getFile().getAbsolutePath());
if (file==null){
return;
}
//读取文件流,直接把文件流AES解密
callback.onDataReady(CXAESUtil.decryptFileToInputStream(file.getFile(),CXAESUtil.AES_KEY));
}
/** 这里是用来释放IO流的*/
@Override
public void cleanup() {
}
@Override
public void cancel() {
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
}
5. 图片下载模型 DptDownload
前提是安装了OkHttp的依赖,注意:这里可以替换任意的文件下载框架,只要能把文件下载就好了。
public class DptDownload {
/**
* 下载文件
* @param url 文件网络路径
* @param fileDir 文件下载本地路径(肯定是存储在的)
* @param fileName 文件名称(包含后缀)
* @param callBack 回调
*/
public static void okHttpDownloadFile(String url, String fileDir, String fileName, final CallBackDownloadFile callBack) {
final File incompleteFile = new File(fileDir, fileName + ".ysy");
final File completeFile = new File(fileDir, fileName);
if (!incompleteFile.exists()) {
try {
incompleteFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Request request = new Request.Builder().url(url).build();
new OkHttpClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
incompleteFile.delete();
callBack.onFailure(e);
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
Sink sink = Okio.sink(incompleteFile);
BufferedSink bufferedSink = Okio.buffer(sink);
bufferedSink.writeAll(response.body().source());
bufferedSink.close();
incompleteFile.renameTo(completeFile);
if (bufferedSink != null) {
bufferedSink.close();
}
// callBack.onDownloadOk(response.body().string());
callBack.onDownloadOk(completeFile.getAbsolutePath());
}
});
}
public interface CallBackDownloadFile{
void onDownloadOk(String str);
void onFailure(IOException e);
}
}
6. 定义图片加载工具DptLoadImage
public class DptLoadImage {
private static final String TAG="mylog_DptLoadImage";
/** 缓存文件夹的名称,清除缓存时可以指定这个文件夹*/
private static final String DPT_CACHE_DIR="dptCache";
public static void load(String imgUrl, final ImageView imageView){
final Context context=imageView.getContext();
String externalCacheDirPath=context.getExternalCacheDir().getAbsolutePath();
File dptFileCacheDir=new File(externalCacheDirPath+ File.separator+DPT_CACHE_DIR);
if (!dptFileCacheDir.exists()){
Log.i(TAG,"不存在缓存文件夹,进行创建");
dptFileCacheDir.mkdir();
}
//根据图片url获取存储的文件名称,防止地址泄露
try {
String fileName= CXAESUtil.encrypt(CXAESUtil.AES_KEY,imgUrl);
//判断这个文件本地是否存在
File imageFile=new File(dptFileCacheDir,fileName);
if (imageFile.exists()){
//文件存在,直接加载文件,不用网络下载,直接进行解密成文件流
Glide.with(context).load(new MyDptFile(imageFile)).placeholder(R.drawable.common_placeholder).into(imageView);
}else{
DptDownload.okHttpDownloadFile(imgUrl,dptFileCacheDir.getAbsolutePath(),fileName,new DptDownload.CallBackDownloadFile() {
@Override
public void onDownloadOk(final String str) {
ThreadUtils.runOnUI(new Runnable() {
@Override
public void run() {
Glide.with(context).load(new MyDptFile(new File(str))).into(imageView);
}
});
}
@Override
public void onFailure(IOException e) {
showErrorImage(imageView);
}
});
}
} catch (Exception e) {
e.printStackTrace();
showErrorImage(imageView);
}
}
/** 显示错误图片*/
private static void showErrorImage(ImageView imageView){
Glide.with(imageView).load(R.drawable.common_placeholder).into(imageView);
}
/** 获取缓存存在的路径*/
public static File getEptCachePath(Context context){
String externalCacheDirPath=context.getExternalCacheDir().getAbsolutePath();
return new File(externalCacheDirPath+ File.separator+DPT_CACHE_DIR);
}
}
7. 使用方法
DptLoadImage.load(imgUrl,imageView);
8. 制作批量加密工具jar包,可命令行操作
- 使用idea编辑器新建一个项目,添加commons-io-2.6.jar到项目中
- 代码,只有一个类
public class Main {
public static void main(String[] args) {
if (args.length==0){
helpPrint();
return;
}
if (args[0].equals("-file")){
if (args.length!=4){
helpPrint();
return;
}
fileEncrypt(args);
}else if (args[0].equals("-dir")){
if (args.length!=4){
helpPrint();
return;
}
dirEncrypt(args);
}else if (args[0].equals("-str")){
if (args.length!=3){
helpPrint();
return;
}
strEncrypt(args);
}else{
helpPrint();
}
}
private static void helpPrint(){
System.out.println("单文件加密: java -jar <*.jar> -file <AES密码> <源文件路径> <加密后的文件路径>");
System.out.println("文件夹加密: java -jar <*.jar> -dir <AES密码> <源文件夹路径> <加密后文件夹路径>");
System.out.println("字符串加密: java -jar <*.jar> -str <AES密码> <源字符串>");
}
/**
* 文件夹内所有文件加密
* @param args
*/
public static void dirEncrypt(String[] args){
//第1个参数是密码, 第2个参数是源文件夹,第3个参数是加密后的文件夹,
String aesKey=args[1];
//源文件夹
String sourceDirPath=args[2];
//加密后的文件夹
String encryptDirPath=args[3];
//目标文件夹是否存在,存在就删除,重新建立
File encryptDir=new File(encryptDirPath);
if (encryptDir.exists()){
//不能随便删除人家的文件夹,很危险,让用户自己去删除
System.out.println("操作异常! 已存在文件夹:"+encryptDirPath+", 请使用新的文件夹名称!");
return;
}
encryptDir.mkdir();
//列出源文件中所有的文件
List<File> files = (List<File>) FileUtils.listFiles(new File(sourceDirPath), EmptyFileFilter.NOT_EMPTY, null);
if (files.size()==0){
System.out.println("操作异常! 源文件夹中没有需要加密的文件");
return;
}
for (int i=0;i<files.size();i++) {
File file=files.get(i);
// System.out.println(file.getAbsolutePath());
//开始加密
//获取文件名称name
String encryptFilePath=encryptDir.getAbsolutePath()+File.separator+file.getName()+".ept";
File encryptFile=new File(encryptFilePath);
if (!encryptFile.exists()){
try {
encryptFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
CXAESUtil.encryptFile(file,encryptFilePath,aesKey);
System.out.println("正在加密 ( "+i+"/"+files.size()+" ) ...");
}
System.out.println("加密完成! 输出文件夹路径:"+encryptDirPath);
}
/**
* 单个文件加密
* @param args
*/
public static void fileEncrypt(String[] args) {
//第1个参数是密码, 第2个参数是源文件,第3个参数是加密后的文件,
String aesKey=args[1];
//源文件
String sourceFilePath=args[2];
//源文件的大小
long len = FileUtils.sizeOf(new File(sourceFilePath));
System.out.println("源文件大小="+len);
//获取加密后文件路径
String encryptFilePath=args[3];
File fileEncrypt=new File(encryptFilePath);
if (!fileEncrypt.exists()){
try {
fileEncrypt.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}else{
System.out.println("操作异常!文件已经存在:"+encryptFilePath);
}
CXAESUtil.encryptFile(new File(sourceFilePath),encryptFilePath,aesKey);
System.out.println("加密完成! 路径 : "+encryptFilePath);
}
public static void strEncrypt(String[] args){
String aesKey=args[1];
String sourceStr=args[2];
//加密后的
try {
String outStr= CXAESUtil.encrypt(aesKey,sourceStr);
System.out.println(outStr);
} catch (Exception e) {
e.printStackTrace();
System.out.println("操作异常! "+e.getLocalizedMessage());
}
}
}
-
生成jar包,<*>.jar
-
使用方法
单文件加密: java -jar <*.jar> -file <AES密码> <源文件路径> <加密后的文件路径>
文件夹内多图片加密: java -jar <*.jar> -dir <AES密码> <源文件夹路径> <加密后文件夹路径>
字符串加密: java -jar <*.jar> -str <AES密码> <源字符串>