首页 文章 精选 留言 我的

精选列表

搜索[安卓系统],共10000篇文章
优秀的个人博客,低调大师

华为 AppGallery 出现大量带有恶意木马的安卓游戏

杀毒软件公司 Dr Web 的网络安全研究人员在华为的官方应用商店 AppGallery 上发现了大量带有恶意软件的 Android 游戏,被感染的游戏类型包括但不限于模拟器、街机、策略和射击游戏,目前这些游戏已被安装超过 930 万次。 这些恶意游戏搭载Android.Cynos.7.origin木马,这个木马是收集用户信息的 Cynos 木马的变种。它会请求联网、拨打和管理电话的权限,用户同意权限后,内置的恶意程序就开始收集数据,并将信息传输到远程服务器。 据外媒the register介绍,被盗的信息包括用户的手机号码、设备位置、移动网络和 Wi-Fi 访问数据、移动网络参数、设备的技术规格以及来自应用程序元数据的参数。 除了收集数据,恶意程序还会往手机上其他的应用投放广告。为了降低用户的防备,恶意程序还通过检测语言和本地化设置来适配不同地域的用户,俄罗斯和中国的儿童用户是首要目标(游戏做得比较弱智,而且小朋友没什么防范意识)。 目前 Dr Web 已将被感染的游戏列表(妥协指标 - IOCs)放在 GitHub 上,而在与安全研究人员沟通之后,华为从 AppGallery 中删除了 190 个受感染的应用,且后续会进一步筛查。

优秀的个人博客,低调大师

Kotlin 1.3.50 发布,新一代安卓开发语言

Kotlin 1.3.50 发布了,此版本除了质量和工具改进之外,还有以下更新亮点: 设计了一个新的持续时间和时间测量 API(预览功能)。 改进 Java-to-Kotlin 转换器。 Gradle Kotlin/JS 项目中生成 npm 依赖项的外部声明(使用 Dukat,实验性功能)。 引入一个用于在 IntelliJ IDEA Ultimate 中调试 Kotlin/Native 代码的单独的插件。 多平台项目中的 Java 编译支持。 详情查看: https://blog.jetbrains.com/kotlin/2019/08/kotlin-1-3-50-released

优秀的个人博客,低调大师

安卓应用安全指南 5.5.1 处理隐私数据 示例代码

5.5.1 处理隐私数据 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 在准备应用的隐私政策时,你可以使用“协助创建应用隐私政策的工具” [29]。 这些工具以 HTML 格式和 XML 格式输出两个文件 - 应用隐私策略的摘要版本和详细版本。 这些文件的 HTML 和 XML 内容符合 MIC SPI 的建议,包括搜索标签等特性。 在下面的示例代码中,我们将演示此工具的用法,并使用由这个工具产生的 HTML 文件来展示程序隐私策略。 [29] http://www.kddilabs.jp/tech/public-tech/appgen.html 更具体地说,你可以使用以下流程图来确定使用哪个示例代码。 这里,“广泛同意”一词,指代广泛许可,由用户在应用的首次加载时,通过展示和查看程序隐私策略授予应用,用于应用将用户数据传输到服务器。 相反,短语“特定同意”指代在传输特定用户数据之前,立即获得的预先同意。 5.5.1.1 授予广泛同意和特定同意:包含应用隐私政策的应用 要点: 首次加载(或应用更新)时,获得广泛同意,来传输将由应用处理的用户数据。 如果用户未授予广泛同意,请勿传输用户数据。 在传输需要特别细致的处理的用户数据之前获得特定同意。 如果用户未授予特定同意,请勿传输相应的数据。 向用户提供可以查看应用隐私策略的方法。 提供通过用户操作删除传输的数据的方法。 提供通过用户操作停止数据传输的方法。 使用 UUID 或 cookie 来跟踪用户数据。 将应用隐私策略的摘要版本放置在素材文件夹中。 MainActivity.java package org.jssec.android.privacypolicy; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import org.jssec.android.privacypolicy.ConfirmFragment.DialogListener; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.location.LocationClient; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, DialogListener { private static final String BASE_URL = "https://www.example.com/pp"; private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; private static final String ID_KEY = "id"; private static final String LOCATION_KEY = "location"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY = "privacyPolicyComprehensiveAgreed"; private static final String PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY = "privacyPolicyDiscreteType1Agreed"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; private String UserId = ""; private LocationClient mLocationClient = null; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int DIALOG_TYPE_PRE_CONFIRMATION = 2; private static final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (resultCode == ConnectionResult.SUCCESS) { mLocationClient = new LocationClient(this, this, this); } } @Override protected void onStart() { super.onStart(); SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // When the application is updated, it is only necessary to renew the user's grant of broad c onsent if the updated application will handle new types of user data. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreeP rivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } // Used to obtain location data if (mLocationClient != null) { mLocationClient.connect(); } } @Override protected void onStop() { if (mLocationClient != null) { mLocationClient.disconnect(); } super.onStop(); } public void onSendToServer(View view) { // Check the status of user consent. // Actually, it is necessary to obtain consent for each user data type. SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofirmS endLocation, DIALOG_TYPE_PRE_CONFIRMATION); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } else { // Start transmission, since it has the user consent. onPositiveButtonClick(DIALOG_TYPE_PRE_CONFIRMATION); } } public void onPositiveButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, getVersionCode()); pref.apply(); } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude:" + currentLocation.getLatitude() + ", Longitude:" + currentLocation.getLongitude(); String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname + "¥n - location : " + locationData, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, locationData, nickname); } } // Store the status of user consent. // Actually, it is necessary to obtain consent for each user data type. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, getVersionCode()); pref.apply(); } } public void onNegativeButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 2 *** If the user does not grant general consent, do not transmit user data. // In this sample application we terminate the application in this case. finish(); } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // *** POINT 4 *** If the user does not grant specific consent, do not transmit the correspon ding data. // The user did not grant consent, so we do nothing. } } private int getVersionCode() { int versionCode = -1; PackageManager packageManager = this.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES); versionCode = packageInfo.versionCode; } catch (NameNotFoundException e) { // This is sample, so omit the exception process } return versionCode; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 5 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 6 *** Provide methods by which transmitted data can be deleted by user operations. new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 7 *** Provide methods by which transmitting data can be stopped by user operations. // If the user stop sending data, user consent is deemed to have been revoked. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, 0); pref.apply(); // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show(); finish(); return true; } return false; } @Override public void onConnected(Bundle connectionHint) { if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude(); String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData; TextView appText = (TextView) findViewById(R.id.appText); appText.setText(text); } } } @Override public void onConnectionFailed(ConnectionResult result) { if (result.hasResolution()) { try { result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } } @Override public void onDisconnected() { mLocationClient = null; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 8 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String location = params.length > 2 ? params[2] : null; String nickname = params.length > 3 ? params[3] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (location != null) jsonData.put(LOCATION_KEY, location); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } } ConfirmFragment.java package org.jssec.android.privacypolicy; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class ConfirmFragment extends DialogFragment { private DialogListener mListener = null; public static interface DialogListener { public void onPositiveButtonClick(int type); public void onNegativeButtonClick(int type); } public static ConfirmFragment newInstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putInt("title", title); args.putInt("sentence", sentence); args.putInt("type", type); fragment.setArguments(args); return fragment; } @Override public Dialog onCreateDialog(Bundle args) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. final int title = getArguments().getInt("title"); final int sentence = getArguments().getInt("sentence"); final int type = getArguments().getInt("type"); LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.fragment_comfirm, null); TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp); linkPP.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // *** POINT 5 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(getActivity(), WebViewAssetsActivity.class); startActivity(intent); } }); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIcon(R.drawable.ic_launcher); builder.setTitle(title); builder.setMessage(sentence); builder.setView(content); builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onPositiveButtonClick(type); } } }); builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onNegativeButtonClick(type); } } }); Dialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); } mListener = (DialogListener) activity; } public void setDialogListener(DialogListener listener) { mListener = listener; } } WebViewAssetsActivity.java package org.jssec.android.privacypolicy; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 9 *** Place a summary version of the application privacy policy in the assets folder private static final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } } 5.5.1.2 授予广泛同意:包含应用隐私政策的应用 要点: 首次加载(或应用更新)时,获得广泛同意,来传输将由应用处理的用户数据。 如果用户未授予广泛同意,请勿传输用户数据。 向用户提供可以查看应用隐私策略的方法。 提供通过用户操作删除传输的数据的方法。 提供通过用户操作停止数据传输的方法。 使用 UUID 或 cookie 来跟踪用户数据。 将应用隐私策略的摘要版本放置在素材文件夹中。 MainActivity.java package org.jssec.android.privacypolicynopreconfirm; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import org.jssec.android.privacypolicynopreconfirm.MainActivity; import org.jssec.android.privacypolicynopreconfirm.R; import org.jssec.android.privacypolicynopreconfirm.ConfirmFragment.DialogListener; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.telephony.TelephonyManager; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements DialogListener { private final String BASE_URL = "https://www.example.com/pp"; private final String GET_ID_URI = BASE_URL + "/get_id.php"; private final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private final String DEL_ID_URI = BASE_URL + "/del_id.php"; private final String ID_KEY = "id"; private final String NICK_NAME_KEY = "nickname"; private final String IMEI_KEY = "imei"; private final String PRIVACY_POLICY_AGREED_KEY = "privacyPolicyAgreed"; private final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); } @Override protected void onStart() { super.onStart(); SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // When the application is updated, it is only necessary to renew the user's grant of broad consent if the updated application will handle new types of user data. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreePr ivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } } public void onSendToServer(View view) { String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); String imei = tm.getDeviceId(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname + ", imei = " + imei, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname, imei); } public void onPositiveButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_AGREED_KEY, getVersionCode()); pref.apply(); } } public void onNegativeButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 2 *** If the user does not grant general consent, do not transmit user data. // In this sample application we terminate the application in this case. finish(); } } private int getVersionCode() { int versionCode = -1; PackageManager packageManager = this.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES); versionCode = packageInfo.versionCode; } catch (NameNotFoundException e) { // This is sample, so omit the exception process } return versionCode; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 3 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 4 *** Provide methods by which transmitted data can be deleted by user operation s. new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 5 *** Provide methods by which transmitting data can be stopped by user operations. // If the user stop sending data, user consent is deemed to have been revoked. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_AGREED_KEY, 0); pref.apply(); // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.L ENGTH_SHORT).show(); finish(); return true; } return false; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 6 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2 ? params[2] : null; String imei = params.length > 3 ? params[3] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); if (imei != null) jsonData.put(IMEI_KEY, imei); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } } ConfirmFragment.java package org.jssec.android.privacypolicynopreconfirm; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class ConfirmFragment extends DialogFragment { private DialogListener mListener = null; public static interface DialogListener { public void onPositiveButtonClick(int type); public void onNegativeButtonClick(int type); } public static ConfirmFragment newInstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putInt("title", title); args.putInt("sentence", sentence); args.putInt("type", type); fragment.setArguments(args); return fragment; } @Override public Dialog onCreateDialog(Bundle args) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. final int title = getArguments().getInt("title"); final int sentence = getArguments().getInt("sentence"); final int type = getArguments().getInt("type"); LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.fragment_comfirm, null); TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp); linkPP.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // *** POINT 3 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(getActivity(), WebViewAssetsActivity.class); startActivity(intent); } }); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIcon(R.drawable.ic_launcher); builder.setTitle(title); builder.setMessage(sentence); builder.setView(content); builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onPositiveButtonClick(type); } } }); builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onNegativeButtonClick(type); } } }); Dialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); } mListener = (DialogListener) activity; } public void setDialogListener(DialogListener listener) { mListener = listener; } } WebViewAssetsActivity.java package org.jssec.android.privacypolicynopreconfirm; import org.jssec.android.privacypolicynopreconfirm.R; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 7 *** Place a summary version of the application privacy policy in the assets folder private final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } } 5.5.1.3 不需要广泛同意:包含应用隐私策略的应用 要点: 向用户提供查看应用隐私策略的方法。 提供通过用户操作删除传输的数据的方法。 提供通过用户操作停止数据传输的方法 使用 UUID 或 cookie 来跟踪用户数据。 将应用隐私策略的摘要版本放置在素材文件夹中。 MainActivity.java package org.jssec.android.privacypolicynocomprehensive; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.SharedPreferences; import android.support.v4.app.FragmentActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity { private static final String BASE_URL = "https://www.example.com/pp"; private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; private static final String ID_KEY = "id"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); } public void onSendToServer(View view) { String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname, Toast.LENGTH_SHORT).show(); new sendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 1 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 2 *** Provide methods by which transmitted data can be deleted by user operations. new sendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 3 *** Provide methods by which transmitting data can be stopped by user operations. // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show(); finish(); return true; } return false; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 4 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class sendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2 ? params[2] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } } WebViewAssetsActivity.java package org.jssec.android.privacypolicynocomprehensive; import org.jssec.android.privacypolicynocomprehensive.R; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 5 *** Place a summary version of the application privacy policy in the assets folder private static final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } } 5.5.1.4 不包含应用隐私策略的应用 要点: 如果你的应用只使用它在设备中获取的信息,则不需要显示应用隐私策略。 在市场应用或类似应用的文档中,请注意应用不会将其获取的信息传输到外部。 MainActivity.java package org.jssec.android.privacypolicynoinfosent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.location.LocationClient; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.content.Intent; import android.content.IntentSender; import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { private LocationClient mLocationClient = null; private final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLocationClient = new LocationClient(this, this, this); } @Override protected void onStart() { super.onStart(); // Used to obtain location data if (mLocationClient != null) { mLocationClient.connect(); } } @Override protected void onStop() { if (mLocationClient != null) { mLocationClient.disconnect(); } super.onStop(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } public void onStartMap(View view) { // *** POINT 1 *** You do not need to display an application privacy policy if your application w ill only use the information it obtains within the device. if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:" + currentLocation.getLatitude() + "," + currentLocation.getLongitude())); startActivity(intent); } } } @Override public void onConnected(Bundle connectionHint) { if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude(); String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + text, Toast.LENGTH_SHORT).show(); TextView appText = (TextView) findViewById(R.id.appText); appText.setText(text); } } } @Override public void onConnectionFailed(ConnectionResult result) { if (result.hasResolution()) { try { result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } } @Override public void onDisconnected() { mLocationClient = null; Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); } } 市场上的示例如下。

优秀的个人博客,低调大师

安卓应用安全指南 5.6.2 密码学 规则书

5.6.2 密码学 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 使用加密技术时,遵循以下规则: 5.6.2.1 指定加密算法时,请显式指定加密模式和填充(必需) 在使用加密技术和数据验证等密码学技术时,加密模式和填充必须显式指定。 在 Android 应用开发中使用加密时,你将主要使用java.crypto中的Cipher类。 为了使用Cipher类,你将首先通过指定要使用的加密类型,来创建Cipher类对象的实例。 这个指定被称为转换,并且有两种格式可以指定转换: 算法/模式/填充 算法 在后一种情况下,加密模式和填充将隐式设置为 Android 可以访问的加密服务供应器的适当默认值。 这些默认值优先考虑便利性和兼容性而选择,并且在某些情况下可能不是特别安全的选择。 为此,为了确保正确的安全保护,必须使用两种格式中的前者,其中显式指定了加密模式和填充。 5.6.2.2 使用强算法(特别是符合相关标准的算法)(必需) 使用加密技术时,选择符合特定标准的强算法很重要。 此外,在算法允许多个密钥长度的情况下,重要的是要考虑应用的整个产品生命周期,并选择足以确保安全性的密钥长度。 此外,对于一些加密模式和填充模式,存在已知的攻击策略;对这些威胁做出有力的选择是非常重要的。 确实,选择弱加密方法会造成灾难性后果。 例如,被加密来防止第三方窃听的文件,实际上可能仅受到无效保护,并且可能允许第三方窃听。 由于 IT 的不断进步导致加密分析技术的持续改进,因此至关重要的是,考虑并选择一个算法,它能够在运行的整个期间,保证安全性。在此时间,你希望应用保持运行。 实际加密技术的标准因国家而异,详见下表(单位:位)。 表 5.6-1 NIST(USA) NIST SP800-57 算法生命周期 对称密钥加密 非对称密钥加密 椭圆曲线加密 HASH(数字签名) HASH(随机数生成) ~2010 80 1024 160 160 160 ~2030 112 2048 224 224 160 2030~ 128 3072 256 256 160 表 5.6-2 ECRYPT II (EU) 算法生命周期 对称密钥加密 非对称密钥加密 椭圆曲线加密 HASH 2009~2012 80 1248 160 160 2009~2020 96 1776 192 192 2009~2030 112 2432 224 224 2009~2040 128 3248 256 256 2009~ 256 15424 512 512 表 5.6-3 CRYPTREC(Japan) CRYPTREC 加密算法列表 技术族 名称 公钥加密 签名 DSA,ECDSA,RSA-PSS,RSASSA-PKCS1-V1_5 机密性 RSA-OAEP 密钥共享 DH,ECDH 共享密钥加密 64 位块加密 3-key Triple DES 128 位块加密 AES,Camellia 流式加密 KCipher-2 哈希函数 SHA-256,SHA-384,SHA-512 加密使用模式 密文模式 CBC,CFB,CTR,OFB 认证密文模式 CCM,GCM 消息认证代码 CMAC,HMAC 实体认证 ISO/IEC 9798-2,ISO/IEC 9798-3 5.6.2.3 使用基于密码的加密时,不要在设备上存储密码(必需) 在基于密码的加密中,当根据用户输入的密码生成加密密钥时,请勿将密码存储在设备中。 基于密码的加密的优点是无需管理加密密钥;将密码存储在设备上消除了这一优势。 无需多说,在设备上存储密码会产生其他应用窃听的风险,因此出于安全原因,在设备上存储密码也是不可接受的。 5.6.2.4 从密码生成密钥时,使用盐(必需) 在基于密码的加密中,当根据用户输入的密码生成加密密钥时,请始终使用盐。 另外,如果你要在同一设备中为不同用户提供功能,请为每个用户使用不同的盐。 原因是,如果你仅使用简单的哈希函数生成加密密钥而不使用盐,则可以使用称为“彩虹表”的技术轻松恢复密码。使用了盐时,会使用相同的密码生成的密钥 将是不同的(不同的哈希值),防止使用彩虹表来搜索密钥。 示例: public final byte[] encrypt(final byte[] plain, final char[] password) { byte[] encrypted = null; try { // *** POINT *** Explicitly specify the encryption mode and the padding. // *** POINT *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Cipher cipher = Cipher.getInstance(TRANSFORMATION); // *** POINT *** When generating keys from passwords, use Salt. SecretKey secretKey = generateKey(password, mSalt); 5.6.2.5 从密码生成密钥时,指定适当的哈希迭代计数(必需) 在基于密码的加密中,当根据用户输入的密码生成加密密钥时,你需要选择在密钥生成过程(“拉伸”)中,散列过程的重复次数;指定足够大的数字来确保安全性非常重要。一般来说,1,000 或更大的迭代次数是足够的。如果你使用密钥来保护更有价值的资产,请指定 1,000,000 或更高的计数。由于散列函数的单个计算所需的处理时间很少,因此攻击者可能很容易进行爆破攻击。因此,通过使用拉伸方法(其中散列处理重复多次),我们可以有意确保该过程消耗大量时间,因此爆破攻击的成本更高。请注意,拉伸重复次数也会影响应用的处理速度,因此请谨慎选择合适的值。 示例: private static final SecretKey generateKey(final char[] password, final byte[] salt) { SecretKey secretKey = null; PBEKeySpec keySpec = null; (Omit) // *** POINT *** When generating a key from password, use Salt. // *** POINT *** When generating a key from password, specify an appropriate hash iteration count. // *** POINT *** Use a key of length sufficient to guarantee the strength of encryption. keySpec = new PBEKeySpec(password, salt, KEY_GEN_ITERATION_COUNT, KEY_LENGTH_BITS); 5.6.2.6 采取措施来增加密码强度(推荐) 在基于密码的加密中,当基于用户输入的密码生成加密密钥时,生成的密钥的强度受用户密码强度的强烈影响,因此值得采取措施来加强从用户那里收到的密码。 例如,你可以要求密码长度至少为 8 个字符,并且包含多种类型的字符 - 可能至少包含一个字母,一个数字和一个符号。

优秀的个人博客,低调大师

安卓应用安全指南 5.6.1 密码学 示例代码

5.6.1 密码学 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 针对特定用途和条件开发了各种加密方法,包括加密和解密数据(来确保机密性)和检测数据伪造(来确保完整性)等用例。 以下是示例代码,根据每种技术的目的分为三大类加密技术。 在每种情况下,应该能够根据密码技术的特点,选择适当的加密方法和密钥类型。 对于需要更详细考虑的情况,请参见章节“5.6.3.1 选择加密方法”。 在使用加密技术设计实现之前,请务必阅读“5.6.3.3 防止随机数字生成器中的漏洞的措施”。 保护数据免受第三方窃听 检测第三方所做的数据伪造 5.6.1.1 使用基于密码的密钥的加密和解密 你可以使用基于密码的密钥加密,来保护用户的机密数据资产。 要点: 显式指定加密模式和填充。 使用强加密技术(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 从密码生成密钥时,使用盐。 从密码生成密钥时,指定适当的哈希迭代计数。 使用足以保证加密强度的密钥长度。 AesCryptoPBEKey.java package org.jssec.android.cryptsymmetricpasswordbasedkey; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; public final class AesCryptoPBEKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption technologies (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Parameters passed to the getInstance method of the Cipher class: Encryption algorithm, block encryption mode, padding rule // In this sample, we choose the following parameter values: encryption algorithm=AES, block encryption mode=CBC, padding rule=PKCS7Padding private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding"; // A string used to fetch an instance of the class that generates the key private static final String KEY_GENERATOR_MODE = "PBEWITHSHA256AND128BITAES-CBC-BC"; // *** POINT 3 *** When generating a key from a password, use Salt. // Salt length in bytes public static final int SALT_LENGTH_BYTES = 20; // *** POINT 4 *** When generating a key from a password, specify an appropriate hash iteration count. // Set the number of mixing repetitions used when generating keys via PBE private static final int KEY_GEN_ITERATION_COUNT = 1024; // *** POINT 5 *** Use a key of length sufficient to guarantee the strength of encryption. // Key length in bits private static final int KEY_LENGTH_BITS = 128; private byte[] mIV = null; private byte[] mSalt = null; public byte[] getIV() { return mIV; } public byte[] getSalt() { return mSalt; } AesCryptoPBEKey(final byte[] iv, final byte[] salt) { mIV = iv; mSalt = salt; } AesCryptoPBEKey() { mIV = null; initSalt(); } private void initSalt() { mSalt = new byte[SALT_LENGTH_BYTES]; SecureRandom sr = new SecureRandom(); sr.nextBytes(mSalt); } public final byte[] encrypt(final byte[] plain, final char[] password) { byte[] encrypted = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption technologies (specifically, technologies that meet the relevant criteria), including algorithms, modes, and padding. Cipher cipher = Cipher.getInstance(TRANSFORMATION); // *** POINT 3 *** When generating keys from passwords, use Salt. SecretKey secretKey = generateKey(password, mSalt); cipher.init(Cipher.ENCRYPT_MODE, secretKey); mIV = cipher.getIV(); encrypted = cipher.doFinal(plain); } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return encrypted; } public final byte[] decrypt(final byte[] encrypted, final char[] password) { byte[] plain = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption technologies (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Cipher cipher = Cipher.getInstance(TRANSFORMATION); // *** POINT 3 *** When generating a key from a password, use Salt. SecretKey secretKey = generateKey(password, mSalt); IvParameterSpec ivParameterSpec = new IvParameterSpec(mIV); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec); plain = cipher.doFinal(encrypted); } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (InvalidAlgorithmParameterException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return plain; } private static final SecretKey generateKey(final char[] password, final byte[] salt) { SecretKey secretKey = null; PBEKeySpec keySpec = null; try { // *** POINT 2 *** Use strong encryption technologies (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Fetch an instance of the class that generates the key // In this example, we use a KeyFactory that uses SHA256 to generate AES-CBC 128-bit keys. SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_GENERATOR_MODE); // *** POINT 3 *** When generating a key from a password, use Salt. // *** POINT 4 *** When generating a key from a password, specify an appropriate hash iteration count. // *** POINT 5 *** Use a key of length sufficient to guarantee the strength of encryption. keySpec = new PBEKeySpec(password, salt, KEY_GEN_ITERATION_COUNT, KEY_LENGTH_BITS); // Clear password Arrays.fill(password, '?'); // Generate the key secretKey = secretKeyFactory.generateSecret(keySpec); } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { keySpec.clearPassword(); } return secretKey; } } 5.6.1.2 使用公钥的加密和解密 在某些情况下,数据加密仅在应用端使用存储的公钥来执行,而解密在单独安全位置(如服务器)在私钥下执行。 在这种情况下,可以使用公钥(非对称密钥)加密。 要点: 显式指定加密模式和填充 使用强加密方法(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 使用足以保证加密强度的密钥长度。 RsaCryptoAsymmetricKey.java package org.jssec.android.cryptasymmetrickey; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; public final class RsaCryptoAsymmetricKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes.. // Parameters passed to getInstance method of the Cipher class: Encryption algorithm, block encryption mode, padding rule // In this sample, we choose the following parameter values: encryption algorithm=RSA, block encryption mode=NONE, padding rule=OAEPPADDING. private static final String TRANSFORMATION = "RSA/NONE/OAEPPADDING"; // encryption algorithm private static final String KEY_ALGORITHM = "RSA"; // *** POINT 3 *** Use a key of length sufficient to guarantee the strength of encryption. // Check the length of the key private static final int MIN_KEY_LENGTH = 2000; RsaCryptoAsymmetricKey() { } public final byte[] encrypt(final byte[] plain, final byte[] keyData) { byte[] encrypted = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes.. Cipher cipher = Cipher.getInstance(TRANSFORMATION); PublicKey publicKey = generatePubKey(keyData); if (publicKey != null) { cipher.init(Cipher.ENCRYPT_MODE, publicKey); encrypted = cipher.doFinal(plain); } } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return encrypted; } public final byte[] decrypt(final byte[] encrypted, final byte[] keyData) { // In general, decryption procedures should be implemented on the server side; // however, in this sample code we have implemented decryption processing within the application to ensure confirmation of proper execution. // When using this sample code in real-world applications, be careful not to retain any private keys within the application. byte[] plain = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes.. Cipher cipher = Cipher.getInstance(TRANSFORMATION); PrivateKey privateKey = generatePriKey(keyData); cipher.init(Cipher.DECRYPT_MODE, privateKey); plain = cipher.doFinal(encrypted); } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return plain; } private static final PublicKey generatePubKey(final byte[] keyData) { PublicKey publicKey = null; KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(keyData)); } catch (IllegalArgumentException e) { } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { } // *** POINT 3 *** Use a key of length sufficient to guarantee the strength of encryption. // Check the length of the key if (publicKey instanceof RSAPublicKey) { int len = ((RSAPublicKey) publicKey).getModulus().bitLength(); if (len < MIN_KEY_LENGTH) { publicKey = null; } } return publicKey; } private static final PrivateKey generatePriKey(final byte[] keyData) { PrivateKey privateKey = null; KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyData)); } catch (IllegalArgumentException e) { } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { } return privateKey; } } 5.6.1.3 使用预共享密钥的加密和解密 预共享密钥可用于处理大型数据集,或保护应用或用户资产的机密性。 要点: 显式指定加密模式和填充 使用强加密方法(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 使用足以保证加密强度的密钥长度。 AesCryptoPreSharedKey.java package org.jssec.android.cryptsymmetricpresharedkey; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public final class AesCryptoPreSharedKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant cr iteria), including algorithms, block cipher modes, and padding modes. // Parameters passed to getInstance method of the Cipher class: Encryption algorithm, block encryption mode, padding rule // In this sample, we choose the following parameter values: encryption algorithm=AES, block encryption mode=CBC, padding rule=PKCS7Padding private static final String TRANSFORMATION = "AES/CBC/PKCS7Padding"; // Encryption algorithm private static final String KEY_ALGORITHM = "AES"; // Length of IV in bytes public static final int IV_LENGTH_BYTES = 16; // *** POINT 3 *** Use a key of length sufficient to guarantee the strength of encryption // Check the length of the key private static final int MIN_KEY_LENGTH_BYTES = 16; private byte[] mIV = null; public byte[] getIV() { return mIV; } AesCryptoPreSharedKey(final byte[] iv) { mIV = iv; } AesCryptoPreSharedKey() { } public final byte[] encrypt(final byte[] keyData, final byte[] plain) { byte[] encrypted = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Cipher cipher = Cipher.getInstance(TRANSFORMATION); SecretKey secretKey = generateKey(keyData); if (secretKey != null) { cipher.init(Cipher.ENCRYPT_MODE, secretKey); mIV = cipher.getIV(); encrypted = cipher.doFinal(plain); } } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return encrypted; } public final byte[] decrypt(final byte[] keyData, final byte[] encrypted) { byte[] plain = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Cipher cipher = Cipher.getInstance(TRANSFORMATION); SecretKey secretKey = generateKey(keyData); if (secretKey != null) { IvParameterSpec ivParameterSpec = new IvParameterSpec(mIV); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec); plain = cipher.doFinal(encrypted); } } catch (NoSuchAlgorithmException e) { } catch (NoSuchPaddingException e) { } catch (InvalidKeyException e) { } catch (InvalidAlgorithmParameterException e) { } catch (IllegalBlockSizeException e) { } catch (BadPaddingException e) { } finally { } return plain; } private static final SecretKey generateKey(final byte[] keyData) { SecretKey secretKey = null; try { // *** POINT 3 *** Use a key of length sufficient to guarantee the strength of encryption if (keyData.length >= MIN_KEY_LENGTH_BYTES) { // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. secretKey = new SecretKeySpec(keyData, KEY_ALGORITHM); } } catch (IllegalArgumentException e) { } finally { } return secretKey; } } 5.6.1.4 使用基于密码的密钥来检测数据伪造 你可以使用基于密码的(共享密钥)加密来验证用户数据的完整性。 要点: 显式指定加密模式和填充。 使用强加密方法(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 从密码生成密钥时,使用盐。 从密码生成密钥时,指定适当的哈希迭代计数。 使用足以保证 MAC 强度的密钥长度。 HmacPBEKey.java package org.jssec.android.signsymmetricpasswordbasedkey; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; public final class HmacPBEKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Parameters passed to the getInstance method of the Mac class: Authentication mode private static final String TRANSFORMATION = "PBEWITHHMACSHA1"; // A string used to fetch an instance of the class that generates the key private static final String KEY_GENERATOR_MODE = "PBEWITHHMACSHA1"; // *** POINT 3 *** When generating a key from a password, use Salt. // Salt length in bytes public static final int SALT_LENGTH_BYTES = 20; // *** POINT 4 *** When generating a key from a password, specify an appropriate hash iteration count. // Set the number of mixing repetitions used when generating keys via PBE private static final int KEY_GEN_ITERATION_COUNT = 1024; // *** POINT 5 *** Use a key of length sufficient to guarantee the MAC strength. // Key length in bits private static final int KEY_LENGTH_BITS = 160; private byte[] mSalt = null; public byte[] getSalt() { return mSalt; } HmacPBEKey() { initSalt(); } HmacPBEKey(final byte[] salt) { mSalt = salt; } private void initSalt() { mSalt = new byte[SALT_LENGTH_BYTES]; SecureRandom sr = new SecureRandom(); sr.nextBytes(mSalt); } public final byte[] sign(final byte[] plain, final char[] password) { return calculate(plain, password); } private final byte[] calculate(final byte[] plain, final char[] password) { byte[] hmac = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Mac mac = Mac.getInstance(TRANSFORMATION); // *** POINT 3 *** When generating a key from a password, use Salt. SecretKey secretKey = generateKey(password, mSalt); mac.init(secretKey); hmac = mac.doFinal(plain); } catch (NoSuchAlgorithmException e) { } catch (InvalidKeyException e) { } finally { } return hmac; } public final boolean verify(final byte[] hmac, final byte[] plain, final char[] password) { byte[] hmacForPlain = calculate(plain, password); if (Arrays.equals(hmac, hmacForPlain)) { return true; } return false; } private static final SecretKey generateKey(final char[] password, final byte[] salt) { SecretKey secretKey = null; PBEKeySpec keySpec = null; try { // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Fetch an instance of the class that generates the key // In this example, we use a KeyFactory that uses SHA1 to generate AES-CBC 128-bit keys. SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_GENERATOR_MODE); // *** POINT 3 *** When generating a key from a password, use Salt. // *** POINT 4 *** When generating a key from a password, specify an appropriate hash iteration count. // *** POINT 5 *** Use a key of length sufficient to guarantee the MAC strength. keySpec = new PBEKeySpec(password, salt, KEY_GEN_ITERATION_COUNT, KEY_LENGTH_BITS); // Clear password Arrays.fill(password, '?'); // Generate the key secretKey = secretKeyFactory.generateSecret(keySpec); } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { keySpec.clearPassword(); } return secretKey; } } 5.6.1.5 使用公钥来检测数据伪造 所处理的数据的签名,由存储在不同的安全位置(如服务器)中的私钥确定时,你可以使用公钥(不对称密钥)加密来处理涉及应用端公钥存储的应用,出于验证数据签名的目的。 要点: 显式指定加密模式和填充。 使用强加密方法(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 使用足以保证签名强度的密钥长度。 RsaSignAsymmetricKey.java package org.jssec.android.signasymmetrickey; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public final class RsaSignAsymmetricKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Parameters passed to the getInstance method of the Cipher class: Encryption algorithm, block encryption mode, padding rule // In this sample, we choose the following parameter values: encryption algorithm=RSA, block encryption mode=NONE, padding rule=OAEPPADDING. private static final String TRANSFORMATION = "SHA256withRSA"; // encryption algorithm private static final String KEY_ALGORITHM = "RSA"; // *** POINT 3 *** Use a key of length sufficient to guarantee the signature strength. // Check the length of the key private static final int MIN_KEY_LENGTH = 2000; RsaSignAsymmetricKey() { } public final byte[] sign(final byte[] plain, final byte[] keyData) { // In general, signature procedures should be implemented on the server side; // however, in this sample code we have implemented signature processing within the application to ensure confirmation of proper execution. // When using this sample code in real-world applications, be careful not to retain any private keys within the application. byte[] sign = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Signature signature = Signature.getInstance(TRANSFORMATION); PrivateKey privateKey = generatePriKey(keyData); signature.initSign(privateKey); signature.update(plain); sign = signature.sign(); } catch (NoSuchAlgorithmException e) { } catch (InvalidKeyException e) { } catch (SignatureException e) { } finally { } return sign; } public final boolean verify(final byte[] sign, final byte[] plain, final byte[] keyData) { boolean ret = false; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Signature signature = Signature.getInstance(TRANSFORMATION); PublicKey publicKey = generatePubKey(keyData); signature.initVerify(publicKey); signature.update(plain); ret = signature.verify(sign); } catch (NoSuchAlgorithmException e) { } catch (InvalidKeyException e) { } catch (SignatureException e) { } finally { } return ret; } private static final PublicKey generatePubKey(final byte[] keyData) { PublicKey publicKey = null; KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(keyData)); } catch (IllegalArgumentException e) { } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { } // *** POINT 3 *** Use a key of length sufficient to guarantee the signature strength. // Check the length of the key if (publicKey instanceof RSAPublicKey) { int len = ((RSAPublicKey) publicKey).getModulus().bitLength(); if (len < MIN_KEY_LENGTH) { publicKey = null; } } return publicKey; } private static final PrivateKey generatePriKey(final byte[] keyData) { PrivateKey privateKey = null; KeyFactory keyFactory = null; try { keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyData)); } catch (IllegalArgumentException e) { } catch (NoSuchAlgorithmException e) { } catch (InvalidKeySpecException e) { } finally { } return privateKey; } } 5.6.1.6 使用预共享密钥来检测数据伪造 你可以使用预共享密钥来验证应用资产或用户资产的完整性。 要点: 显式指定加密模式和填充。 使用强加密方法(特别是符合相关标准的技术),包括算法,分组加密模式和填充模式。 使用足以保证 MAC 强度的密钥长度。 HmacPreSharedKey.java package org.jssec.android.signsymmetricpresharedkey; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public final class HmacPreSharedKey { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. // Parameters passed to the getInstance method of the Mac class: Authentication mode private static final String TRANSFORMATION = "HmacSHA256"; // Encryption algorithm private static final String KEY_ALGORITHM = "HmacSHA256"; // *** POINT 3 *** Use a key of length sufficient to guarantee the MAC strength. // Check the length of the key private static final int MIN_KEY_LENGTH_BYTES = 16; HmacPreSharedKey() { } public final byte[] sign(final byte[] plain, final byte[] keyData) { return calculate(plain, keyData); } public final byte[] calculate(final byte[] plain, final byte[] keyData) { byte[] hmac = null; try { // *** POINT 1 *** Explicitly specify the encryption mode and the padding. // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. Mac mac = Mac.getInstance(TRANSFORMATION); SecretKey secretKey = generateKey(keyData); if (secretKey != null) { mac.init(secretKey); hmac = mac.doFinal(plain); } } catch (NoSuchAlgorithmException e) { } catch (InvalidKeyException e) { } finally { } return hmac; } public final boolean verify(final byte[] hmac, final byte[] plain, final byte[] keyData) { byte[] hmacForPlain = calculate(plain, keyData); if (hmacForPlain != null && Arrays.equals(hmac, hmacForPlain)) { return true; } return false; } private static final SecretKey generateKey(final byte[] keyData) { SecretKey secretKey = null; try { // *** POINT 3 *** Use a key of length sufficient to guarantee the MAC strength. if (keyData.length >= MIN_KEY_LENGTH_BYTES) { // *** POINT 2 *** Use strong encryption methods (specifically, technologies that meet the relevant criteria), including algorithms, block cipher modes, and padding modes. secretKey = new SecretKeySpec(keyData, KEY_ALGORITHM); } } catch (IllegalArgumentException e) { } finally { } return secretKey; } }

优秀的个人博客,低调大师

安卓应用安全指南 5.5.2 处理隐私数据 规则书

5.5.2 处理隐私数据 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 处理隐私策略时,遵循以下规则: 5.5.2.1 将用户数据的传输限制为最低需求(必需) 将使用数据传输到外部服务器或其他目标时,将传输限制在提供服务的最低需求。 特别是,你应该设计为,应用只能访问这些用户数据,用户可以根据应用描述来想象它们的使用目的。 例如,用户可以想象,它是个警报应用,但不能访问位置数据。另一方面,如果警报应用可以根据用户的位置发出警报,并将其功能写入应用的描述中,则应用可以访问位置数据。 在只需要在应用中访问信息的情况下,避免将信息传输到外部,并采取其他措施来减少无意中泄漏用户数据的可能性。 5.5.2.2 在首次加载(或应用更新)时,获得广泛同意来传输需要特别细致处理或用户可能难以更改的用户数据(必需) 如果应用向外部服务器,传输用户可能难以更改的任何用户数据,或需要特别细致处理的任何用户数据,则应用必须在用户开始使用之前,获得用户的预先同意(选择性加入) - 通知用户哪些类型的信息将被发送到服务器,以及是否会涉及任何第三方厂商。 更具体地说,首次启动时,应用应显示其应用隐私政策并确认该用户已阅读并同意。 此外,无论何时应用更新,通过将新类型的用户数据传输到外部服务器,它都必须再次确认用户已经阅读并同意这些更改。 如果用户不同意,应用应该终止或以其他方式采取措施,来确保所有需要传输数据的功能都被禁用。 这些步骤可以确保,用户了解他们在使用应用时如何处理数据,为用户提供安全感并增强他们对应用的信任。 MainActivity.java protected void onStart() { super.onStart(); // (some portions omitted) if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // When the application is updated, it is only necessary to renew the user’s grant of broad consent if the updated application will handle new types of user data. ConfirmFragment dialog = ConfirmFragment.newInstance( R.string.privacyPolicy, R.string.agreePrivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } 5.5.2.3 在传输需要特殊处理的用户数据之前获得特定的同意(必需) 向外部服务器传输任何需要特别细致处理的用户数据时,除了需要获得一般同意之外,应用必须获得用户对每种这类用户数据(或涉及传输用户数据的每个功能)的预先同意(选择性加入)。 如果用户不同意,则应用不得将相应的数据发送到外部服务器。 这确保用户可以更全面地了解应用的功能(及其提供的服务)和用户对其授予一般同意的,用户数据的传输之间的关系;同时,应用提厂商可以基于更精确的决策,预计获得用户的同意。 MainActivity.java public void onSendToServer(View view) { // *** POINT *** Obtain specific consent before transmitting user data that requires particularly delicate handling. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofi rmSendLocation, DIALOG_TYPE_PRE_CONFIRMATION); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } 5.5.2.4 向用户提供查看应用隐私策略的方法(必需) 一般来说,Android 应用市场将提供应用隐私策略的链接,供用户在选择安装相应的应用之前进行复查。 除了支持此功能之外,应用还需要提供一些方法,用户在设备上安装应用后,可以查看应用隐私策略。 特别重要的是提供一些方法,用户可以轻易复查应用隐私政策。在同意的情况下,将用户数据传输到外部服务器来协助用户作出适当决定。 MainActivity.java @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; 5.5.2.5 在素材文件夹中放置应用隐私策略的摘要版本(推荐) 将应用隐私策略的摘要版本放在素材文件夹中,来确保用户可以按需对其进行复查,这是一个不错的主意。 确保素材文件夹中存在应用隐私策略,不仅可以让用户随时轻松访问它,还可以避免用户看到由恶意第三方准备的应用隐私策略的伪造或损坏版本的风险。 5.5.2.6 提供可以删除传输的数据的方法,以及可以通过用户操作停止数据传输的方法(推荐) 提供根据用户需要,删除传输到外部服务器的用户数据的方法,是一个好主意。与之相似,在应用本身已经在设备内存储用户数据(或其副本)的情况下,向用户提供用于删除该数据的方法是一个好主意。而且,提供可以根据用户要求停止用户数据发送的方法,是一个好主意。 这一规则(建议)由欧盟推行的“被遗忘权”编纂而成;更普遍的是,在未来,各种提案将要求进一步加强用户保护其数据的权利,这看起来很明显。为此在这些指导方针中,我们建议提供删除用户数据的方法,除非有一些具体原因不能这样做。并且,停止数据传输,主要由浏览器的对应观点“不追踪(否定追踪)”定义。 MainActivity.java @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { (some portions omitted) case R.id.action_del_id: // *** POINT *** Provide methods by which transmitted data can be deleted by user operations. new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; } 5.5.2.7 从 UUID 和 Cookie 中分离设备特定的 ID(推荐) 不应通过与用户数据绑定的方式传输 IMEI 和其他设备特定 ID。 事实上,如果一个设备特定的 ID 和一段用户数据被捆绑在一起,并发布或泄露给公众 - 即使只有一次 - 随后也不可能改变该设备特定的 ID,因此对于把 ID 和用户数据绑定的服务器来说,这是不可能的(或至少 很难)。 在这种情况下,最好使用 UUID 或 cookie(即每次基于随机数重新生成的变量 ID),与用户数据一起传输时代替设备特定的 ID。 这允许实现上面讨论的“被遗忘的权利”的概念。 MainActivity.java @Override protected String doInBackground(String... params) { // *** POINT *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } 5.5.2.8 如果你只在设备内使用用户数据,请通知用户,数据不会传输到外部(推荐) 即使在用户数据只在用户设备中临时访问的情况下,向用户传达这一事实也是一个好主意,来确保用户充分和透明地理解了应用行为。 更具体来说,应该告知用户,应用访问的用户数据只在设备内用于特定的目的,不会被存储或发送。 将此内容传达给用户的可能方法,包括在应用市场上的应用描述中指定它。 仅在设备中临时使用的信息,不需要在应用隐私策略中讨论。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册