android 如何给strings.xml文件内容加密?

序:本篇文章只是交流,如需上线还需要自己多完善
各位同学可想过如何给strings.xml里面的内容进行加密,然后在使用的地方进行解密呢?
我们想一想如果要做加密,应该要解决什么问题?

什么时候给strings.xml进行加密?
activity和xml里面的内容,怎么解密?

第一个问题
我们可以在编译成apk的时候进行任务拦截,然后处理合并之后的strings.xml文件内容,注意,在清单文件或anim等非常规xml文件中使用的不能加密
伪代码如下
void attachObs(Project pj) {
pj.afterEvaluate(new Action() {
@Override
void execute(Project project) {

            Map<Project, Set<Task>> allTasks = project.getAllTasks(true)
            for (Map.Entry<Project, Set<Task>> projectSetEntry : allTasks.entrySet()) {
                Set<Task> value = projectSetEntry.getValue()
                for (Task task : value) {

                        if(task.name.matches("^merge\S*ReleaseResources\$")) {
                           
                            String channel = task.name
                            channel = task.name.substring(5, channel.length() - 16)
                            task.doFirst { t ->
                                println("-------mergeReleaseResources---------" + channel)

                                for (File file : t.getInputs().getFiles().getFiles()) {
                                    if (file.isDirectory()) {
                                        getStringFile(file, project, channel.toLowerCase())
                                    }
                                }

                                modifyAppStringXmlFile(project)

                            }
                            
                        }

                }
            }
        }
    }
}


/***
 * 给string.xml进行加密
 * @param file
 * @param project
 * @param channel
 */
void getStringFile(File file, Project project, String channel) {
    for (File cfile : file.listFiles()) {
        if (cfile.isDirectory()) {
            getStringFile(cfile, project, channel)
        } else if (cfile.absolutePath.contains("values") && cfile.name.endsWith(".xml")) {
            println("values file->" + cfile.absolutePath)
            // 处理所需要加密的values.xml文件,因为strings.xml是一个xml文件,所以我们完全可以使用XmlParser来解析,而里面的内容,其实就是一个一个node节点,我们把要加密的node节点,添加到app这个module的strings.xml文件中,这样打包会覆盖其原来的内容
            
            
        }
    }
}

复制代码
下边是加密伪代码
boolean isAppStringFile = false;
if(absolutePath.contains(“values”+File.separator+“strings.xml”)) {
// 说明是app下的values下的strings.xml
Utils.println(“获取到app下的values下的strings.xml”);
appStringFile = xmlFile;
isAppStringFile = true;
}
try {
XmlParser xmlparser = new XmlParser();
Node xml = xmlparser.parse(xmlFile.getPath());
if(xml == null) {
return;
}
Iterator iterator = xml.iterator();

while(iterator.hasNext()) {
    Object next = iterator.next();
    if(next instanceof Node) {
        Node node = (Node) next;
        String nodeText = node.text();
        if(node.name().equals("string")) {
            if(nodeText != null && nodeText.length() > 0) {
                String nodeNameAttr = node.attribute("name").toString();
                Utils.println("string--- node=" + nodeText + "  name=" + node.name() + "  text=" + node.text() +
                        " nodeNameAttr=" + nodeNameAttr + " node.attribute="+node.attributes() );
                // 这里就可以对你的strings.xml的node节点做加密
                if(nodeNameAttr.equals(你的strings.xml里面的要加密的name的名字)){
                    // 这里保存你的node节点,到modifyAppStringXmlFile方法去修改app下的strings.xml文件
                }
            }
        }
    }
}

} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
复制代码

/***
* 修改app下的strings.xml文件
* @param project
*/
void modifyAppStringXmlFile(Project project) {

    removeStringXmlSameNode(project,appStringFile,needEntryNodeList) // 这个方法是为了在jenkins上打包的时候,先删除掉上一次加密过的strings.xml里面的节点,demo中可注释
    addNodeToStringXml(project,appStringFile,needEntryNodeList)

}

/***
 * 添加node节点到strings.xml文件中
 * @param project
 * @param appStringFile
 * @param needEntryNodeList
 */
void addNodeToStringXml(Project project,File appStringFile,List<Node> needEntryNodeList) {
    def xml = project.file(appStringFile)
    def appStringxml = new XmlParser().parseText(xml.getText())
    if(appStringxml != null) {
        List<Node> appendNodeList = new ArrayList<>()

        for(Node node1:needEntryNodeList) {
            String needEntryText = node1.text();
            needEntryText = Utils.getRandomString(5) + needEntryText
            String str = AESUtil.encrypt(needEntryText,"hello");
            println("string---加密后"+str)
            Node node2 = new Node(node1.parent(),"string",node1.attributes(),str)
            appendNodeList.add(node2)
            appStringxml.append(node2)
        }
        // 保存修改后的strings.xml文件
        def serialize = groovy.xml.XmlUtil.serialize(appStringxml)
        xml.withWriter {writer->
            writer.write(serialize)
        }

       
    }
}

复制代码
上边是加密的核心代码,现在再来回顾一下逻辑

遍历所有的strings.xml文件(有的string写到了values.xml中)
找到要加密的strings.xml文件中要加密的node节点
将要加密的node节点写入到app下的strings.xml文件中,以便可以用加密后的节点覆盖加密前的节点

下边我们再来说一说如何解密?
其实当时做的时候,我是有一些纠结的,因为我不知道在哪里去对R.string.xxx去进行解密,而且如果布局xml文件中有使用的话,那又该怎么做解密呢?
其实所有的资源文件都是通过resources类来做解析的,这里的原理跟换肤有点类似,伪代码如下

public class WxjResources extends Resources {

private static final String TAG = "BaseActivity";

private Resources mResources;

public WxjResources(Resources resources) {
    super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
    mResources = resources;
}


@NonNull
@Override
public CharSequence getText(int id) throws NotFoundException {
    //CharSequence value = mResources.getResourceEntryName(id);
    CharSequence value = mResources.getText(id);

    value = jiemiStr2(value);
    Log.d(TAG, "getText 2222222222 value===="+value+"id="+id);
    return value;
}

@Override
public CharSequence getText(int id, CharSequence def) {
    CharSequence value = mResources.getText(id, def);
    value = jiemiStr2(value);
    Log.d(TAG, "getText 2222222222 value===="+value+"id="+id);
    return value;
}

private CharSequence jiemiStr2(CharSequence value) {
    if(AESUtil.checkHexString(value.toString())) {
        String decryptValue = null;
        try {
            decryptValue = AESUtil.decrypt(value.toString(), "hello");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d(TAG, "jiemistr2 decryptValue===="+decryptValue);
        if(TextUtils.isEmpty(decryptValue)) {
            return value;
        }
        decryptValue = decryptValue.substring(5);

        return decryptValue;
    }
    Log.d(TAG, "jiemistr2 value===="+value);
    return value;
}

private String jiemiStr(String value) {
    if(AESUtil.checkHexString(value)) {
        String decryptValue = null;
        try {
            decryptValue = AESUtil.decrypt(value, "hello");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d(TAG, "jiemiStr decryptValue===="+decryptValue);
        if(TextUtils.isEmpty(decryptValue)) {
            return value;
        }
        decryptValue = decryptValue.substring(5);

        return decryptValue;
    }
    Log.d(TAG, "jiemistr value===="+value);
    return value;
}

@NonNull
@Override
public String getString(int id, Object... formatArgs) throws NotFoundException {
    String value = mResources.getString(id, formatArgs);
    if(TextUtils.isEmpty(value)) {
        return value;
    }
    Log.d(TAG, "getString 111111111111 value===="+value);
    value = jiemiStr(value);
    return value;
}

@NonNull
@Override
public String getString(int id) throws NotFoundException {
    String value = mResources.getString(id);
    if(TextUtils.isEmpty(value)) {
        return value;
    }
    value = jiemiStr(value);
    Log.d(TAG, "getString 2222222222 value===="+value+"id="+id);
    return value;
}






@RequiresApi(api = Build.VERSION_CODES.O)
@NonNull
@Override
public Typeface getFont(int id) throws NotFoundException {
    return mResources.getFont(id);
}

@NonNull
@Override
public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
    return mResources.getQuantityText(id, quantity);
}

@NonNull
@Override
public String getQuantityString(int id, int quantity, Object... formatArgs) throws NotFoundException {
    return mResources.getQuantityString(id, quantity, formatArgs);
}

@NonNull
@Override
public String getQuantityString(int id, int quantity) throws NotFoundException {
    return mResources.getQuantityString(id, quantity);
}

@NonNull
@Override
public CharSequence[] getTextArray(int id) throws NotFoundException {
    return mResources.getTextArray(id);
}

@NonNull
@Override
public String[] getStringArray(int id) throws NotFoundException {
    return mResources.getStringArray(id);
}

@NonNull
@Override
public int[] getIntArray(int id) throws NotFoundException {
    return mResources.getIntArray(id);
}

@NonNull
@Override
public TypedArray obtainTypedArray(int id) throws NotFoundException {
    return mResources.obtainTypedArray(id);
}

@Override
public float getDimension(int id) throws NotFoundException {
    return mResources.getDimension(id);
}

@Override
public int getDimensionPixelOffset(int id) throws NotFoundException {
    return mResources.getDimensionPixelOffset(id);
}

@Override
public int getDimensionPixelSize(int id) throws NotFoundException {
    return mResources.getDimensionPixelSize(id);
}

@Override
public float getFraction(int id, int base, int pbase) {
    return mResources.getFraction(id, base, pbase);
}

@Override
public Drawable getDrawable(int id) throws NotFoundException {
    return mResources.getDrawable(id);
}

@Override
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
    return mResources.getDrawable(id, theme);
}

@Nullable
@Override
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
    return mResources.getDrawableForDensity(id, density);
}

@Nullable
@Override
public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
    return mResources.getDrawableForDensity(id, density, theme);
}

@Override
public Movie getMovie(int id) throws NotFoundException {
    return mResources.getMovie(id);
}

@Override
public int getColor(int id) throws NotFoundException {
    return mResources.getColor(id);
}

@Override
public int getColor(int id, @Nullable Theme theme) throws NotFoundException {
    return mResources.getColor(id, theme);
}

@NonNull
@Override
public ColorStateList getColorStateList(int id) throws NotFoundException {
    return mResources.getColorStateList(id);
}

@NonNull
@Override
public ColorStateList getColorStateList(int id, @Nullable Theme theme) throws NotFoundException {
    return mResources.getColorStateList(id, theme);
}

@Override
public boolean getBoolean(int id) throws NotFoundException {
    return mResources.getBoolean(id);
}

@Override
public int getInteger(int id) throws NotFoundException {
    return mResources.getInteger(id);
}

@RequiresApi(api = Build.VERSION_CODES.Q)
@Override
public float getFloat(int id) {
        return mResources.getFloat(id);
}

@NonNull
@Override
public XmlResourceParser getLayout(int id) throws NotFoundException {
    return mResources.getLayout(id);
}

@NonNull
@Override
public XmlResourceParser getAnimation(int id) throws NotFoundException {
    return mResources.getAnimation(id);
}

@NonNull
@Override
public XmlResourceParser getXml(int id) throws NotFoundException {
    return mResources.getXml(id);
}

@NonNull
@Override
public InputStream openRawResource(int id) throws NotFoundException {
    return mResources.openRawResource(id);
}

@NonNull
@Override
public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
    return mResources.openRawResource(id, value);
}

@Override
public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
    return mResources.openRawResourceFd(id);
}

@Override
public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
    mResources.getValue(id, outValue, resolveRefs);
}

@Override
public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
    mResources.getValueForDensity(id, density, outValue, resolveRefs);
}

@Override
public void getValue(String name, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
    mResources.getValue(name, outValue, resolveRefs);
}

@Override
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
    return mResources.obtainAttributes(set, attrs);
}

@Override
public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
    mResources.updateConfiguration(config, metrics);
}

@Override
public DisplayMetrics getDisplayMetrics() {
    return mResources.getDisplayMetrics();
}

@Override
public Configuration getConfiguration() {
    return mResources.getConfiguration();
}

@Override
public int getIdentifier(String name, String defType, String defPackage) {
    return mResources.getIdentifier(name, defType, defPackage);
}

@Override
public String getResourceName(int resid) throws NotFoundException {
    return mResources.getResourceName(resid);
}

@Override
public String getResourcePackageName(int resid) throws NotFoundException {
    return mResources.getResourcePackageName(resid);
}

@Override
public String getResourceTypeName(int resid) throws NotFoundException {
    return mResources.getResourceTypeName(resid);
}

@Override
public String getResourceEntryName(int resid) throws NotFoundException {
    return mResources.getResourceEntryName(resid);
}

@Override
public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) throws IOException, XmlPullParserException {
    mResources.parseBundleExtras(parser, outBundle);
}

@Override
public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle) throws XmlPullParserException {
    mResources.parseBundleExtra(tagName, attrs, outBundle);
}

@Override
public void addLoaders(@NonNull ResourcesLoader... loaders) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        mResources.addLoaders(loaders);
    }
}

@Override
public void removeLoaders(@NonNull ResourcesLoader... loaders) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        mResources.removeLoaders(loaders);
    }
}

}
复制代码
在所有的activity中都需要去使用

public class ResEntryLifecycle implements Application.ActivityLifecycleCallbacks {

private static final String TAG = "ResEntryLifecycle";

@Override
public void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
    try {
        Resources res = activity.getResources();
        WxjResources wxjResources = new WxjResources(res);

        Field mResources = ContextThemeWrapper.class.getDeclaredField("mResources");
        mResources.setAccessible(true);
        mResources.set(activity,wxjResources);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
    if(activity instanceof AppCompatActivity) {
        installLayoutFactory(activity);
    } else {
        installLayoutFactoryByActivity(activity);
    }
}

@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {

}

private void installLayoutFactoryByActivity(Activity activity) {
    LayoutInflater layoutInflater = activity.getLayoutInflater();
    LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
        @Nullable
        @Override
        public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            Log.d(TAG, "installLayoutFactoryByActivity parent = " + parent + " name="+name + " attrs="+attrs.toString());
            LayoutInflater inflater = LayoutInflater.from(context);
            try {
                View view = null;
                if (name.indexOf('.') > 0) { //表明是自定义View
                    view = inflater.createView(name, null, attrs);
                } else {
                    String prefix = "android.widget.";
                    if(name.equals("ViewStub")) {
                        prefix = "android.view.";
                    }
                    view = inflater.createView(name, prefix, attrs);
                }
                Log.d(TAG, "报错类找不到"+view);
                if(view instanceof TextView) {
                    int[] set = {
                            android.R.attr.text        // idx 0
                    };
                    // 不需要recycler,后面会在创建view时recycle的
                    @SuppressLint("Recycle")
                    TypedArray a = context.obtainStyledAttributes(attrs, set);
                    int resourceId = a.getResourceId(0, 0);
                    if (resourceId != 0) {
                        // 在这里进行解析
                        String value = activity.getResources().getString(resourceId);
                        Log.d(TAG, "installLayoutFactoryByActivity解密后value:"+value);

                        ((TextView) view).setText(value);

                        return view;
                    }
                } else {
                    Log.d(TAG, "view 不是textview");
                }

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            return null;
        }
    });
}

private void installLayoutFactory(Activity activity) {
    LayoutInflater layoutInflater = activity.getLayoutInflater();
    LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
        @Nullable
        @Override
        public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            LayoutInflater inflater = LayoutInflater.from(context);
            AppCompatActivity activity = null;
            if (parent == null) {
                if (context instanceof AppCompatActivity) {
                    activity = ((AppCompatActivity)context);
                }
            } else if (parent.getContext() instanceof AppCompatActivity) {
                activity = (AppCompatActivity) parent.getContext();
            }
            if (activity == null) {
                return null;
            }

            AppCompatDelegate delegate = activity.getDelegate();
            int[] set = {
                    android.R.attr.text        // idx 0
            };

            // 不需要recycler,后面会在创建view时recycle的
            @SuppressLint("Recycle")
            TypedArray a = context.obtainStyledAttributes(attrs, set);
            View view = delegate.createView(parent, name, context, attrs);
            if (view == null && name.indexOf('.') > 0) { //表明是自定义View
                try {
                    view = inflater.createView(name, null, attrs);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

            if (view instanceof TextView) {
                int resourceId = a.getResourceId(0, 0);
                if (resourceId != 0) {
                    // 在这里进行解析
                    String value = activity.getResources().getString(resourceId);
                    Log.d(TAG, "解密后value:"+value);

                    ((TextView) view).setText(value);
                }
            }

            return view;
        }

        @Nullable
        @Override
        public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            return null;
        }
    });
}




@Override
public void onActivityStarted(@NonNull Activity activity) {

}

@Override
public void onActivityResumed(@NonNull Activity activity) {

}

@Override
public void onActivityPaused(@NonNull Activity activity) {

}

@Override
public void onActivityStopped(@NonNull Activity activity) {

}

@Override
public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {

}

}
复制代码
下边就是使用了
public class App extends Application {

@Override
public void onCreate() {
    super.onCreate();
    ResEntryLifecycle resEntryLifecycle = new ResEntryLifecycle();
    registerActivityLifecycleCallbacks(resEntryLifecycle);
}


@Override
public Resources getResources() {
    Resources res = super.getResources();
    return new WxjResources(res);
}

}

复制代码
到这里所有的逻辑代码已经完成了,其实上边遗留了一个问题,我们项目是在jenkins上打包,然后当打包失败的时候,很有可能strings.xml里面已经存在加密后的node了,如果此时再次打包,那么就会有两个相同的node,这个问题其实也可以通过加密之前先把strings.xml里面的节点都干掉,然后再添加,虽然暴力,但是有效,还有很多不完善的地方,敬请谅解,只是提供一个思路!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值