4.2.1 创建/使用广播接收器 示例代码

原书:Android Application Secure Design/Secure Coding Guidebook

译者:飞龙

协议:CC BY-NC-SA 4.0

接收广播需要创建广播接收器。 使用广播接收器的风险和对策,根据收到的广播的类型而有所不同。 你可以在以下判断流程中找到你的广播接收器。 接收应用无法检查发送广播的应用的包名称,它是链接伙伴所需的。 因此,无法创建用于伙伴的广播接收器。

表 4.2:广播接收器的类型定义:

类型 定义
私有 只能接收来自相同应用的广播的广播接收器,所以是最安全的
公共 可以接收来自未指定的大量应用的广播的广播接收器
内部 只能接收来自其他内部应用的广播的广播接收器

另外,根据定义方法,广播接收器可以分为两类:静态和动态。 它们之间的差异可以在下图中找到。 示例代码展示了每类的实现方法。 还描述了发送应用的实现方法,因为发送信息的对策取决于接收器来确定。

表 4.2-2

定义方法 特性
静态 AndroidManifest.xml中的<receiver>元素定义
动态 通过在程序中调用registerReceiver()unregisterReceiver(),动态注册和注销广播接收器

4.2.1.1 私有广播接收器

私人广播接收器是最安全的广播接收器,因为只能接收到从应用内发送的广播。 动态广播接收器不能注册为私有,所以私有广播接收器只包含静态广播接收器。

要点(接收广播):

1) 将导出属性显示设为false

2) 小心并安全地处理收到的意图,即使意图从相同的应用中发送

3) 敏感信息可以作为返回结果发送,因为请求来自相同应用

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.broadcast.privatereceiver" ><application
        android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:allowBackup="false" ><!-- Private Broadcast Receiver --><!-- *** POINT 1 *** Explicitly set the exported attribute to false. --><receiver
        android:name=".PrivateReceiver"android:exported="false" /><activity
            android:name=".PrivateSenderActivity"android:label="@string/app_name"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>

PrivateReceiver.java

package org.jssec.android.broadcast.privatereceiver;import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;public class PrivateReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 2 *** Handle the received intent carefully and securely,// even though the intent was sent from within the same application.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."String param = intent.getStringExtra("PARAM");Toast.makeText(context,String.format("Received param: ¥"%s¥"", param),Toast.LENGTH_SHORT).show();// *** POINT 3 *** Sensitive information can be sent as the returned results since the requests come from within the same application.setResultCode(Activity.RESULT_OK);setResultData("Sensitive Info from Receiver");abortBroadcast();}
}

向私有广播接收器发送广播的代码展示在下面:

要点(发送广播):

4) 使用带有指定类的显式意图,来调用相同应用中的接收器。

5) 敏感信息可以发送,因为目标接收器在相同应用中。

6) 小心并安全地处理收到的返回结果,即使数据来自相同应用中的接收器。

PrivateSenderActivity.java

package org.jssec.android.broadcast.privatereceiver;import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;public class PrivateSenderActivity extends Activity {public void onSendNormalClick(View view) {// *** POINT 4 *** Use the explicit Intent with class specified to call a receiver within the same application.Intent intent = new Intent(this, PrivateReceiver.class);// *** POINT 5 *** Sensitive information can be sent since the destination Receiver is within the same application.intent.putExtra("PARAM", "Sensitive Info from Sender");sendBroadcast(intent);}public void onSendOrderedClick(View view) {// *** POINT 4 *** Use the explicit Intent with class specified to call a receiver within the same application.Intent intent = new Intent(this, PrivateReceiver.class);// *** POINT 5 *** Sensitive information can be sent since the destination Receiver is within the same application.intent.putExtra("PARAM", "Sensitive Info from Sender");sendOrderedBroadcast(intent, null, mResultReceiver, null, 0, null, null);}private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 6 *** Handle the received result data carefully and securely,// even though the data came from the Receiver within the same application.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."String data = getResultData();PrivateSenderActivity.this.logLine(String.format("Received result: ¥"%s¥"", data));}};private TextView mLogView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mLogView = (TextView)findViewById(R.id.logview);}private void logLine(String line) {mLogView.append(line);mLogView.append("¥n");}
}

4.2.1.2 公共广播接收器

公共广播接收器是可以从未指定的大量应用程序接收广播的广播接收器,因此有必要注意,它可能从恶意软件接收广播。

要点(接收广播):

1) 将导出属性显式设为true

2) 小心并安全地处理收到的意图。

3) 返回结果时,不要包含敏感信息。

公共广播接收器的示例代码可以用于静态和动态广播接收器。

PublicReceiver.java

package org.jssec.android.broadcast.publicreceiver;import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;public class PublicReceiver extends BroadcastReceiver {private static final String MY_BROADCAST_PUBLIC ="org.jssec.android.broadcast.MY_BROADCAST_PUBLIC";public boolean isDynamic = false;private String getName() {return isDynamic ? "Public Dynamic Broadcast Receiver" : "Public Static Broadcast Receiver";}@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 2 *** Handle the received Intent carefully and securely.// Since this is a public broadcast receiver, the requesting application may be malware.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."if (MY_BROADCAST_PUBLIC.equals(intent.getAction())) {String param = intent.getStringExtra("PARAM");Toast.makeText(context,String.format("%s:¥nReceived param: ¥"%s¥"", getName(), param),Toast.LENGTH_SHORT).show();}// *** POINT 3 *** When returning a result, do not include sensitive information.// Since this is a public broadcast receiver, the requesting application may be malware.// If no problem when the information is taken by malware, it can be returned as result.setResultCode(Activity.RESULT_OK);setResultData(String.format("Not Sensitive Info from %s", getName()));abortBroadcast();}
}

静态广播接收器定义在AndroidManifest.xml中:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jssec.android.broadcast.publicreceiver" ><application
        android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:allowBackup="false" ><!-- Public Static Broadcast Receiver --><!-- *** POINT 1 *** Explicitly set the exported attribute to true. --><receiver
            android:name=".PublicReceiver"android:exported="true" ><intent-filter><action android:name="org.jssec.android.broadcast.MY_BROADCAST_PUBLIC" /></intent-filter></receiver><service
            android:name=".DynamicReceiverService"android:exported="false" /><activity
            android:name=".PublicReceiverActivity"android:label="@string/app_name"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>

在动态广播接收器中,通过调用程序中的registerReceiver()unregisterReceiver()来执行注册/注销。 为了通过按钮操作执行注册/注销,该按钮PublicReceiverActivity中定义。 由于动态广播接收器实例的作用域比PublicReceiverActivity长,因此不能将其保存为PublicReceiverActivity的成员变量。 在这种情况下,请将动态广播接收器实例保存为DynamicReceiverService的成员变量,然后从PublicReceiverActivity启动/结束DynamicReceiverService,来间接注册/注销动态广播接收器。

DynamicReceiverService.java

package org.jssec.android.broadcast.publicreceiver;import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.widget.Toast;public class DynamicReceiverService extends Service {private static final String MY_BROADCAST_PUBLIC ="org.jssec.android.broadcast.MY_BROADCAST_PUBLIC";private PublicReceiver mReceiver;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();// Register Public Dynamic Broadcast Receiver.mReceiver = new PublicReceiver();mReceiver.isDynamic = true;IntentFilter filter = new IntentFilter();filter.addAction(MY_BROADCAST_PUBLIC);filter.setPriority(1); // Prioritize Dynamic Broadcast Receiver, rather than Static Broadcast Receiver.registerReceiver(mReceiver, filter);Toast.makeText(this,"Registered Dynamic Broadcast Receiver.",Toast.LENGTH_SHORT).show();}@Overridepublic void onDestroy() {super.onDestroy();// Unregister Public Dynamic Broadcast Receiver.unregisterReceiver(mReceiver);mReceiver = null;Toast.makeText(this,"Unregistered Dynamic Broadcast Receiver.",Toast.LENGTH_SHORT).show();}
}

PublicReceiverActivity.java

package org.jssec.android.broadcast.publicreceiver;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;public class PublicReceiverActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);}public void onRegisterReceiverClick(View view) {Intent intent = new Intent(this, DynamicReceiverService.class);startService(intent);}public void onUnregisterReceiverClick(View view) {Intent intent = new Intent(this, DynamicReceiverService.class);stopService(intent);}
}

接下来,展示了将广播发送到公共广播接收器的示例代码。 当向公共广播接收器发送广播时,需要注意广播可以被恶意软件接收。

要点(发送广播):

4) 不要发送敏感信息

5) 接受广播时,小心并安全地处理结果数据

PublicSenderActivity.java

package org.jssec.android.broadcast.publicsender;import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;public class PublicSenderActivity extends Activity {private static final String MY_BROADCAST_PUBLIC ="org.jssec.android.broadcast.MY_BROADCAST_PUBLIC";public void onSendNormalClick(View view) {// *** POINT 4 *** Do not send sensitive information.Intent intent = new Intent(MY_BROADCAST_PUBLIC);intent.putExtra("PARAM", "Not Sensitive Info from Sender");sendBroadcast(intent);}public void onSendOrderedClick(View view) {// *** POINT 4 *** Do not send sensitive information.Intent intent = new Intent(MY_BROADCAST_PUBLIC);intent.putExtra("PARAM", "Not Sensitive Info from Sender");sendOrderedBroadcast(intent, null, mResultReceiver, null, 0, null, null);}public void onSendStickyClick(View view) {// *** POINT 4 *** Do not send sensitive information.Intent intent = new Intent(MY_BROADCAST_PUBLIC);intent.putExtra("PARAM", "Not Sensitive Info from Sender");//sendStickyBroadcast is deprecated at API Level 21sendStickyBroadcast(intent);}public void onSendStickyOrderedClick(View view) {// *** POINT 4 *** Do not send sensitive information.Intent intent = new Intent(MY_BROADCAST_PUBLIC);intent.putExtra("PARAM", "Not Sensitive Info from Sender");//sendStickyOrderedBroadcast is deprecated at API Level 21sendStickyOrderedBroadcast(intent, mResultReceiver, null, 0, null, null);}public void onRemoveStickyClick(View view) {Intent intent = new Intent(MY_BROADCAST_PUBLIC);//removeStickyBroadcast is deprecated at API Level 21removeStickyBroadcast(intent);}private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 5 *** When receiving a result, handle the result data carefully and securely.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."String data = getResultData();PublicSenderActivity.this.logLine(String.format("Received result: ¥"%s¥"", data));}};private TextView mLogView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mLogView = (TextView)findViewById(R.id.logview);}private void logLine(String line) {mLogView.append(line);mLogView.append("¥n");}
}

4.2.1.3 内部广播接收器

内部广播接收器是广播接收器,它将永远不会收到从内部应用以外发送的任何广播。 它由几个内部应用组成,用于保护内部应用处理的信息或功能。

要点(接收广播):

1) 定义内部签名权限来接收广播。

2) 声明使用内部签名权限来接收结果。

3) 将导出属性显式设置为true

4) 需要静态广播接收器定义的内部签名权限。

5) 需要内部签名来注册动态广播接收器。

6) 确认内部签名权限是由内部应用定义的。

7) 尽管广播是从内部应用发送的,但要小心并安全地处理接收到的意图。

8) 由于请求应用是内部的,因此可以返回敏感信息。

9) 导出 APK 时,使用与发送应用相同的开发人员密钥对 APK 进行签名。

内部广播接收器的示例代码可用于静态和动态广播接收器。

InhouseReceiver.java

package org.jssec.android.broadcast.inhousereceiver;import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;public class InhouseReceiver extends BroadcastReceiver {// In-house Signature Permissionprivate static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION";// In-house certificate hash valueprivate static String sMyCertHash = null;private static String myCertHash(Context context) {if (sMyCertHash == null) {if (Utils.isDebuggable(context)) {// Certificate hash value of "androiddebugkey" in the debug.keystore.sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";} else {// Certificate hash value of "my company key" in the keystore.sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";}}return sMyCertHash;}private static final String MY_BROADCAST_INHOUSE ="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";public boolean isDynamic = false;private String getName() {return isDynamic ? "In-house Dynamic Broadcast Receiver" : "In-house Static Broadcast Receiver";}@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 6 *** Verify that the in-house signature permission is defined by an in-house application.if (!SigPerm.test(context, MY_PERMISSION, myCertHash(context))) {Toast.makeText(context, "The in-house signature permission is not declared by in-house application.",Toast.LENGTH_LONG).show();return;}// *** POINT 7 *** Handle the received intent carefully and securely,// even though the Broadcast was sent from an in-house application..// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."if (MY_BROADCAST_INHOUSE.equals(intent.getAction())) {String param = intent.getStringExtra("PARAM");Toast.makeText(context,String.format("%s:¥nReceived param: ¥"%s¥"", getName(), param),Toast.LENGTH_SHORT).show();}// *** POINT 8 *** Sensitive information can be returned since the requesting application is inhouse.setResultCode(Activity.RESULT_OK);setResultData(String.format("Sensitive Info from %s", getName()));abortBroadcast();}
}

静态广播接收器定义在AndroidManifest.xml中。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.broadcast.inhousereceiver" ><!-- *** POINT 1 *** Define an in-house signature permission to receive Broadcasts --><permission
    android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION"android:protectionLevel="signature" /><!-- *** POINT 2 *** Declare to use the in-house signature permission to receive results. --><uses-permission
    android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION" /><application
        android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:allowBackup="false" ><!-- *** POINT 3 *** Explicitly set the exported attribute to true. --><!-- *** POINT 4 *** Require the in-house signature permission by the Static Broadcast Receiverdefinition. --><receiver
            android:name=".InhouseReceiver"android:permission="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION"android:exported="true"><intent-filter><action android:name="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE" /></intent-filter></receiver><service
        android:name=".DynamicReceiverService"android:exported="false" /><activity
            android:name=".InhouseReceiverActivity"android:label="@string/app_name"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>

在动态广播接收器中,通过调用程序中的registerReceiver()unregisterReceiver()来执行注册/注销。 为了通过按钮操作执行注册/注销,该按钮PublicReceiverActivity中定义。 由于动态广播接收器实例的作用域比PublicReceiverActivity长,因此不能将其保存为PublicReceiverActivity的成员变量。 在这种情况下,请将动态广播接收器实例保存为DynamicReceiverService的成员变量,然后从PublicReceiverActivity启动/结束DynamicReceiverService,来间接注册/注销动态广播接收器。

InhouseReceiverActivity.java

package org.jssec.android.broadcast.inhousereceiver;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;public class InhouseReceiverActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);}public void onRegisterReceiverClick(View view) {Intent intent = new Intent(this, DynamicReceiverService.class);startService(intent);}public void onUnregisterReceiverClick(View view) {Intent intent = new Intent(this, DynamicReceiverService.class);stopService(intent);}
}

DynamicReceiverService.java

package org.jssec.android.broadcast.inhousereceiver;import android.app.Service;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.widget.Toast;public class DynamicReceiverService extends Service {private static final String MY_BROADCAST_INHOUSE ="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";private InhouseReceiver mReceiver;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();mReceiver = new InhouseReceiver();mReceiver.isDynamic = true;IntentFilter filter = new IntentFilter();filter.addAction(MY_BROADCAST_INHOUSE);filter.setPriority(1); // Prioritize Dynamic Broadcast Receiver, rather than Static Broadcast Receiver.// *** POINT 5 *** When registering a dynamic broadcast receiver, require the in-house signature permission.registerReceiver(mReceiver, filter, "org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION", null);Toast.makeText(this,"Registered Dynamic Broadcast Receiver.",Toast.LENGTH_SHORT).show();}@Overridepublic void onDestroy() {super.onDestroy();unregisterReceiver(mReceiver);mReceiver = null;Toast.makeText(this,"Unregistered Dynamic Broadcast Receiver.",Toast.LENGTH_SHORT).show();}
}

SigPerm.java

package org.jssec.android.shared;import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;public class SigPerm {public static boolean test(Context ctx, String sigPermName, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, sigPermName));}public static String hash(Context ctx, String sigPermName) {if (sigPermName == null) return null;try {// Get the package name of the application which declares a permission named sigPermName.PackageManager pm = ctx.getPackageManager();PermissionInfo pi;pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);String pkgname = pi.packageName;// Fail if the permission named sigPermName is not a Signature Permissionif (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;// Return the certificate hash value of the application which declares a permission named sigPermName.return PkgCert.hash(ctx, pkgname);} catch (NameNotFoundException e) {return null;}}
}

PkgCert.java

package org.jssec.android.shared;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;public class PkgCert {public static boolean test(Context ctx, String pkgname, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, pkgname));}public static String hash(Context ctx, String pkgname) {if (pkgname == null) return null;try {PackageManager pm = ctx.getPackageManager();PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.Signature sig = pkginfo.signatures[0];byte[] cert = sig.toByteArray();byte[] sha256 = computeSha256(cert);return byte2hex(sha256);} catch (NameNotFoundException e) {return null;}}private static byte[] computeSha256(byte[] data) {try {return MessageDigest.getInstance("SHA-256").digest(data);} catch (NoSuchAlgorithmException e) {return null;}}private static String byte2hex(byte[] data) {if (data == null) return null;final StringBuilder hexadecimal = new StringBuilder();for (final byte b : data) {hexadecimal.append(String.format("%02X", b));}return hexadecimal.toString();}
}

导出 APK 时,使用与发送应用相同的开发人员密钥对 APK 进行签名。

下面,展示了用于向内部广播接收器发送广播的示例代码。

要点(发送广播):

10) 定义内部签名权限来接收结果。

11) 声明使用内部签名权限来接收广播。

12) 确认内部签名权限是由内部应用定义的。

13) 由于请求应用是内部应用,因此可以返回敏感信息。

14) 需要接收器的内部签名权限。

15) 小心并安全地处理收到的结果数据。

16) 导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="org.jssec.android.broadcast.inhousesender" ><uses-permission android:name="android.permission.BROADCAST_STICKY"/><!-- *** POINT 10 *** Define an in-house signature permission to receive results. --><permission
    android:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION"android:protectionLevel="signature" /><!-- *** POINT 11 *** Declare to use the in-house signature permission to receive Broadcasts. --><uses-permission
    android:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION" /><application
        android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:allowBackup="false" ><activity
            android:name="org.jssec.android.broadcast.inhousesender.InhouseSenderActivity"android:label="@string/app_name"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application>
</manifest>

InhouseSenderActivity.java

package org.jssec.android.broadcast.inhousesender;import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;public class InhouseSenderActivity extends Activity {// In-house Signature Permissionprivate static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousesender.MY_PERMISSION";// In-house certificate hash valueprivate static String sMyCertHash = null;private static String myCertHash(Context context) {if (sMyCertHash == null) {if (Utils.isDebuggable(context)) {// Certificate hash value of "androiddebugkey" in the debug.keystore.sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";} else {// Certificate hash value of "my company key" in the keystore.sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";}}return sMyCertHash;}private static final String MY_BROADCAST_INHOUSE ="org.jssec.android.broadcast.MY_BROADCAST_INHOUSE";public void onSendNormalClick(View view) {// *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",Toast.LENGTH_LONG).show();return;}// *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.Intent intent = new Intent(MY_BROADCAST_INHOUSE);intent.putExtra("PARAM", "Sensitive Info from Sender");// *** POINT 14 *** Require the in-house signature permission to limit receivers.sendBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION");}public void onSendOrderedClick(View view) {// *** POINT 12 *** Verify that the in-house signature permission is defined by an in-house application.if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) {Toast.makeText(this, "The in-house signature permission is not declared by in-house application.",Toast.LENGTH_LONG).show();return;}// *** POINT 13 *** Sensitive information can be returned since the requesting application is in-house.Intent intent = new Intent(MY_BROADCAST_INHOUSE);intent.putExtra("PARAM", "Sensitive Info from Sender");// *** POINT 14 *** Require the in-house signature permission to limit receivers.sendOrderedBroadcast(intent, "org.jssec.android.broadcast.inhousesender.MY_PERMISSION",mResultReceiver, null, 0, null, null);}private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// *** POINT 15 *** Handle the received result data carefully and securely,// even though the data came from an in-house application.// Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."String data = getResultData();InhouseSenderActivity.this.logLine(String.format("Received result: ¥"%s¥"", data));}};private TextView mLogView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mLogView = (TextView)findViewById(R.id.logview);}private void logLine(String line) {mLogView.append(line);mLogView.append("¥n");}
}

SigPerm.java

package org.jssec.android.shared;import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;public class SigPerm {public static boolean test(Context ctx, String sigPermName, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, sigPermName));}public static String hash(Context ctx, String sigPermName) {if (sigPermName == null) return null;try {// Get the package name of the application which declares a permission named sigPermName.PackageManager pm = ctx.getPackageManager();PermissionInfo pi;pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA);String pkgname = pi.packageName;// Fail if the permission named sigPermName is not a Signature Permissionif (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null;// Return the certificate hash value of the application which declares a permission named sigPermName.return PkgCert.hash(ctx, pkgname);} catch (NameNotFoundException e) {return null;}}
}

PkgCert.java

package org.jssec.android.shared;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;public class PkgCert {public static boolean test(Context ctx, String pkgname, String correctHash) {if (correctHash == null) return false;correctHash = correctHash.replaceAll(" ", "");return correctHash.equals(hash(ctx, pkgname));}public static String hash(Context ctx, String pkgname) {if (pkgname == null) return null;try {PackageManager pm = ctx.getPackageManager();PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures.Signature sig = pkginfo.signatures[0];byte[] cert = sig.toByteArray();byte[] sha256 = computeSha256(cert);return byte2hex(sha256);} catch (NameNotFoundException e) {return null;}}private static byte[] computeSha256(byte[] data) {try {return MessageDigest.getInstance("SHA-256").digest(data);} catch (NoSuchAlgorithmException e) {return null;}}private static String byte2hex(byte[] data) {if (data == null) return null;final StringBuilder hexadecimal = new StringBuilder();for (final byte b : data) {hexadecimal.append(String.format("%02X", b));}return hexadecimal.toString();}
}

导出 APK 时,使用与发送应用相同的开发人员密钥对 APK 进行签名。

安卓应用安全指南 4.2.1 创建/使用广播接收器 示例代码相关推荐

  1. 安卓应用安全指南 4.2.3 创建/使用广播接收器 高级话题

    4.2.3 创建/使用广播接收器 高级话题 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC ...

  2. 安卓应用安全指南 4.2.2 创建/使用广播接收器 规则书

    4.2.2 创建/使用广播接收器 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC- ...

  3. 安卓应用安全指南 5.4.1 通过 HTTPS 的通信 示例代码

    5.4.1 通过 HTTPS 的通信 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY- ...

  4. 安卓应用安全指南 4.4.3 创建/使用服务高级话题

    安卓应用安全指南 4.4.3 创建/使用服务高级话题 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC ...

  5. 安卓应用安全指南 4.4.2 创建/使用服务 规则书

    安卓应用安全指南 4.4.2 创建/使用服务 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC ...

  6. 安卓应用安全指南 4.1.1 创建/使用活动 示例代码

    4.1.1 创建/使用活动 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA ...

  7. 安卓应用安全指南 4.1.3 创建/使用活动 高级话题

    4.1.3 创建/使用活动 高级话题 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA ...

  8. 安卓应用安全指南 4.1.2 创建/使用活动 规则书

    4.1.2 创建/使用活动 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA ...

  9. 安卓应用安全指南 4.4.1 创建/使用服务 示例代码

    4.4.1 创建/使用服务 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA ...

最新文章

  1. AJAX学习基础:简单介绍数据岛使用方法
  2. 浅谈RNN,LSTM和GRU
  3. 【原】HTML页面元素加载顺序研究报告(2)----背景图片
  4. 快讯|工业大数据产业发展联盟成立,助力我国产业生态集聚
  5. Java 10 – JEP 286:局部变量类型推断
  6. leetcode111. 二叉树的最小深度
  7. go依赖管理-govendor
  8. 部署Django工程
  9. python︱mysql数据库连接——pyodbc
  10. 素数就是不能再进行等分的数。比如2,3,5,7,11,等 9=3*3说明它可以等分,因而不是素数 我们国家在1949年建国,如果只给你 1 9 4 9 这4个数字卡片, 可以随意摆放他们的先后顺序(但
  11. 外贸术语 交货条件(zt)
  12. 读《富爸爸,穷爸爸》后感(四)
  13. 心灵捕手:勒索软件是如何运用心理战术达到攻击目的?
  14. 北德克萨斯大学计算机系,美国北德克萨斯大学计算机科学与工程系主任Barrett Bryant访问昆明理工大学...
  15. 定制化件T恤其实很简单,您需要了解的有以下几点
  16. SAP CO生产订单的标准成本、计划成本、实际成本和目标成本
  17. 小程序学习笔记5---导航跳转
  18. 对脚手架的使用的几点建议
  19. java 获取回车字符_java回车键的字符
  20. C游戏编程入门第一讲心得

热门文章

  1. mysql结果集键值对_键值对集合DictionaryK,V根据索引提取数据
  2. 蓝桥杯单片机:11届决赛
  3. 12009.IMU惯导传感器
  4. 12002.i2ctools工具
  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动
  6. 【AD】Altium designer pcb 交叉选择模式
  7. php文章远程图片,php实现异步将远程链接上内容(图片或内容)写到本地的方法
  8. 远程分支显示不全 idea_IDEA中的Git操作,看完你就会了
  9. java复习系列[4] - Java IO
  10. 《Reids 设计与实现》第十四章 集群(上)