Wi-Fi和许多其他缩写。 如何在Android应用程序中的Wi-Fi节点上获取数据而不会膨胀

一旦我需要从Android扫描Wi-Fi网络应用程序并获得有关接入点数据的详细计算。

在这里,我不得不面对几个困难:在Android非文档版本中,许多描述的类已被弃用(API级别> 26),但没有反映在其中; 文档中对某些内容的描述很少(例如,尽管其中包含许多重要数据,但以任何方式都没有描述ScanResult类的功能字段)。 第三个困难可能是因为与Wi-Fi的初次接触不同于阅读理论并为本地主机设置路由器,因此您必须处理许多似乎可以单独理解的缩写。 但是如何关联和构建它们可能并不明显(判断是主观的,取决于先前的经验)。

本文讨论了如何在没有NDK和黑客的情况下从Android代码获取Wi-Fi环境中的全面数据,而仅使用Android API并了解如何解释它们。

我们不会拉并开始编写代码。

1.创建一个项目


该注释是为那些多次创建Android项目的人设计的,因此我们省略了此项目的详细信息。 以下代码将以Kotlin语言minSdkVersion = 23呈现。

2.访问权限


要通过应用程序使用Wi-Fi,您将需要从用户那里获得多个权限。 根据文档 ,为了在OS版本8.0以后的设备上扫描网络,除了可以查看网络环境的状态之外,您还需要访问以更改设备的Wi-Fi模块的状态或访问坐标(近似或精确)。 从9.0版开始,您必须同时要求用户提供这两个条件,并同时明确要求用户启用位置服务。 不要忘了向用户大胆地解释这是Google的一时兴起,而不是我们监视他的欲望:)

总计,在AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 

在链接到当前活动的代码中:

 import android.app.Activity import android.content.Context import android.location.LocationManager import androidx.core.app.ActivityCompat .... if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CHANGE_WIFI_STATE), 1 ) makeEnableLocationServices(activity.applicationContext) } else { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.CHANGE_WIFI_STATE), 1 ) } /*        */ fun makeEnableLocationServices(context: Context) { // TODO:       ,      val lm: LocationManager = context.applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager val gpsEnabled: Boolean = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); val networkEnabled: Boolean = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (!gpsEnabled && !networkEnabled) { context.startActivity(Intent(ACTION_LOCATION_SOURCE_SETTINGS)); } } 

3.创建BroadcastReceiver并订阅有关扫描Wi-Fi网络环境的更新事件


 val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager val wifiScanReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false) if (success) { scanSuccess() } } } val intentFilter = IntentFilter() /*         */ intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) context.registerReceiver(wifiScanReceiver, intentFilter) val success = wifiManager.startScan() if (!success) { /* -     ,    */ } .... private fun scanSuccess() { /*  ,   */ val results: List<ScanResult> = wifiManager.scanResults } 

文档中的WiFiManager.startScan方法被标记为从API版本28起已使用,但已关闭。 指南建议使用它。

总共,我们收到了ScanResult对象的列表。

4.我们查看ScanResult并了解其术语


让我们看一下该类的一些字段并描述它们的含义:

SSID-服务集标识符是网络的名称。

BSSID-基本服务集标识符-网络适配器的MAC地址(Wi-Fi点)

级别 -接收信号强度指示器[dBm(俄罗斯dBm)-分贝,参考功率1 mW。]-接收信号的水平指示器。 它的取值范围是0到-100,距离0越远,从Wi-Fi点到设备的信号损耗就越大。 可以在Wikipedia上找到更多详细信息。 在这里,我将告诉您,使用Android类WifiManager,您可以通过选择的步骤将信号电平从极好到极差的等级进行校准:

  val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager val numberOfLevels = 5 val level = WifiManager.calculateSignalLevel(level, numberOfLevels) 

频率 -Wi-Fi点的频率[Hz]。 除了频率本身之外,您可能还对所谓的频道感兴趣。 每个点都有其自己的工作清洁度。 在撰写本文时,最受欢迎的Wi-Fi点范围是2.4 GHz。 但是,更确切地说,该点以接近命名频率的编号频率将信息传输到您的手机。 通道数和相应频率的值已标准化 。 这样做是为了使附近的点以不同的频率工作,从而不会互相干扰,也不会相互降低传输的速度和质量。 同时,这些点不在相同的频率上工作,而是在频率范围(channelWidth 参数 )(称为信道宽度)上工作。 就是说,在相邻信道上运行的点(不仅在相邻信道上,而且甚至在其自身上的3个点上)都相互干扰。 这个简单的代码对您很有用,它使您可以通过频率值为2.4和5 Ghz的点的频率值来计算通道号:

 /*      */ val channel: Int get() { return if (frequency in 2412..2484) { (frequency - 2412) / 5 + 1 } else if (frequency in 5170..5825) { (frequency - 5170) / 5 + 34 } else { -1 } } 

功能是最有趣的分析领域,需要花费大量时间。 这里,点的“功能”被写入到行中。 在这种情况下,将无法找到文档中字符串解释的详细信息。 以下是此行可能包含的一些示例:

 [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][WPS][ESS] [WPA2-PSK-CCMP][ESS] [WPA2-PSK-CCMP+TKIP][ESS] [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS] [ESS][WPS] 

5.了解缩写和parsim功能


值得一提的是android.net.wifi包的类*在后台使用wpa_supplicant linux实用程序,并且功能字段中的输出是扫描过程中flags字段的副本。

我们将按顺序采取行动。 首先,考虑以下格式的输出,其中括号内的元素用“-”分隔:

 [WPA-PSK-TKIP+CCMP] [WPA2-PSK-CCMP] 

第一个值描述了所谓的 认证方式 也就是说,设备和接入点必须执行什么动作序列,以便接入点允许其自身被使用以及如何加密有效负载。 在撰写本文时,最常见的选择是WPA和WPA2,其中每个直接连接的设备或通过所谓的连接的设备。 RADIUS服务器(WPA-Enterprice)通过加密通道提供密码。 接入点很可能在您的家中根据此方案提供连接。 第二个版本与第一个版本之间的区别在于,它具有更强大的密码:针对不安全TKIP的AES。 更复杂,更高级的WPA3也正在逐步引入。 从理论上讲,CCKM(思科集中式密钥管理)enterprice解决方案可能有一个选择,但我从未见过。

可以将接入点配置为通过MAC地址进行身份验证。 或者,如果访问点使用过时的WEP算法提供数据,则实际上没有身份验证(此处的秘密密钥是加密密钥)。 这些选项被分类为其他。
在公共wi-fi中,还有一种最喜欢的方法是带有隐藏的强制门户检测-通过浏览器进行身份验证的请求。 这样的访问点看起来对扫描仪是开放的(从物理连接的角度来看)。 因此,我们将它们分类为OPEN。

第二个值可以指定为密钥管理算法 。 它是上述认证方法的参数。 讨论加密密钥的交换是如何发生的。 让我们考虑可能的选项。 EAP-在上述WPA-Enterprice中使用,它使用数据库来验证输入的身份验证数据。 SAE-用于高级WPA3中,更耐破坏。 PSK是最常见的选项,它涉及输入密码并以加密形式进行传输。 IEEE8021X-根据国际标准(不受WPA系列支持)。 OWE(机会无线加密)是IEEE 802.11标准的扩展,针对我们归类为OPEN的几点。 OWE通过加密来确保通过不安全网络传输的数据的安全性。 当没有访问键时,也可以使用一种变体;我们将此选项称为NONE。

第三个参数就是所谓的 加密方法(加密方案) -用于保护传输数据的密码的精确程度。 我们列出了这些选项。 WEP-使用RC4流密码,秘密密钥是加密密钥,在现代密码学领域被认为是不可接受的。 TKIP-用于WPA,CKIP-用于WPA2。 TKIP + CKIP-可以在支持WPA和WPA2的点上指定,以实现向后兼容。

除了三个元素,您还可以找到一个单独的WEP标记:

 [WEP] 

正如我们上面所讨论的,这足以指定不使用密钥的算法(默认情况下不使用密钥)和加密方法(默认情况下为一种)。

现在考虑一下这个支架:

 [ESS] 

这是Wi-Fi操作模式Wi-Fi网络拓扑 。 您可能会遇到BSS(基本服务集)模式-当存在一个连接的设备通过其进行通信的访问点时。 可以在本地网络上找到。 通常,需要接入点才能连接来自不同本地网络的设备,因此它们是扩展服务集-ESS的一部分。 IBSS(独立基本服务集)的类型指示该设备是对等网络的一部分。

WPS标志也可能会下降:

 [WPS] 

WPS(Wi-Fi保护设置)-Wi-Fi网络的半自动初始化协议。 要初始化,用户可以输入8个字符的密码或在路由器上按一个按钮。 如果您的访问点是第一种类型,并且此标记在访问点名称的旁边突出显示,则强烈建议您转到管理面板并禁用通过WPS的访问。 事实是,通常可以通过MAC地址识别8位PIN,或者在可预见的时间内整理出8位PIN,这样一些不干净的人就可以利用。

6.创建一个解析模型和函数


基于上面的发现,我们使用数据类描述发生了什么:

 /*   */ enum class AuthMethod { WPA3, WPA2, WPA, // Wi-Fi Protected Access OTHER, //    Shared Key Authentication  .  mac-address-based  WEP CCKM, // Cisco OPEN // Open Authentication.     Captive Portal Detection -     } /*    */ enum class KeyManagementAlgorithm { IEEE8021X, //   EAP, // Extensible Authentication Protocol,    PSK, // Pre-Shared Key —         WEP, //  WEP     (No auth key) SAE, // Simultaneous Authentication of Equals -    WPA3 OWE, // Opportunistic Wireless Encryption -    ,    OPEN NONE //      OPEN, OTHER } /*   */ enum class CipherMethod { WEP, // Wired Equivalent Privacy,       TKIP, // Temporal Key Integrity Protocol CCMP, // Counter Mode with Cipher Block Chaining Message Authentication Code Protocol, //              //   AES NONE //      OPEN, OTHER } /*     ,      */ data class Capability( var authScheme: AuthMethod? = null, var keyManagementAlgorithm: KeyManagementAlgorithm? = null, var cipherMethod: CipherMethod? = null ) /*   WiFi (   WiFi) */ enum class TopologyMode { IBSS, //   (Ad-Hoc  IBSS – Independent Basic Service Set). BSS, //    Basic Service Set (BSS)  Infrastructure Mode. ESS //    ESS – Extended Service Set. } 

现在,我们将编写一个将解析功能字段的函数:

 private fun parseCapabilities(capabilitiesString: String): List < Capability > { val capabilities: List < Capability > = capabilitiesString .splitByBrackets() .filter { !it.isTopology() && !it.isWps() } .flatMap { parseCapability(it) } return if (!capabilities.isEmpty()) { capabilities } else { listOf(Capability(AuthMethod.OPEN, KeyManagementAlgorithm.NONE, CipherMethod.NONE)) } } private fun parseCapability(part: String): List < Capability > { if (part.contains("WEP")) { return listOf(Capability( AuthMethod.OTHER, KeyManagementAlgorithm.WEP, CipherMethod.WEP )) } val authScheme = when { part.contains("WPA3") - > AuthMethod.WPA3 part.contains("WPA2") - > AuthMethod.WPA2 part.contains("WPA") - > AuthMethod.WPA else - > null } val keyManagementAlgorithm = when { part.contains("OWE") - > KeyManagementAlgorithm.OWE part.contains("SAE") - > KeyManagementAlgorithm.SAE part.contains("IEEE802.1X") - > KeyManagementAlgorithm.IEEE8021X part.contains("EAP") - > KeyManagementAlgorithm.EAP part.contains("PSK") - > KeyManagementAlgorithm.PSK else - > null } val capabilities = ArrayList < Capability > () if (part.contains("TKIP") || part.contains("CCMP")) { if (part.contains("TKIP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.TKIP )) } if (part.contains("CCMP")) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.CCMP )) } } else if (authScheme != null || keyManagementAlgorithm != null) { capabilities.add(Capability( authScheme ? : AuthMethod.OPEN, keyManagementAlgorithm ? : KeyManagementAlgorithm.NONE, CipherMethod.NONE )) } return capabilities } private fun parseTopologyMode(capabilitiesString: String): TopologyMode ? { return capabilitiesString .splitByBrackets() .mapNotNull { when { it.contains("ESS") - > TopologyMode.ESS it.contains("BSS") - > TopologyMode.BSS it.contains("IBSS") - > TopologyMode.IBSS else - > null } } .firstOrNull() } private fun parseWPSAvailable(capabilitiesString: String): Boolean { return capabilitiesString .splitByBrackets() .any { it.isWps() } } private fun String.splitByBrackets(): List < String > { val m = Pattern.compile("\\[(.*?)\\]").matcher(this) val parts = ArrayList < String > () while (m.find()) { parts.add(m.group().replace("[", "").replace("]", "")) } return parts } private fun String.isTopology(): Boolean { return TopologyMode.values().any { this == it.name } } private fun String.isWps(): Boolean { return this == "WPS" } 

8.我们看结果


我将扫描网络并显示发生了什么。 显示通过Log.d进行简单输出的结果:

 Capability of Home-Home [WPA2-PSK-CCMP][ESS][WPS] ... capabilities=[Capability(authScheme=WPA2, keyManagementAlgorithm=PSK, cipherMethod=CCMP)], topologyMode=ESS, availableWps=true 

从应用程序代码连接到网络的问题仍然没有解决。 我只能说,为了读取保存的移动设备的OS密码,您需要root权限并愿意通过文件系统进行翻阅以读取wpa_supplicant.conf。 如果应用程序逻辑涉及从外部输入密码,则可以通过android.net.wifi.WifiManager类进行连接。

感谢Egor Ponomarev的宝贵补充。

如果您认为需要添加或修复某些内容,请在评论中写:)

Source: https://habr.com/ru/post/zh-CN467765/


All Articles