安卓應用安全指南5.4.1通過HTTPS的通訊示例程式碼
5.4.1 通過 HTTPS 的通訊 示例程式碼
原書:Android Application Secure Design/Secure Coding Guidebook
譯者:飛龍
你可以通過下面的圖表(圖 5.4-1)找出你應該實現的 HTTP / HTTPS 通訊型別。
當傳送或接收敏感資訊時,將使用 HTTPS 通訊,因為其通訊通道使用 SSL / TLS 加密。 以下敏感資訊需要 HTTPS 通訊。
- Web 服務的登入 ID /密碼。
- 保持認證狀態的資訊(會話 ID,令牌,Cookie 等)
- 取決於 Web 服務的重要/機密資訊(個人資訊,信用卡資訊等)
具有網路通訊的智慧手機應用是“系統”和 Web 伺服器的一部分。 而且你必須根據整個“系統”的安全設計和編碼,為每個通訊選擇 HTTP 或 HTTPS。 表 5.4-1 用於比較 HTTP 和 HTTPS。 表 5.4-2 是示例程式碼的差異。
表 5.4-1 HTTP 與 HTTPS 通訊方式的比較
HTTP | HTTPS |
---|---|
特性 | URL |
加密內容 | |
內容的篡改檢測 | |
對伺服器進行認證 | |
損害的風險 | 由攻擊者讀取內容 |
由攻擊者修改內容 | |
應用訪問了偽造的伺服器 |
表 5.4-2 HTTP/HTTPS 通訊示例程式碼的解釋
示例程式碼 | 通訊 | 收發敏感資訊 | 伺服器證照 |
---|---|---|---|
通過 HTTP 的通訊 | HTTP | 不適用 | – |
通過 HTTPS 的通訊 | HTTPS | OK | 伺服器證照由可信第三方機構簽署,例如 Cybertrust 和 VeriSign |
通過 HTTPS 使用私有證照的通訊 | HTTTPS | OK | 私有證照(經常能在內部伺服器或測試伺服器上看到的操作) |
Android 支援java.net.HttpURLConnection
/ javax.net.ssl.HttpsURLConnection
作為 HTTP / HTTPS 通訊 API。 在 Android 6.0(API Level 23)版本中,另一個 HTTP 客戶端庫 Apache HttpClient 的支援已被刪除。
5.4.1.1 通過 HTTP 進行通訊
它基於兩個前提,即通過 HTTP 通訊傳送/接收的所有內容都可能被攻擊者嗅探和篡改,並且你的目標伺服器可能被攻擊者準備的假伺服器替換。 只有在沒有造成損害或損害在允許範圍內的情況下,才能使用 HTTP 通訊,即使在本地也是如此。 如果應用無法接受該前提,請參閱“5.4.1.2 通過 HTTPS 進行通訊”和“5.4.1.3 通過 HTTPS 使用私有證照進行通訊”。
以下示例程式碼顯示了一個應用,它在 Web 伺服器上執行影像搜尋,獲取結果影像並顯示它。與伺服器的 HTTP 通訊在搜尋時執行兩次。第一次通訊是搜尋影像資料,第二次是獲取它。它使用AsyncTask
建立用於通訊過程的工作執行緒,來避免在 UI 執行緒上執行通訊。與伺服器的通訊中傳送/接收的內容,在這裡不被認為是敏感的(例如,用於搜尋的字串,影像的 URL 或影像資料)。因此,接收到的資料,如影像的 URL 和影像資料,可能由攻擊者提供。為了簡單地顯示示例程式碼,在示例程式碼中沒有采取任何對策,通過將接收到的攻擊資料視為可容忍的。此外,在 JSON 解析或顯示影像資料期間,可能出現異常的處理將被忽略。根據應用規範,有必要正確處理例外情況。
要點:
- 傳送的資料中不得包含敏感資訊。
- 假設收到的資料可能來自攻擊者。
HttpImageSearch.java
package org.jssec.android.https.imagesearch;
import android.os.AsyncTask;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
public abstract class HttpImageSearch extends AsyncTask<String, Void, Object> {
@Override
protected Object doInBackground(String... params) {
byte[] responseArray;
// --------------------------------------------------------
// Communication 1st time: Execute image search
// --------------------------------------------------------
// *** POINT 1 *** Sensitive information must not be contained in send data.
// Send image search character string
StringBuilder s = new StringBuilder();
for (String param : params){
s.append(param);
s.append(`+`);
}
s.deleteCharAt(s.length() - 1);
String search_url = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=" +
s.toString();
responseArray = getByteArray(search_url);
if (responseArray == null) {
return null;
}
// *** POINT 2 *** Suppose that received data may be sent from attackers.
// This is sample, so omit the process in case of the searching result is the data from an attacker.
// This is sample, so omit the exception process in case of JSON purse.
String image_url;
try {
String json = new String(responseArray);
image_url = new JSONObject(json).getJSONObject("responseData")
.getJSONArray("results").getJSONObject(0).getString("url");
} catch(JSONException e) {
return e;
}
// --------------------------------------------------------
// Communication 2nd time: Get images
// --------------------------------------------------------
// *** POINT 1 *** Sensitive information must not be contained in send data.
if (image_url != null ) {
responseArray = getByteArray(image_url);
if (responseArray == null) {
return null;
}
}
// *** POINT 2 *** Suppose that received data may be sent from attackers.
return responseArray;
}
private byte[] getByteArray(String strUrl) {
byte[] buff = new byte[1024];
byte[] result = null;
HttpURLConnection response;
BufferedInputStream inputStream = null;
ByteArrayOutputStream responseArray = null;
int length;
try {
URL url = new URL(strUrl);
response = (HttpURLConnection) url.openConnection();
response.setRequestMethod("GET");
response.connect();
checkResponse(response);
inputStream = new BufferedInputStream(response.getInputStream());
responseArray = new ByteArrayOutputStream();
while ((length = inputStream.read(buff)) != -1) {
if (length > 0) {
responseArray.write(buff, 0, length);
}
}
result = responseArray.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// This is sample, so omit the exception process
}
}
if (responseArray != null) {
try {
responseArray.close();
} catch (IOException e) {
// This is sample, so omit the exception process
}
}
}
return result;
}
private void checkResponse(HttpURLConnection response) throws IOException {
int statusCode = response.getResponseCode();
if (HttpURLConnection.HTTP_OK != statusCode) {
throw new IOException("HttpStatus: " + statusCode);
}
}
}
ImageSearchActivity.java
package org.jssec.android.https.imagesearch;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
public class ImageSearchActivity extends Activity {
private EditText mQueryBox;
private TextView mMsgBox;
private ImageView mImgBox;
private AsyncTask<String, Void, Object> mAsyncTask ;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mQueryBox = (EditText)findViewById(R.id.querybox);
mMsgBox = (TextView)findViewById(R.id.msgbox);
mImgBox = (ImageView)findViewById(R.id.imageview);
}
@Override
protected void onPause() {
// After this, Activity may be deleted, so cancel the asynchronization process in advance.
if (mAsyncTask != null) mAsyncTask.cancel(true);
super.onPause();
}
public void onHttpSearchClick(View view) {
String query = mQueryBox.getText().toString();
mMsgBox.setText("HTTP:" + query);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might not have been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate by UI thread, communicate by worker thread by AsynchTask.
mAsyncTask = new HttpImageSearch() {
@Override
protected void onPostExecute(Object result) {
// Process the communication result by UI thread.
if (result == null) {
mMsgBox.append("¥nException occurs¥n");
} else if (result instanceof Exception) {
Exception e = (Exception)result;
mMsgBox.append("¥nException occurs¥n" + e.toString());
} else {
// Exception process when image display is omitted here, since it`s sample.
byte[] data = (byte[])result;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
mImgBox.setImageBitmap(bmp);
}
}
}.execute(query);
// pass search character string and start asynchronous process
}
public void onHttpsSearchClick(View view) {
String query = mQueryBox.getText().toString();
mMsgBox.setText("HTTPS:" + query);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might not have been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate by UI thread, communicate by worker thread by AsynchTask.
mAsyncTask = new HttpsImageSearch() {
@Override
protected void onPostExecute(Object result) {
// Process the communication result by UI thread.
if (result instanceof Exception) {
Exception e = (Exception)result;
mMsgBox.append("¥nException occurs¥n" + e.toString());
} else {
byte[] data = (byte[])result;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
mImgBox.setImageBitmap(bmp);
}
}
}.execute(query);
// pass search character string and start asynchronous process
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.https.imagesearch"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@drawable/ic_launcher"
android:allowBackup="false"
android:label="@string/app_name" >
<activity
android:name=".ImageSearchActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Light"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
5.4.1.2 使用 HTTPS 進行通訊
在 HTTPS 通訊中,檢查伺服器是否可信,以及傳輸的資料是否加密。 為了驗證伺服器,Android HTTPS 庫驗證“伺服器證照”,它在 HTTPS 事務的握手階段從伺服器傳輸,其要點如下:
- 伺服器證照由可信的第三方證照機構簽署
- 伺服器證照的期限和其他屬性有效
- 伺服器的主機名匹配伺服器證照的主題欄位中的 CN(通用名稱)或 SAN(主題備用名稱)
如果上述驗證失敗,則會引發SSLException
(伺服器證照驗證異常)。 這可能意味著中間人攻擊或伺服器證照缺陷。 你的應用必須根據應用規範,以適當的順序處理異常。
下一個示例程式碼用於 HTTPS 通訊,它使用可信的第三方證照機構頒發的伺服器證照連線到 Web 伺服器。 對於使用私有伺服器證照的 HTTPS 通訊,請參閱“5.4.1.3 通過 HTTPS 使用私有證照進行通訊”。
以下示例程式碼展示了一個應用,它在 Web 伺服器上執行影像搜尋,獲取結果影像並顯示它。 與伺服器的 HTTPS 通訊在搜尋時執行兩次。 第一次通訊是搜尋影像資料,第二次是獲取它。 它使用AsyncTask
建立用於通訊過程的工作執行緒,來避免在 UI 執行緒上執行通訊。 與伺服器的通訊中傳送/接收的所有內容,在這裡被認為是敏感的(例如,用於搜尋的字串,影像的 URL 或影像資料)。 為了簡單地顯示示例程式碼,不會執行鍼對SSLException
的特殊處理。 根據應用規範,有必要正確處理異常。 另外,下面的示例程式碼允許使用 SSLv3 進行通訊。 通常,我們建議配置遠端伺服器上的設定來禁用 SSLv3,以避免針對 SSLv3 中的漏洞(稱為 POODLE)的攻擊。
要點:
- URI 以
https://
開頭。 - 傳送資料中可能包含敏感資訊。
- 儘管資料是從通過 HTTPS 連線的伺服器傳送的,但要小心並安全地處理收到的資料。
-
SSLException
應該在應用中以適當的順序處理。
HttpsImageSearch.java
package org.jssec.android.https.imagesearch;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.AsyncTask;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
public abstract class HttpsImageSearch extends AsyncTask<String, Void, Object> {
@Override
protected Object doInBackground(String... params) {
byte[] responseArray;
// --------------------------------------------------------
// Communication 1st time : Execute image search
// --------------------------------------------------------
// *** POINT 1 *** URI starts with https://.
// *** POINT 2 *** Sensitive information may be contained in send data.
StringBuilder s = new StringBuilder();
for (String param : params){
s.append(param);
s.append(`+`);
}
s.deleteCharAt(s.length() - 1);
String search_url = "https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=" +
s.toString();
responseArray = getByteArray(search_url);
if (responseArray == null) {
return null;
}
// *** POINT 3 *** Handle the received data carefully and securely,
// even though the data was sent from the server connected by HTTPS.
// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
String image_url;
try {
String json = new String(responseArray);
image_url = new JSONObject(json).getJSONObject("responseData")
.getJSONArray("results").getJSONObject(0).getString("url");
} catch(JSONException e) {
return e;
}
// --------------------------------------------------------
// Communication 2nd time : Get image
// --------------------------------------------------------
// *** POINT 1 *** URI starts with https://.
// *** POINT 2 *** Sensitive information may be contained in send data.
if (image_url != null ) {
responseArray = getByteArray(image_url);
if (responseArray == null) {
return null;
}
}
return responseArray;
}
private byte[] getByteArray(String strUrl) {
byte[] buff = new byte[1024];
byte[] result = null;
HttpURLConnection response;
BufferedInputStream inputStream = null;
ByteArrayOutputStream responseArray = null;
int length;
try {
URL url = new URL(strUrl);
response = (HttpURLConnection) url.openConnection();
response.setRequestMethod("GET");
response.connect();
checkResponse(response);
inputStream = new BufferedInputStream(response.getInputStream());
responseArray = new ByteArrayOutputStream();
while ((length = inputStream.read(buff)) != -1) {
if (length > 0) {
responseArray.write(buff, 0, length);
}
}
result = responseArray.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// This is sample, so omit the exception process
}
}
if (responseArray != null) {
try {
responseArray.close();
} catch (IOException e) {
// This is sample, so omit the exception process
}
}
}
return result;
}
private void checkResponse(HttpURLConnection response) throws IOException {
int statusCode = response.getResponseCode();
if (HttpURLConnection.HTTP_OK != statusCode) {
throw new IOException("HttpStatus: " + statusCode);
}
}
}
其他示例程式碼檔案與“5.4.1.1 通過 HTTP 進行通訊”相同,因此請參閱“5.4.1.1 通過 HTTP 進行通訊”。
5.4.1.3 使用私有證照通過 HTTPS 進行通訊
這部分展示了一個 HTTPS 通訊的示例程式碼,其中包含私人頒發的伺服器證照(私有證照),但不是可信的第三方機構頒發的伺服器證照。 請參閱“5.4.3.1 如何建立私有證照並配置伺服器”,來建立私有證照機構和私有證照的根證照,並在 Web 伺服器中設定 HTTPS。 示例程式的資產中包含cacert.crt
檔案。 它是私有證照機構的根證照檔案。
以下示例程式碼展示了一個應用,在 Web 伺服器上獲取影像並顯示該影像。 HTTPS 用於與伺服器的通訊。 它使用AsyncTask
建立用於通訊過程的工作執行緒,來避免在 UI 執行緒上執行通訊。 與伺服器的通訊中傳送/接收的所有內容(影像的 URL 和影像資料)都被認為是敏感的。 為了簡單地顯示示例程式碼,不會執行鍼對SSLException
的特殊處理。 根據應用規範,有必要正確處理異常。
要點:
- 使用私人證照機構的根證照來驗證伺服器證照。
- URI 以
https://
開頭。 - 傳送資料中可能包含敏感資訊。
- 接收的資料可以像伺服器一樣被信任。
-
SSLException
應該在應用中以適當的順序處理。
PrivateCertificathettpsGet.java
package org.jssec.android.https.privatecertificate;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import android.content.Context;
import android.os.AsyncTask;
public abstract class PrivateCertificateHttpsGet extends AsyncTask<String, Void, Object> {
private Context mContext;
public PrivateCertificateHttpsGet(Context context) {
mContext = context;
}
@Override
protected Object doInBackground(String... params) {
TrustManagerFactory trustManager;
BufferedInputStream inputStream = null;
ByteArrayOutputStream responseArray = null;
byte[] buff = new byte[1024];
int length;
try {
URL url = new URL(params[0]);
// *** POINT 1 *** Verify a server certificate with the root certificate of a private certificate authority.
// Set keystore which includes only private certificate that is stored in assets, to client.
KeyStore ks = KeyStoreUtil.getEmptyKeyStore();
KeyStoreUtil.loadX509Certificate(ks,
mContext.getResources().getAssets().open("cacert.crt"));
// *** POINT 2 *** URI starts with https://.
// *** POINT 3 *** Sensitive information may be contained in send data.
trustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManager.init(ks);
SSLContext sslCon = SSLContext.getInstance("TLS");
sslCon.init(null, trustManager.getTrustManagers(), new SecureRandom());
HttpURLConnection con = (HttpURLConnection)url.openConnection();
HttpsURLConnection response = (HttpsURLConnection)con;
response.setDefaultSSLSocketFactory(sslCon.getSocketFactory());
response.setSSLSocketFactory(sslCon.getSocketFactory());
checkResponse(response);
// *** POINT 4 *** Received data can be trusted as same as the server.
inputStream = new BufferedInputStream(response.getInputStream());
responseArray = new ByteArrayOutputStream();
while ((length = inputStream.read(buff)) != -1) {
if (length > 0) {
responseArray.write(buff, 0, length);
}
}
return responseArray.toByteArray();
} catch(SSLException e) {
// *** POINT 5 *** SSLException should be handled with an appropriate sequence in an application.
// Exception process is omitted here since it`s sample.
return e;
} catch(Exception e) {
return e;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
// This is sample, so omit the exception process
}
}
if (responseArray != null) {
try {
responseArray.close();
} catch (Exception e) {
// This is sample, so omit the exception process
}
}
}
}
private void checkResponse(HttpURLConnection response) throws IOException {
int statusCode = response.getResponseCode();
if (HttpURLConnection.HTTP_OK != statusCode) {
throw new IOException("HttpStatus: " + statusCode);
}
}
}
KeyStoreUtil.java
package org.jssec.android.https.privatecertificate;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
public class KeyStoreUtil {
public static KeyStore getEmptyKeyStore() throws KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException {
KeyStore ks = KeyStore.getInstance("BKS");
ks.load(null);
return ks;
}
public static void loadAndroidCAStore(KeyStore ks)
throws KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException {
KeyStore aks = KeyStore.getInstance("AndroidCAStore");
aks.load(null);
Enumeration<String> aliases = aks.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Certificate cert = aks.getCertificate(alias);
ks.setCertificateEntry(alias, cert);
}
}
public static void loadX509Certificate(KeyStore ks, InputStream is)
throws CertificateException, KeyStoreException {
try {
CertificateFactory factory = CertificateFactory.getInstance("X509");
X509Certificate x509 = (X509Certificate)factory.generateCertificate(is);
String alias = x509.getSubjectDN().getName();
ks.setCertificateEntry(alias, x509);
} finally {
try { is.close(); } catch (IOException e) { /* This is sample, so omit the exception process
*/ }
}
}
}
PrivateCertificateHttpsActivity.java
package org.jssec.android.https.privatecertificate;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
public class PrivateCertificateHttpsActivity extends Activity {
private EditText mUrlBox;
private TextView mMsgBox;
private ImageView mImgBox;
private AsyncTask<String, Void, Object> mAsyncTask ;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUrlBox = (EditText)findViewById(R.id.urlbox);
mMsgBox = (TextView)findViewById(R.id.msgbox);
mImgBox = (ImageView)findViewById(R.id.imageview);
}
@Override
protected void onPause() {
// After this, Activity may be discarded, so cancel asynchronous process in advance.
if (mAsyncTask != null) mAsyncTask.cancel(true);
super.onPause();
}
public void onClick(View view) {
String url = mUrlBox.getText().toString();
mMsgBox.setText(url);
mImgBox.setImageBitmap(null);
// Cancel, since the last asynchronous process might have not been finished yet.
if (mAsyncTask != null) mAsyncTask.cancel(true);
// Since cannot communicate through UI thread, communicate by worker thread by AsynchTask.
mAsyncTask = new PrivateCertificateHttpsGet(this) {
@Override
protected void onPostExecute(Object result) {
// Process the communication result through UI thread.
if (result instanceof Exception) {
Exception e = (Exception)result;
mMsgBox.append("¥nException occurs¥n" + e.toString());
} else {
byte[] data = (byte[])result;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
mImgBox.setImageBitmap(bmp);
}
}
}.execute(url);
// Pass URL and start asynchronization process
}
}
相關文章
- 安卓應用安全指南 5.6.1 密碼學 示例程式碼安卓密碼學
- 安卓應用安全指南4.1.1建立/使用活動示例程式碼安卓
- 安卓應用安全指南4.6.1處理檔案示例程式碼安卓
- 安卓應用安全指南4.3.1建立/使用內容供應器示例程式碼安卓
- 安卓應用安全指南翻譯完成安卓
- 安卓應用安全指南4.8輸出到LogCat安卓GC
- 安卓應用安全指南六、困難問題安卓
- 安卓應用安全指南4.5.2使用SQLite規則書安卓SQLite
- 安卓應用安全指南4.7使用可瀏覽的意圖安卓
- 安卓應用安全指南4.1.2建立/使用活動規則書安卓
- https的通訊過程HTTP
- 更安全的Web通訊HTTPSWebHTTP
- HTTPS的安全通訊機制HTTP
- appium 安卓應用指令碼APP安卓指令碼
- 安卓移動應用程式碼安全加固系統設計及實現安卓
- 安卓應用安全指南5.5.2處理隱私資料規則書安卓
- ajax與json通過程式碼的簡單應用JSON
- 安卓應用安全指南4.2.3建立/使用廣播接收器高階話題安卓
- AndroidSerialPort:安卓串列埠通訊庫Android安卓串列埠
- 安卓串列埠通訊疑問安卓串列埠
- 驗證碼簡訊 API 接入指南:Java 語言示例程式碼APIJava
- Flutter 安卓 Platform 與 Dart 端訊息通訊方式 Channel 原始碼解析Flutter安卓PlatformDart原始碼
- 深入理解Https如何保證通訊安全HTTP
- 通過bundle Id查詢應用資訊
- 鴻蒙Next安全之應用加密:保障應用程式碼安全鴻蒙加密
- linux tomcat 開通443 (用https安全訪問)LinuxTomcatHTTP
- Android技術分享| 安卓3行程式碼,實現整套音視訊通話功能Android安卓行程
- 詳解Nginx伺服器和iOS的HTTPS安全通訊Nginx伺服器iOSHTTP
- 50多個惡意安卓應用繞過Google Play,感染了3000萬安卓使用者安卓Go
- [譯]通過HTTPS協議執行你的Flask程式HTTP協議Flask
- Flutter 如何釋出安卓應用?Flutter安卓
- Canonical通過Flutter啟用Linux桌面應用程式支援FlutterLinux
- 記一次https通訊除錯過程HTTP除錯
- 低程式碼智慧通訊:騰訊雲簡訊助力,快速構建高效訊息應用
- 實現在安卓平臺下的即時通訊安卓
- 中秋禮物!開源即時通訊GGTalk安卓版全新原始碼!安卓原始碼
- 用 HTTPS 安全嗎?HTTPS 的原理是啥?HTTP
- 應用程式安全的看法