随着最近发生的所有数据泄露事件,隐私已成为一个重要话题。几乎每个应用程序都通过网络进行通信,因此考虑用户信息的安全性很重要。在这篇文章中,您将了解当前保护 android 应用程序通信的最佳实践。
使用https
在您开发应用程序时,最好将您的网络请求限制为必不可少的请求。对于重要的,请确保它们是通过 HTTPS 而不是 HTTP 创建的。HTTPS 是一种对流量进行加密的协议,使其不易被窃听者拦截。Android 的好处是迁移就像将 URL 从http更改为https一样简单。
URL url = new URL("https://example.com"); HttpsURLConnection httpsURLConnection = (HttpsURLConnection)url.openConnection(); httpsURLConnection.connect();
事实上,Android N 及更高版本可以使用Android 的 Network Security Configuration强制执行 HTTPS 。
在 Android Studio 中,为您的项目选择app/res/xml目录。如果xml目录不存在,请创建它。选择它并单击File > New File。称之为network_security_config.xml。该文件的格式如下:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">example.com</domain> </domain-config> </network-security-config>
要告诉 Android 使用此文件,请将文件名添加到AndroidManifest.xml文件中的应用程序标记中:
<application android:networkSecurityConfig="@xml/network_security_config"
更新加密提供者
多年来,HTTPS 协议已被多次利用。当安全研究人员报告漏洞时,通常会修补缺陷。应用补丁可确保您的应用程序的网络连接使用最新的行业标准协议。最新版本的协议包含的弱点比以前的要少。
要更新加密提供商,您需要包含 Google Play服务。在 build.gradle 的模块文件中,将以下行添加到依赖项部分:
implementation 'com.google.android.gms:play-services-safetynet:15.0.1'
SafetyNet服务api具有更多功能,包括检查 URL 以查看它们是否已被标记为已知威胁的安全浏览 API,以及保护您的应用免受垃圾邮件发送者和其他恶意流量影响的reCAPTCHA API。
同步 Gradle 后,可以调用ProviderInstaller's方法:installIfNeededasync
public class MainActivity extends Activity implements ProviderInstaller.ProviderInstallListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ProviderInstaller.installIfNeededAsync(this, this); } }
当提供程序成功更新或已经更新时,将调用该onProviderInstalled()方法。否则,onProviderInstallFailed(int errorCode, Intent recoveryIntent)被调用。
证书和公钥固定
当您与服务器建立 HTTPS 连接时,服务器会提供数字证书并由 Android 验证以确保连接安全。该证书可以使用来自中间证书颁发机构的证书进行签名。中间权威机构使用的这个证书可能又由另一个中间权威机构签署,依此类推,只要最后一个证书是由 Android OS 已经信任的根证书颁发机构签署的,就可以信任。
如果信任链中的任何证书无效,则连接不安全。虽然这是一个很好的系统,但它并非万无一失。攻击者可以指示 Android 操作系统接受自定义证书。拦截代理可以拥有受信任的证书,如果设备由公司控制,则公司可能已将设备配置为接受自己的证书。这些场景ios允许“中间人”攻击,允许 HTTPS 流量被解密和读取。
证书固定通过检查服务器证书与预期证书的副本来进行救援。当证书与预期的证书不同时,将不会建立此 PR事件的连接。
为了在 Android N 及更高版本上实现 pinning,您需要将证书的哈希(称为 pin)添加到network_security_config.xml文件中。这是一个示例实现:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">duckduckgo.com</domain> <pin-set> <pin digest="SHA-256">lFL47+i9MZkLqDTjnbPTx2GZbGmRfvF3GkEh+J+1F3g=</pin> <pin digest="SHA-256">w9MWhhnFZDSPWTFBjaoeGuClsrCs7Z70lG7YNlo8t+s=</pin> </pin-set> </domain-config> </network-security-config>
要查找特定站点的 pin,您可以转到SSL Labs,输入该站点,然后单击Submit。或者,如果您正在为一家公司开发应用程序,您可以向该公司索取。
注意:如果您需要支持运行早于 Android N 的操作系统版本的设备,您可以使用TrustKit库。它以完全相同的方式利用网络安全配置文件。
消毒和验证
到目前为止,有了所有保护措施,您的连接应该非常安全。即便如此,也不要忘记定期的编程验证。盲目相信从网络接收到的数据是不安全的。一个好的编程实践是“契约式设计”,你的方法的输入和输出满足定义特定接口期望的契约。
例如,如果您的服务器需要一个不超过 48 个字符的字符串,请确保界面只返回最多 48 个字符(包括 48 个字符)。
if (editText.getText().toString().length() <= 48) { ; //return something... } else { ; //return default or error }
如果您只期望来自服务器的数字,您的输入应该检查这一点。虽然这有助于防止无辜错误,但它也降低了注入和内存损坏攻击的可能性。当数据被传递到NDK 或 JNI (本机 C 和 C++ 代码)时尤其如此。
向服务器发送数据也是如此。不要盲目地发送数据,特别是如果它是用户生成的。例如,限制用户输入的长度是一种很好的做法,尤其是当它由 sql 服务器或任何将运行代码的技术执行时。
虽然保护服务器免受攻击超出了本文的范围,但作为移动开发人员,您可以通过删除服务器使用的语言的字符来尽自己的一份力量。这样,输入就不容易受到注入攻击。当引号、分号和斜杠对用户输入不是必需的时,一些示例是去除它们:
string = string.replace("\\", "").replace(";", "").replace("\"", "").replace("\'", "");
如果您确切知道预期的格式,您应该检查这一点。一个很好的例子是电子邮件验证:
private final String emailRegexString = "^[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,4}$"; private boolean isValidEmailString(String emailString) { return emailString != null && Pattern.compile(emailRegexString).matcher(emailString).matches(); }
也可以检查文件。如果您将照片发送到您的服务器,您可以检查它是一张有效的照片。FF D8前两个字节和后两个字节始终FF D9 为 JPEG 格式。
private static boolean isValidJPEGAtPath(String pathString) throws IOException { RandomaccessFile randoMaccessFile = null; try { randomAccessFile = new RandomAccessFile(pathString, "r"); long length = randomAccessFile.length(); if (length < 10L) { return false; } byte[] start = new byte[2]; randomAccessFile.readFully(start); randomAccessFile.seek(length - 2); byte[] end = new byte[2]; randomAccessFile.readFully(end); return start[0] == -1 && start[1] == -40 && end[0] == -1 && end[1] == -39; } finally { if (randomAccessFile != null) { randomAccessFile.close(); } } }
显示直接显示来自服务器的消息的错误警报时要小心。错误消息可能会泄露私人调试或安全相关信息。解决方案是让服务器发送一个错误代码,客户端查找该错误代码以显示预定义的消息。
与其他应用程序的通信
在保护进出设备的通信时,保护 IPC 也很重要。曾有开发者留下共享文件或实现了socket来交换敏感信息的情况。这是不安全的。最好使用Intent。您可以使用Intent通过提供包名称来发送数据,如下所示:
Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example.app","com.example.app.TheActivity")); intent.putExtra("UserInfo", "Example string"); startActivity(intent);
要将数据广播到多个应用程序,您应该强制只有使用您的签名密钥签名的应用程序才能获取数据。否则,您发送的信息可以被任何注册接收广播的应用程序读取。同样,如果您已注册接收广播,恶意应用可以向您的应用发送广播。您可以在发送和接收使用签名作为protectionLevel的广播时使用权限。您可以像这样在清单文件中定义自定义权限:
<permission android:name="com.example.mypermission" android:protectionLevel="signature"/>
然后你可以像这样授予权限:
<uses-permission android:name="com.example.mypermission"/>
两个应用程序都需要具有清单文件中的权限才能工作。要发送广播:
Intent intent = new Intent(); intent.putExtra("UserInfo", "Example string"); intent.setAction("com.example.SOME_NOTIFICATION"); sendBroadcast(intent, "com.example.mypermission");
或者,您可以在发送广播时使用,以将其设置为与指定包匹配的一组应用程序。在清单文件中设置为将排除从您的应用程序外部接收的广播。 setPackage(String)android:exportedfalse
端到端加密
了解 HTTPS 对保护网络通信的限制非常重要。在大多数 HTTPS 实现中,加密在服务器端终止。例如,您与公司服务器的连接可能是通过 HTTPS 进行的,但是一旦该流量到达服务器,它就未加密。然后可以通过建立另一个 HTTPS 会话或通过未加密的方式将其转发到其他服务器。公司能够看到已发送的信息,并且在大多数情况下,这是业务运营的要求。但是,这也意味着公司可以将信息不加密地传递给第三方。
最近有一种称为“端到端加密”的趋势,其中只有两个终端通信设备才能读取流量。一个很好的例子是一个加密的聊天应用程序,其中两个移动设备通过服务器相互通信;只有发送者和接收者可以阅读对方的消息。
帮助您理解端到端加密的一个类比是想象您希望有人向您发送一条只有您可以阅读的消息。为此,您向他们提供一个带有打开挂锁的盒子(公钥),同时保留挂锁钥匙(私钥)。用户写了一条消息,把它放在盒子里,锁上挂锁,然后把它发回给你。只有您可以阅读该消息,因为您是唯一拥有解锁挂锁钥匙的人。
通过端到端加密,两个用户相互发送他们的密钥。服务器只提供通信服务,但无法读取通信内容。虽然实现细节超出了本文的范围,但它是一项强大的技术。如果您想了解有关此方法的更多信息,一个很好的起点是开源Signal 项目的GitHub 存储库。
结论
借助GDPR等所有新的隐私法,安全性变得越来越重要。这通常是移动应用程序开发中被忽视的一个方面。