利用ASM加密Jar包字符串
Posted ZhangJianIsAStark
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用ASM加密Jar包字符串相关的知识,希望对你有一定的参考价值。
ASM是一个JAVA字节码分析、创建和修改的开源应用框架。
它可以动态生成二进制格式的stub类或其它代理类,或者在类被JAVA虚拟机装入内存之前,动态修改类。
一般情况下,Class文件是通过javac编译器产生的,然后通过类加载器加载到虚拟机内,再通过执行引擎去执行。
现在我们可以通过ASM的API直接生成符合Java虚拟机规范的Class字节流,
从这个角度来看,ASM承担的工作与javac解释器的工作相似。
ASM可以从字节码的角度,分析、创建一个类,同时也可以修改一个已经被编译过的类文件。
因此,我们可以通过ASM来实现诸如代码生成、代码混淆、代码转换等等以字节码为操作目标的工作。
本篇博客就记录一下利用ASM来加密Jar中字符串的方法。
这么做的初衷是:
Proguard只能混淆代码,无法混淆代码中使用的字符串。
想要混淆字符串,可能要借助收费的DexGuard。
于是,可以利用ASM来直接加密Jar包。
P.S.:
我目前的公司,产品以SDK为主,输出为Jar包。
因此这里以加密Jar包为例。
关于ASM框架的详细内容就不再赘述了,直接上加密Jar的代码。
这里依赖的lib为asm-all-5.2.jar和commons-lang3-3.5.jar。
1、定义加密字符的方法
package com.encrypt.util;
/**
* Created by zhangjian on 18-3-29
*/
public class Transform
/**
* 将字符串编码成16进制数字,适用于所有字符(包括中文)
*/
public static byte[] encode(byte[] bytes, String key)
int len = bytes.length;
int keyLen = key.length();
for (int i = 0; i < len; i++)
bytes[i] = (byte) (bytes[i] ^ key.charAt(i % keyLen));
return bytes;
/**
* 将16进制数字解码成字符串,适用于所有字符(包括中文)
*/
public static String decode(byte[] bytes, String key)
int len = bytes.length;
int keyLen = key.length();
for (int i = 0; i < len; i++)
bytes[i] = (byte) (bytes[i] ^ key.charAt(i % keyLen));
return new String(bytes);
2、定义Main Class
/**
* Created by zhangjian on 18-3-29
*/
public class Encrypt
//Transform.java对应class文件路径
private static final String TRANSFORM_FILE = Transform.class.getName().replace(".", "/") + ".class";
public static void main(String[] args) throws IOException
if (args == null || args.length < 1)
return;
//读取传入的待加密的Jar包路径
String path = args[0];
if (!path.endsWith(".jar"))
return;
//读取Transform.java对应的Java字节码
byte b[] = readClass();
//指定输入和输出文件
int index = path.lastIndexOf(".jar");
File jarIn = new File(path);
File jarOut = new File(path.substring(0, index) + "obfused.jar");
try
//开始处理Jar包
processJar(jarIn, jarOut, Charset.forName("UTF-8"), Charset.forName("UTF-8"), b);
catch (IllegalArgumentException e)
if ("MALFORMED".equals(e.getMessage()))
processJar(jarIn, jarOut, Charset.forName("GBK"), Charset.forName("UTF-8"), b);
else
throw e;
private static byte[] readClass()
InputStream in;
try
in = Transform.class.getClassLoader().getResourceAsStream(TRANSFORM_FILE);
int len = in.available();
byte[] b = new byte[len];
in.read(b);
in.close();
return b;
catch (Exception e)
e.printStackTrace();
return null;
private static void processJar(File jarIn, File jarOut, Charset charsetIn,
Charset charsetOut, byte[] out) throws IOException
ZipInputStream zis = null;
ZipOutputStream zos = null;
try
zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(jarIn)), charsetIn);
zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(jarOut)), charsetOut);
HashSet<String> processedEntryNameSet = new HashSet<>();
ZipEntry entryIn;
while ((entryIn = zis.getNextEntry()) != null)
String entryName = entryIn.getName();
if (!processedEntryNameSet.contains(entryName))
ZipEntry entryOut = new ZipEntry(entryIn);
entryOut.setCompressedSize(-1);
zos.putNextEntry(entryOut);
if (!entryIn.isDirectory())
if (entryName.endsWith(".class"))
//开始处理Class文件
processClass(zis, zos);
else
copy(zis, zos);
zos.closeEntry();
processedEntryNameSet.add(entryName);
ZipEntry inject = new ZipEntry(TRANSFORM_FILE);
zos.putNextEntry(inject);
zos.write(out);
zos.closeEntry();
finally
closeQuietly(zos);
closeQuietly(zis);
private static void processClass(InputStream classIn, OutputStream classOut) throws IOException
//利用ASM中的ClassReader和ClassWriter来读、写class文件
ClassReader cr = new ClassReader(classIn);
ClassWriter cw = new ClassWriter(1);
//通过ASM中的ClassVistor来访问class中的各个域,并进行修改
ClassVisitor aia = ClassVisitorFactory.create(cr.getClassName(), cw);
cr.accept(aia, 0);
//最后写入
classOut.write(cw.toByteArray());
classOut.flush();
private static void copy(InputStream in, OutputStream out) throws IOException
byte[] buffer = new byte[8192];
int c;
while ((c = in.read(buffer)) != -1)
out.write(buffer, 0, c);
private static void closeQuietly(Closeable target)
if (target != null)
try
target.close();
catch (Exception e)
// Ignored.
3、定义ClassVistor
/**
* Created by zhangjian on 18-3-29
*/
public final class ClassVisitorFactory
private ClassVisitorFactory()
public static ClassVisitor create(String className, ClassWriter cw)
//这里指定不修改Transform对应的class文件
if (Transform.class.getName().replace('.', '/').equals(className))
return createEmpty(cw);
//也可以增加类似白名单的功能,过滤其它不需要修改的类
//StringFieldClassVisitor就是加密String的核心代码
return new StringFieldClassVisitor(cw);
private static ClassVisitor createEmpty(ClassWriter cw)
return new ClassVisitor(Opcodes.ASM5, cw) ;
我们来看看StringFieldClassVisitor:
/**
* Created by zhangjian on 18-3-29
*/
class StringFieldClassVisitor extends ClassVisitor
private static final String TRANSFORM_PATH = Transform.class.getName().replace('.', '/');
private boolean mIsClInitExists;
private List<ClassStringField> mStaticFinalFields = new ArrayList<>();
private List<ClassStringField> mStaticFields = new ArrayList<>();
private List<ClassStringField> mFinalFields = new ArrayList<>();
private String mClassName;
StringFieldClassVisitor(ClassWriter cw)
super(Opcodes.ASM5, cw);
//这其实都是访问每个class文件的回调接口
//记录当前访问的class name
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
mClassName = name;
super.visit(version, access, name, signature, superName, interfaces);
//记录String类型的成员变量
//ClassStringField是自定义的一个结构体
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
if (ClassStringField.STRING_DESC.equals(desc) && name != null)
if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) != 0)
mStaticFinalFields.add(new ClassStringField(name, (String) value));
value = null;
if ((access & Opcodes.ACC_STATIC) != 0 && (access & Opcodes.ACC_FINAL) == 0)
mStaticFields.add(new ClassStringField(name, (String) value));
value = null;
if ((access & Opcodes.ACC_STATIC) == 0 && (access & Opcodes.ACC_FINAL) != 0)
mFinalFields.add(new ClassStringField(name, (String) value));
value = null;
return super.visitField(access, name, desc, signature, value);
//访问方法时,开始加密
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (mv != null)
if ("<clinit>".equals(name))
mIsClInitExists = true;
// If clinit exists meaning the static fields (not final) would have be inited here.
mv = new MethodVisitor(Opcodes.ASM5, mv)
private String lastStashCst;
@Override
public void visitCode()
super.visitCode();
// Here init static final fields.
for (ClassStringField field : mStaticFinalFields)
if (field.mValue == null)
continue;
//加密StaticFinal域
encode(super.mv, field.mValue);
super.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.mName, ClassStringField.STRING_DESC);
@Override
public void visitLdcInsn(Object cst)
// Here init static or static final fields, but we must check field name int 'visitFieldInsn'
if (cst != null && cst instanceof String && !isEmptyAfterTrim((String) cst))
lastStashCst = (String) cst;
//加密
encode(super.mv, lastStashCst);
else
lastStashCst = null;
super.visitLdcInsn(cst);
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc)
if (mClassName.equals(owner) && lastStashCst != null)
boolean isContain = false;
for (ClassStringField field : mStaticFields)
if (field.mName.equals(name))
isContain = true;
break;
if (!isContain)
for (ClassStringField field : mStaticFinalFields)
if (field.mName.equals(name) && field.mValue == null)
field.mValue = lastStashCst;
break;
lastStashCst = null;
super.visitFieldInsn(opcode, owner, name, desc);
;
else if ("<init>".equals(name))
// Here init final(not static) and normal fields
mv = new MethodVisitor(Opcodes.ASM5, mv)
@Override
public void visitLdcInsn(Object cst)
// We don't care about whether the field is final or normal
if (cst != null && cst instanceof String && !isEmptyAfterTrim((String) cst))
//加密
encode(super.mv, (String) cst);
else
super.visitLdcInsn(cst);
;
else
mv = new MethodVisitor(Opcodes.ASM5, mv)
@Override
public void visitLdcInsn(Object cst)
if (cst != null && cst instanceof String && !isEmptyAfterTrim((String) cst))
// If the value is a static final field
for (ClassStringField field : mStaticFinalFields)
if (cst.equals(field.mValue))
super.visitFieldInsn(Opcodes.GETSTATIC, mClassName, field.mName, ClassStringField.STRING_DESC);
return;
// If the value is a final field (not static)
for (ClassStringField field : mFinalFields)
// if the value of a final field is null, we ignore it
if (cst.equals(field.mValue))
super.visitVarInsn(Opcodes.ALOAD, 0);
super.visitFieldInsn(Opcodes.GETFIELD, mClassName, field.mName, "Ljava/lang/String;");
return;
encode(super.mv, (String) cst);
return;
super.visitLdcInsn(cst);
;
return mv;
@Override
public void visitEnd()
if (!mIsClInitExists && !mStaticFinalFields.isEmpty())
MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
// Here init static final fields.
for (ClassStringField field : mStaticFinalFields)
if (field.mValue == null)
continue; // It could not be happened
encode(mv, field.mValue);
mv.visitFieldInsn(Opcodes.PUTSTATIC, mClassName, field.mName, ClassStringField.STRING_DESC);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
super.visitEnd();
private boolean isEmptyAfterTrim(String str)
return str == null || str.length() == 0 || str.trim().length() == 0;
//实际的加密函数
private void encode(MethodVisitor mv, String str)
String key = UUID.randomUUID().toString().replace("-", "").trim().substring(0, 6);
//利用Transform中的函数加密
byte[] enc = Transform.encode(str.getBytes(), key);
int len = enc.length;
mv.visitIntInsn(Opcodes.SIPUSH, len);
mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BYTE);
for (int i = 0; i < len; i++)
mv.visitInsn(Opcodes.DUP);
mv.visitIntInsn(Opcodes.SIPUSH, i);
mv.visitIntInsn(Opcodes.BIPUSH, enc[i]);
mv.visitInsn(Opcodes.BASTORE);
mv.visitLdcInsn(key);
//最后class中的string被替换为Transform.decode(new byte[] 字符串加密后的字符数组, key)
mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRANSFORM_PATH, "decode", "([BLjava/lang/String;)Ljava/lang/String;", false);
上文中的ClassStringField就是个简单的结构体,类似于:
/**
* Created by zhangjian on 18-3-29
*/
class ClassStringField
static final String STRING_DESC = "Ljava/lang/String;";
String mName;
String mValue;
ClassStringField(String name, String value)
mName = name;
mValue = value;
4、总结
至此,主要思路记录完毕。
个人觉得为了更好地驾驭ASM,需要理解Class文件字节码的格式,
以及ASM解读Class文件的流程。
以上是关于利用ASM加密Jar包字符串的主要内容,如果未能解决你的问题,请参考以下文章