开发更安全的安卓应用要注意哪些?

本文探讨了安卓应用在处理敏感数据时可能面临的安全挑战,并提出了三种关键的安全策略:确保网络服务调用安全,保护设备上存储的数据,以及防止应用被逆向工程和篡改。

安卓应用常被用于处理非常敏感的数据。开发者有责任确保用户提供的信息不被居心不良者轻易截取。开放式 Web 应用安全项目(OWASP)[9,10]尝试着列举移动应用潜在的安全问题。


其中一些是系统架构师的责任(例如弱服务器端控制有关的问题),一些是后端开发者的责任(授权检查相关问题),最后一些就是纯粹与移动应用本身有关。本文我们将关注通过安卓开发者努力可以解决的问题。


因此我们将在这里提出三个潜在的漏洞源:使用网络服务(WS)通信时的风险、在存储设备上存储数据时潜在的信息泄漏以及第三方软件能轻易编辑应用程序的漏洞。


一、安全的网络服务调用


对于使用WS的敏感应用,最重要的就是保证用户分享在后台的数据是安全的。事实上,如果网络上的请求能被轻易截取,最安全的应用程序也是毫无用处的。


威胁:中间人攻击(MITM)


应用程序遭受中间人攻击有两个主要的风险。


1.信息泄漏


如果窃取者控制了用户使用应用的本地网络,他暗地里轻易就能截取到 app 和 WS 的所有通信。


2.网络服务(WS)模仿


对 WS 有一定认识的人可以阻塞应用调用并且提供伪造的回应,这时用户认为他们的请求已经执行,然而请求根本没有到达后台。


测试应用在中间人攻击时的脆弱性相当简单:你只需要使用一个代理软件(例如CharlesProxy[12]),然后建立设备来使用安装了这个代理的机器。如果应用不能阻止中间人攻击,你将能看到它执行的每个请求。现在,想象你的 app 用户通过“不安全”的网络连接到你的网络服务:窃取者可以毫不费力地将代理安装在网络路由器上,就能嗅到所有不加密的请求。


攻击源:TLS/SSL证书链


保证通信安全至少要使用 HTTPS 协议,也就是说使用安全传输层协议(TLS)或是它的前身安全套接层协议(SSL)加密的通信。



安卓 SSL 本地保护:


安卓网络层有一个内置的CA 证书列表(超过一百个,你可以在设备参数中检查这个列表)。每个HTTPS网络调用需要在证书链上有一个CA证书。


然而,没有办法确保其余的链适合我们想要连接的服务器。例如一个窃取者可以向CA购买中间证书实施中间人攻击。所有的网络事务都将被系统视作无效。这种漏洞非常常见:一个研究表明[1]73%使用HTTPS 协议的应用并没有用正确的方式检查证书。


怎样保证连接的是我们的后台并且这个连接是安全?


解决上述问题的方式是手动检查中间证书(适用于特定服务器)是已知证书。这意味着我们需要将特定的服务器证书存储在应用中,可以将它作为常数存储在资源文件或直接放在源代码中来实现。


我们可能会疑惑为什么需要检查中间证书而不是终端用户。这里有两个理由,第一,终端用户证书生存期很短。第二点是安全理由:想象一下,一个黑客完全控制了系统,那他将拥有你的私钥(服务器需要私钥签名请求)。应用会认为请求是由正确的终端用户私钥签名,并且会允许连接。如果认证在中间服务器检查时实现,那就有可能通过联系中间CA远程废除证书。


SSLSocketFactory 类可以验证一个 SSL 连接是否安全。创建一个执行中间证书检查的 SSLSocketFactory,我们需要执行以下步骤。


1.创建一个继承于 X509TrustManger 的类。这是一个 java.net.ssl 包中的抽象类,用于检查服务器端 SSL socket 的合法性。


public class MyX509TrustManager implements X509TrustManager {

private X509Certificate certificate;

 

public MyX509TrustManager (InputStream knownIntermediateCertificate) throws CertificateException {

CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

certificate = certFactory.generateCertificate(knownIntermediateCertificate);

}

 

@Override

public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// Do nothing. We only want to check server side certificate.

}

 

@Override

public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// Verify that the certificate domain name matches the expected one

if (!chain[0].getIssuerDN().equals(certificate.getSubjectDN())) {

throw new CertificateException("Parent certificate of server was different than expected signing certificate");

}

 

try {

// Verifiy that the certificate key matches the expected one

chain[0].verify(certificate.getPublicKey());

 

// Verify that the certificate has not expired

chain[0].checkValidity();

} catch (Exception e) {

throw new CertificateException("Parent certificate of server was different than expected signing certificate");

}

}

 

@Override

public X509Certificate[] getAcceptedIssuers() {

// Do nothing

return new X509Certificate[0];

}

}



2.设置一个新的默认 SSLSocketFactory。代码需要在网络调用前运行。


TrustManager[] trustManagerArray = new TrustManager[1];

MyTrustManager trustManager = new MyTrustManager(TRUSTED_CERTIFICATE);

trustManagerArray[0] = trustManager;

 

final SSLContext sslc;

 

// TLS is the last SSL protocol and is used by all the CA

sslc = SSLContext.getInstance("TLS");

 

// We only need to give a TrustManager list as we don't need to perform client authentification

sslc.init(null, trustManagers, null);

HttpsURLConnection.setDefaultSSLSocketFactory(sslc.getSocketFactory());


证书检查潜在缺陷


1.中间证书会过期(其生存期大概是10年)。在证书过期前将新的证书添加到白名单可以有效检测证书的变化。


2.中间证书认证机构会失效。如果中间证书认证机构失效,它所提供的安全机制将会完全无效。事实上,如果黑客保留了中间 CA ,他将能够伪造证书链,拥有和你的证书链相同的中间证书。此时他就能实施中间人攻击。尽管CA理论上是安全的,依然可能发生像2011年Dignotar证书失效的事件。一旦发生这种情况,唯一能做的事情就是改变服务器上的SSL证书链,并且发布一个内嵌新的中间证书的版本。


3.SSLSocketFactory 认为它的策略适用于应用中的所有网络调用。如果sdk是内置的,那就有必要为远程服务器的这些sdk嵌入中间证书。存在的不确定性是无法轻易察觉服务器证书的变化。


动态植入证书会遇到这种问题。应用只允许一个证书(主服务器一个)并且在应用运行时获取一个授权中间证书的动态列表,然后将这些证书添加到 SSLContext 的 trust manager中。


总而言之,在大多数情况下,中间检查机制能够防御中间人攻击。黑客窃听通信时,他必须将自己的证书列入证书链, TrustManager不能识别这个证书,于是拒绝 HTTPS 连接。


二、设备上的存储安全



2.使用提供 SharedPreferences 包装类的库。这些密码库使用很方便,开发者不需要关心使用什么算法。然而,使用这些库会失去灵活性并且有些并不使用安全算法。因此,它们并不适合存储敏感的数据。SecurePreference[4] 是其中一个最常用的提供这种包装特性的库。如果你选择这种方式,你可以用非常直接的方式实例化一个继承自SharedPreferences 的 SecurePreferences 类:


SecurePreferences securePreferences = new SecurePreferences(context, "MyPassword", null);


这两种方法是基于例如AES(有一个合适的key 大小)的对称密码学。带来的疑问是:我们应该使用哪种key?事实上,如果我们使用一个静态key,可以通过反编译应用解密参数。所以,最好的解决方式是在应用启动时使用用户输入的pin码/通行码。或者使用 Fingerprint API [15](需要API 23以上版本),可以提供一种安全流畅的认证方式。


不幸的是,这种方法不能满足每个应用的用户体验。例如如果我们想要在pin码输入前显示存储的信息,那我们就不能使用安全加密系统。


幸好,安卓提供了一种安全的方式来为一些应用程序/设备生成特定的key: KeyStore。安卓 KeyStore 是为了允许应用将私钥放在不能被其他应用的地方,或者是不能通过实质性访问存储在设备上的数据获取私钥的地方。


机制非常简单:首先,运行应用检查应用相关的私钥是否存在。如果不存在,就生成一个并存储在KeyStore 中。如果私钥存在,它可以成为安全密钥来解析SharedPreferences 的数据,这多亏了上文描述的算法。


Obaro Ogbo写了一篇详细的文章深刻描绘了如何使用 KeyStore 生成私钥/公钥对。KeyStore 主要的缺点是只允许API18以上版本使用。但是有一个兼容API14以上版本的补丁库(这不是“官方”的布丁,所以你必须自己承担后果)。


因此,我们建议当需要决定优先使用哪种系统时可以参考下面的策略图:



三、防止应用被源代码分析和修改


有时,安卓开发者不希望应用被其他人分析,解读,最后被修改。这种要求有各种理由:


  • 我们不希望黑客移除用于阻止非付费用户使用某些功能的应用锁。

  • 我们开发的敏感应用时的风险是,黑客会将应用修改成所有输入信息都会返回给他。尽管应用商店不容易发生这种情况,但是用户可以去许多其他地方下载到伪造的应用,这些应用会用完全透明的方式偷取他所有的数据。


每个安卓开发者都应该注意,当开发敏感应用时,经验丰富的人反编译一个安卓应用是相当简单的。特别是使用“本地”应用配置的情况。事实上,因为大多数安卓app的特性(使用 Java 字节码),反编译字节码后解读,改正,最后重建一个修改过的应用非常简单。


这部分我们会强调一些可以规避风险的技术工具和构建原则。同时,我们需要注意,因为应用程序运行在客户端设备上,我们并没有百分之百确信的方法来规避这些风险。


1.有价值的算法写在服务器端


下面是构建指南。如果你的应用程序价值是以算法为基础的,你当然不希望有人能轻易解读、复制并且将你的算法嵌入自己的应用程序中。此时,最好的解决方式是在服务器端实现算法。应用程序只需提供待处理的数据给服务器,然后获取算法的返回值。这种结构的明显缺点是,在离线状态下无法使用app 的核心功能。


2.防止WS完全开放


如果应用程序的功能是借助 WS 获取数据,你可以发送在认证阶段获得的session token 或者在每个请求中授予user / password 来保证WS 安全。如果仅仅是在app 参数中使用认证标志,将这个标志设置成“always connected”就可以轻松修改应用程序代码。这么做的风险是用户需要定期输入使用过的id和密码来延长会话。


3.使用Proguard混淆代码



网上有许多详细解释如何配置Proguard 的指导,比如在安卓系统文档中的配置[7]。


4.使用编译库



使用 NDK 依然有许多缺点:我们必须为应用程序针对的不同类型的硬件结构编译本地库,这样就放弃了产生 crash 时得到适当stacktrace 的可能性,同时它也大大增加了代码结构。


总结


在本文,我们建议的解决方法覆盖了 3 个 OWASP 中排名前十的手机安全问题[9]。像我们介绍中提到的,只有当系统结构是安全的,应用程序才是安全的。一个人可以开发技术上安全的应用,但如果服务器没有授权良好的认证系统,所有的努力都是无意义的。同时,确保完美的安全边界是手机开发者的责任,这篇文章给出了覆盖安全边界的解决方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值