4.2.1.3 内部广播接收器
内部广播接收器是广播接收器,它将永远不会收到从内部应用以外发送的任何广播。 它由几个内部应用组成,用于保护内部应用处理的信息或功能。
要点(接收广播):
定义内部签名权限来接收广播。
声明使用内部签名权限来接收结果。
将导出属性显式设置为
true。需要静态广播接收器定义的内部签名权限。
需要内部签名来注册动态广播接收器。
确认内部签名权限是由内部应用定义的。
尽管广播是从内部应用发送的,但要小心并安全地处理接收到的意图。
由于请求应用是内部的,因此可以返回敏感信息。
导出 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 --><permissionandroid: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-permissionandroid:name="org.jssec.android.broadcast.inhousesender.MY_PERMISSION" /><applicationandroid: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. --><receiverandroid: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><serviceandroid:name=".DynamicReceiverService"android:exported="false" /><activityandroid: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 进行签名。

下面,展示了用于向内部广播接收器发送广播的示例代码。
要点(发送广播):
定义内部签名权限来接收结果。
声明使用内部签名权限来接收广播。
确认内部签名权限是由内部应用定义的。
由于请求应用是内部应用,因此可以返回敏感信息。
需要接收器的内部签名权限。
小心并安全地处理收到的结果数据。
导出 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. --><permissionandroid: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-permissionandroid:name="org.jssec.android.broadcast.inhousereceiver.MY_PERMISSION" /><applicationandroid:icon="@drawable/ic_launcher"android:label="@string/app_name"android:allowBackup="false" ><activityandroid: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 进行签名。

