Most app developers will integrate some third party libraries into their apps. If it's to access a service, such as Dropbox or YouTube, or for logging crashes. The number of third party libraries and services is staggering. Most of those libraries and services are integrated by somehow authenticating with the service, most of the time, this happens through an API key. For security purposes, services usually generate a public and private, often also referred to as secret, key. Unfortunately, in order to connect to the services, this private key must be used to authenticate and hence, probably be part of the application.
Needless to say, that this faces in immense security problem. Public and private API keys can be extracted from APKs in a matter of minutes and can easily be automated.
假设我有类似的东西,我如何保护密钥:
public class DropboxService {
private final static String APP_KEY = "jk433g34hg3";
private final static String APP_SECRET = "987dwdqwdqw90";
private final static AccessType ACCESS_TYPE = AccessType.DROPBOX;
// SOME MORE CODE HERE
}
你认为储存私钥的最佳及最安全的方法是什么?混淆,加密,你怎么看?
这个例子有许多不同的方面。我将提到一些我认为在其他地方没有明确提到的要点。
保护运输中的秘密
首先要注意的是,使用他们的应用程序认证机制访问dropbox API需要你传输你的密钥和秘密。连接是HTTPS,这意味着您无法在不知道TLS证书的情况下拦截流量。这是为了防止一个人在从移动设备到服务器的旅途中拦截和读取数据包。对于普通用户来说,这是一个很好的方式来确保他们的流量隐私。
它所不擅长的是阻止恶意的人下载应用程序并检查流量。对于进出移动设备的所有流量,使用中间人代理非常容易。在这种情况下,由于Dropbox API的性质,它不需要拆卸或反向工程代码来提取应用程序密钥和秘密。
您可以进行固定检查,以确定从服务器接收的TLS证书是否是您所期望的证书。这向客户端增加了一个检查,使拦截流量变得更加困难。这将使检查飞行中的流量变得更加困难,但是固定检查发生在客户端,因此仍然可能禁用固定测试。但这确实让它更难。
保守秘密
作为第一步,使用像保护物这样的东西将有助于使秘密隐藏的位置不那么明显。您还可以使用NDK存储密钥和秘密并直接发送请求,这将大大减少具有提取信息的适当技能的人员数量。进一步的混淆可以通过不将值直接存储在内存中任何时间长度来实现,您可以在使用之前对它们进行加密和解密,正如另一个答案所建议的那样。
更高级的选项
如果你现在偏执于在应用程序的任何地方放置秘密,并且你有时间和金钱来投资更全面的解决方案,那么你可以考虑将凭据存储在你的服务器上(假设你有)。这将增加对API的任何调用的延迟,因为它必须通过服务器进行通信,并且由于数据吞吐量的增加,可能会增加运行服务的成本。
You then have to decide how best to communicate with your servers to ensure they are protected. This is important to prevent all of the same problems coming up again with your internal API. The best rule of thumb I can give is to not transmit any secret directly because of the man-in-the-middle threat. Instead you can sign the traffic using your secret and verify the integrity of any requests that come to your server. One standard way of doing this is to compute an HMAC of the message keyed on a secret. I work at a company that has a security product that also operates in this field which is why this sort of stuff interests me. In fact, here is a blog article from one of my colleagues that goes over most of this.
我应该做多少?
有了这样的安全建议,你需要对你想让别人入侵的难度做出成本/效益决定。如果你是一家保护数百万客户的银行,你的预算与那些在业余时间支持应用程序的人完全不同。要防止别人破坏你的安全系统几乎是不可能的,但在实践中,很少有人需要所有的铃铛和哨子,只要有一些基本的预防措施,你就可以走得很远。
很少有想法,在我看来,只有第一个想法给了一些保证:
Keep your secrets on some server on internet, and when needed just grab them and use. If user is about to use dropbox then nothing stops you from making request to your site and get your secret key.
Put your secrets in jni code, add some variable code to make your libraries bigger and more difficult to decompile. You might also split key string in few parts and keep them in various places.
use obfuscator, also put in code hashed secret and later on unhash it when needed to use.
Put your secret key as last pixels of one of your image in assets. Then when needed read it in your code. Obfuscating your code should help hide code that will read it.
如果你想快速看看阅读你的apk代码是多么容易,然后获取APKAnalyser:
http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/
一个可能的解决方案是在应用程序中编码数据,并在运行时(当你想使用该数据时)使用解码。我还建议使用progaard来增加应用程序反编译源代码的阅读和理解难度。例如,我把一个编码的密钥在应用程序,然后使用解码方法在我的应用程序解码我的秘密密钥在运行时:
// "the real string is: "mypassword" ";
//encoded 2 times with an algorithm or you can encode with other algorithms too
public String getClientSecret() {
return Utils.decode(Utils
.decode("Ylhsd1lYTnpkMjl5WkE9PQ=="));
}
一个受保护的应用程序的反编译源代码如下:
public String c()
{
return com.myrpoject.mypackage.g.h.a(com.myrpoject.mypackage.g.h.a("Ylhsd1lYTnpkMjl5WkE9PQ=="));
}
至少对我来说够复杂了。当我别无选择只能在我的应用程序中存储一个值时,我就是这样做的。当然我们都知道这不是最好的方法,但对我来说很管用。
/**
* @param input
* @return decoded string
*/
public static String decode(String input) {
// Receiving side
String text = "";
try {
byte[] data = Decoder.decode(input);
text = new String(data, "UTF-8");
return text;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "Error";
}
反编译版本:
public static String a(String paramString)
{
try
{
str = new String(a.a(paramString), "UTF-8");
return str;
}
catch (UnsupportedEncodingException localUnsupportedEncodingException)
{
while (true)
{
localUnsupportedEncodingException.printStackTrace();
String str = "Error";
}
}
}
你可以在谷歌中找到这么多的加密类。
As it is, your compiled application contains the key strings, but also the constant names APP_KEY and APP_SECRET. Extracting keys from such self-documenting code is trivial, for instance with the standard Android tool dx.
You can apply ProGuard. It will leave the key strings untouched, but it will remove the constant names. It will also rename classes and methods with short, meaningless names, where ever possible. Extracting the keys then takes some more time, for figuring out which string serves which purpose.
Note that setting up ProGuard shouldn't be as difficult as you fear. To begin with, you only need to enable ProGuard, as documented in project.properties. If there are any problems with third-party libraries, you may need to suppress some warnings and/or prevent them from being obfuscated, in proguard-project.txt. For instance:
-dontwarn com.dropbox.**
-keep class com.dropbox.** { *; }
This is a brute-force approach; you can refine such configuration once the processed application works.
You can obfuscate the strings manually in your code, for instance with a Base64 encoding or preferably with something more complicated; maybe even native code. A hacker will then have to statically reverse-engineer your encoding or dynamically intercept the decoding in the proper place.
You can apply a commercial obfuscator, like ProGuard's specialized sibling DexGuard. It can additionally encrypt/obfuscate the strings and classes for you. Extracting the keys then takes even more time and expertise.
You might be able to run parts of your application on your own server. If you can keep the keys there, they are safe.
In the end, it's an economic trade-off that you have to make: how important are the keys, how much time or software can you afford, how sophisticated are the hackers who are interested in the keys, how much time will they want to spend, how much worth is a delay before the keys are hacked, on what scale will any successful hackers distribute the keys, etc. Small pieces of information like keys are more difficult to protect than entire applications. Intrinsically, nothing on the client-side is unbreakable, but you can certainly raise the bar.
(我是ProGuard和DexGuard的开发者)
旧的不安全的方式:
遵循3个简单的步骤来保护API/密钥(旧答案)
我们可以使用Gradle来保护API密钥或秘密密钥。
1. gradle。properties(项目属性):创建带有key的变量。
GoogleAPIKey = "Your API/Secret Key"
2. 构建。gradle (Module: app):在build中设置变量。Gradle在活动或片段中访问它。将以下代码添加到buildTypes{}。
buildTypes.each {
it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey
}
3.通过应用程序的BuildConfig在Activity/Fragment中访问它:
BuildConfig.GoogleSecAPIKEY
更新:
上述解决方案对通过Git提交的开源项目很有帮助。(感谢David Rawson和riyazi -ali的评论)。
根据Matthew和Pablo Cegarra的评论,上述方式是不安全的,反编译器将允许某人使用我们的密钥查看BuildConfig。
解决方案:
我们可以使用NDK来保护API密钥。我们可以在原生C/ c++类中存储键,并在Java类中访问它们。
请跟随这个博客使用NDK保护API密钥。
关于如何在Android中安全地存储令牌的后续内容