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

4.4.1 创建/使用服务 示例代码

原书:Android Application Secure Design/Secure Coding Guidebook

译者:飞龙

协议:CC BY-NC-SA 4.0

使用服务的风险和对策取决于服务的使用方式。 您可以通过下面展示的图表找出您应该创建的服务类型。 由于安全编码的最佳实践,根据服务的创建方式而有所不同,因此我们也将解释服务的实现。

表 4.4-1 服务类型的定义

类型定义
私有不能由其他应用加载,所以是最安全的服务
公共应该由很多未指定的应用使用的服务
伙伴只能由可信的伙伴公司开发的应用使用的服务
内部只能由其他内部应用使用的服务

有几种服务实现方法,您将选择匹配您想要创建的服务类型的方法。 表中列的条目展示了实现方法,并将它们分为 5 种类型。 “OK”表示可能的组合,其他表示不可能/困难的组合。

服务的详细实现方法,请参阅“4.4.3.2 如何实现服务”和每个服务类型的示例代码(在表中带有*标记)。

表 4.4-2

类别私有服务公共服务伙伴服务内部服务
startService类型OK*OK-OK
IntentService类型OKOK*-OK
本地绑定类型OK---
Messenger绑定类型OKOK-OK*
AIDL 绑定类型OKOKOK*OK

每种服务安全类型的示例代码展示在下面,通过表 4.4-2 中的使用*标记。

4.4.1.1 创建/使用私有服务

私有服务是不能由其他应用启动的服务,因此它是最安全的服务。 当使用仅在应用中使用的私有服务时,只要您对该类使用显式意图,那么您就不必担心意外将它发送到任何其他应用。

下面展示了如何使用startService类型服务的示例代码。

要点(创建服务):

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.service.privateservice" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <activity
            android:name=".PrivateUserActivity"
            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>
        <!-- Private Service derived from Service class -->
        <!-- *** POINT 1 *** Explicitly set the exported attribute to false. -->
        <service android:name=".PrivateStartService" android:exported="false"/>
        <!-- Private Service derived from IntentService class -->
        <!-- *** POINT 1 *** Explicitly set the exported attribute to false. -->
        <service android:name=".PrivateIntentService" android:exported="false"/>
    </application>
</manifest>

PrivateStartService.java

package org.jssec.android.service.privateservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;

public class PrivateStartService extends Service {

    // The onCreate gets called only one time when the service starts.
    @Override
    public void onCreate() {
        Toast.makeText(this, "PrivateStartService - onCreate()", Toast.LENGTH_SHORT).show();
    }

    // The onStartCommand gets called each time after the startService gets called.
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // *** POINT 2 *** Handle the received intent carefully and securely,
        // even though the intent was sent from 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(this,
        String.format("PrivateStartService¥nReceived param: ¥"%s¥"", param),
        Toast.LENGTH_LONG).show();
        return Service.START_NOT_STICKY;
    }

    // The onDestroy gets called only one time when the service stops.
    @Override
    public void onDestroy() {
        Toast.makeText(this, "PrivateStartService - onDestroy()", Toast.LENGTH_SHORT).show();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // This service does not provide binding, so return null
        return null;
    }
}

下面是使用私有服务的活动代码:

要点(使用服务):

4) 使用指定类的显式意图,调用同一应用程序的服务。

5) 由于目标服务位于同一应用中,因此可以发送敏感信息。

6) 即使数据来自同一应用中的服务,也要小心并安全地处理收到的结果数据。

PrivateUserActivity.java

package org.jssec.android.service.privateservice;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class PrivateUserActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.privateservice_activity);
    }

    // --- StartService control ---
    public void onStartServiceClick(View v) {
        // *** POINT 4 *** Use the explicit intent with class specified to call a service in the same application.
        Intent intent = new Intent(this, PrivateStartService.class);
        // *** POINT 5 *** Sensitive information can be sent since the destination service is in the same application.
        intent.putExtra("PARAM", "Sensitive information");
        startService(intent);
    }

    public void onStopServiceClick(View v) {
        doStopService();
    }

    @Override
    public void onStop() {
        super.onStop();
        // Stop service if the service is running.
        doStopService();
    }

    private void doStopService() {
        // *** POINT 4 *** Use the explicit intent with class specified to call a service in the same application.
        Intent intent = new Intent(this, PrivateStartService.class);
        stopService(intent);
    }

    // --- IntentService control ---
    public void onIntentServiceClick(View v) {
        // *** POINT 4 *** Use the explicit intent with class specified to call a service in the same application.
        Intent intent = new Intent(this, PrivateIntentService.class);
        // *** POINT 5 *** Sensitive information can be sent since the destination service is in the same application.
        intent.putExtra("PARAM", "Sensitive information");
        startService(intent);
    }
}

4.4.1.2 创建/使用公共服务

公共服务是应该由未指定的大量应用使用的服务。 有必要注意,它可能会收到恶意软件发送的信息(意图等)。 在使用公共服务的情况下,有必要注意,恶意软件可能会收到要发送的信息(意图等)。

下面展示了如何使用startService类型服务的示例代码。

要点(创建服务):

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

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.service.publicservice" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <!-- Most standard Service -->
        <!-- *** POINT 1 *** Explicitly set the exported attribute to true. -->
        <service android:name=".PublicStartService" android:exported="true">
            <intent-filter>
                <action android:name="org.jssec.android.service.publicservice.action.startservice" />
            </intent-filter>
        </service>
        <!-- Public Service derived from IntentService class -->
        <!-- *** POINT 1 *** Explicitly set the exported attribute to true. -->
        <service android:name=".PublicIntentService" android:exported="true">
            <intent-filter>
                <action android:name="org.jssec.android.service.publicservice.action.intentservice" />
            </intent-filter>
        </service>
    </application>
</manifest>

PublicIntentService.java

package org.jssec.android.service.publicservice;

import android.app.IntentService;
import android.content.Intent;
import android.widget.Toast;

public class PublicIntentService extends IntentService{

    /**
    * Default constructor must be provided when a service extends IntentService class.
    * If it does not exist, an error occurs.
    */
    public PublicIntentService() {
        super("CreatingTypeBService");
    }

    // The onCreate gets called only one time when the Service starts.
    @Override
    public void onCreate() {
        super.onCreate();
        Toast.makeText(this, this.getClass().getSimpleName() + " - onCreate()", Toast.LENGTH_SHORT).show();
    }

    // The onHandleIntent gets called each time after the startService gets called.
    @Override
    protected void onHandleIntent(Intent intent) {
        // *** POINT 2 *** Handle intent carefully and securely.
        // Since it's public service, the intent may come from malicious 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(this, String.format("Recieved parameter ¥"%s¥"", param), Toast.LENGTH_LONG).show();
    }

    // The onDestroy gets called only one time when the service stops.
    @Override
    public void onDestroy() {
        Toast.makeText(this, this.getClass().getSimpleName() + " - onDestroy()", Toast.LENGTH_SHORT).show();
    }
}

下面是使用公共服务的活动代码:

要点(使用服务):

4) 不要发送敏感信息。

5) 收到结果时,小心并安全地处理结果数据。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.service.publicserviceuser" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <activity
            android:name=".PublicUserActivity"
            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>

PublicUserActivity.java


package org.jssec.android.service.publicserviceuser;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class PublicUserActivity extends Activity {

    // Using Service Info
    private static final String TARGET_PACKAGE = "org.jssec.android.service.publicservice";
    private static final String TARGET_START_CLASS = "org.jssec.android.service.publicservice.PublicStartService";
    private static final String TARGET_INTENT_CLASS = "org.jssec.android.service.publicservice.PublicIntentService";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.publicservice_activity);
    }

    // --- StartService control ---
    public void onStartServiceClick(View v) {
        Intent intent = new Intent("org.jssec.android.service.publicservice.action.startservice");
        // *** POINT 4 *** Call service by Explicit Intent
        intent.setClassName(TARGET_PACKAGE, TARGET_START_CLASS);
        // *** POINT 5 *** Do not send sensitive information.
        intent.putExtra("PARAM", "Not sensitive information");
        startService(intent);
        // *** POINT 6 *** When receiving a result, handle the result data carefully and securely.
        // This sample code uses startService(), so receiving no result.
    }

    public void onStopServiceClick(View v) {
        doStopService();
    }

    // --- IntentService control ---
    public void onIntentServiceClick(View v) {
        Intent intent = new Intent("org.jssec.android.service.publicservice.action.intentservice");
        // *** POINT 4 *** Call service by Explicit Intent
        intent.setClassName(TARGET_PACKAGE, TARGET_INTENT_CLASS);
        // *** POINT 5 *** Do not send sensitive information.
        intent.putExtra("PARAM", "Not sensitive information");
        startService(intent);
    }

    @Override
    public void onStop(){
        super.onStop();
        // Stop service if the service is running.
        doStopService();
    }

    // Stop service
    private void doStopService() {
        Intent intent = new Intent("org.jssec.android.service.publicservice.action.startservice");
        // *** POINT 4 *** Call service by Explicit Intent
        intent.setClassName(TARGET_PACKAGE, TARGET_START_CLASS);
        stopService(intent);
    }
}

4.4.1.3 创建/使用伙伴服务

伙伴服务是只能由特定应用使用的服务。 系统由伙伴公司的应用和内部应用组成,用于保护在伙伴应用和内部应用之间处理的信息和功能。

以下是 AIDL 绑定类型服务的示例。

要点(创建服务):

1) 不要定义意图过滤器,并将导出属性显式设置为true

2) 验证请求应用的证书是否已在自己的白名单中注册。

3) 请勿(无法)通过onBind(onStartCommand, onHandleIntent)识别请求应用是否为伙伴。

4) 小心并安全地处理接收到的意图,即使意图是从伙伴应用发送的。

5) 仅返回公开给伙伴应用的信息。

另外,请参阅“5.2.1.3 如何验证应用证书的哈希值”,来了解如何验证目标应用的哈希值,它在白名单中指定。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.service.partnerservice.aidl" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <!-- Service using AIDL -->
        <!-- *** POINT 1 *** Do not define the intent filter and explicitly set the exported attribute to
        true. -->
        <service
        android:name="org.jssec.android.service.partnerservice.aidl.PartnerAIDLService"
        android:exported="true" />
    </application>
</manifest>

在这个例子中,将创建 2 个 AIDL 文件。 一个是回调接口,将数据从服务提供给活动。 另一个接口将数据从活动提供给服务,并获取信息。 另外,AIDL 文件中描述的包名称,应与 AIDL 文件的目录层次一致,与java文件中描述的包名称相同。

IExclusiveAIDLServiceCallback.aidl

package org.jssec.android.service.exclusiveservice.aidl;

interface IExclusiveAIDLServiceCallback {

    /**
    * It's called when the value is changed.
    */
    void valueChanged(String info);
}

IExclusiveAIDLService.aidl

package org.jssec.android.service.exclusiveservice.aidl;

import org.jssec.android.service.exclusiveservice.aidl.IExclusiveAIDLServiceCallback;

interface IExclusiveAIDLService {

    /**
    * Register Callback.
    */
    void registerCallback(IExclusiveAIDLServiceCallback cb);

    /**
    * Get Information
    */
    String getInfo(String param);

    /**
    * Unregister Callback
    */
    void unregisterCallback(IExclusiveAIDLServiceCallback cb);
}

PartnerAIDLService.java

package org.jssec.android.service.partnerservice.aidl;

import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.widget.Toast;

public class PartnerAIDLService extends Service {

    private static final int REPORT_MSG = 1;
    private static final int GETINFO_MSG = 2;
    // The value which this service informs to client
    private int mValue = 0;
    // *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the own white list.
    private static PkgCertWhitelists sWhitelists = null;

    private static void buildWhitelists(Context context) {
        boolean isdebug = Utils.isDebuggable(context);
        sWhitelists = new PkgCertWhitelists();
        // Register certificate hash value of partner application "org.jssec.android.service.partnerservice.aidluser"
        sWhitelists.add("org.jssec.android.service.partnerservice.aidluser", isdebug ?
        // Certificate hash value of debug.keystore "androiddebugkey"
            "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" :
            // Certificate hash value of keystore "partner key"
            "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A");
            // Register other partner applications in the same way
    }

    private static boolean checkPartner(Context context, String pkgname) {
        if (sWhitelists == null) buildWhitelists(context);
        return sWhitelists.test(context, pkgname);
    }

    // Object to register callback
    // Methods which RemoteCallbackList provides are thread-safe.
    private final RemoteCallbackList<IPartnerAIDLServiceCallback> mCallbacks =
        new RemoteCallbackList<IPartnerAIDLServiceCallback>();

    // Handler to send data when callback is called.
    private static class ServiceHandler extends Handler{
    private Context mContext;
    private RemoteCallbackList<IPartnerAIDLServiceCallback> mCallbacks;
    private int mValue = 0;

    public ServiceHandler(Context context, RemoteCallbackList<IPartnerAIDLServiceCallback> callback, int value){
        this.mContext = context;
        this.mCallbacks = callback;
        this.mValue = value;
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case REPORT_MSG: {
                if(mCallbacks == null){
                    return;
                }
                // Start broadcast
                // To call back on to the registered clients, use beginBroadcast().
                // beginBroadcast() makes a copy of the currently registered callback list.
                final int N = mCallbacks.beginBroadcast();
                for (int i = 0; i < N; i++) {
                    IPartnerAIDLServiceCallback target = mCallbacks.getBroadcastItem(i);
                    try {
                        // *** POINT 5 *** Information that is granted to disclose to partner applications can be returned.
                        target.valueChanged("Information disclosed to partner application (callback from Service) No." + (++mValue));
                    } catch (RemoteException e) {
                        // Callbacks are managed by RemoteCallbackList, do not unregister callbacks here.
                        // RemoteCallbackList.kill() unregister all callbacks
                    }
                }
                // finishBroadcast() cleans up the state of a broadcast previously initiated by calling beginBroadcast().
                mCallbacks.finishBroadcast();
                // Repeat after 10 seconds
                sendEmptyMessageDelayed(REPORT_MSG, 10000);
                break;
            }
            case GETINFO_MSG: {
                if(mContext != null) {
                    Toast.makeText(mContext,
                        (String) msg.obj, Toast.LENGTH_LONG).show();
                }
                break;
            }
            default:
                super.handleMessage(msg);
                break;
        } // switch
    }

    protected final ServiceHandler mHandler = new ServiceHandler(this, mCallbacks, mValue);

    // Interfaces defined in AIDL
    private final IPartnerAIDLService.Stub mBinder = new IPartnerAIDLService.Stub() {
        private boolean checkPartner() {
            Context ctx = PartnerAIDLService.this;
            if (!PartnerAIDLService.checkPartner(ctx, Utils.getPackageNameFromUid(ctx, getCallingUid()))) {
                mHandler.post(new Runnable(){
                    @Override
                    public void run(){
                        Toast.makeText(PartnerAIDLService.this, "Requesting application is not partner application.", Toast.LENGTH_LONG).show();
                    }
                });
                return false;
            }
            return true;
        }

        public String getInfo(String param) {
            // *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the own white list.
            if (!checkPartner()) {
                return null;
            }
            // *** POINT 4 *** Handle the received intent carefully and securely,
            // even though the intent was sent from a partner application
            // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
            Message msg = new Message();
            msg.what = GETINFO_MSG;
            msg.obj = String.format("Method calling from partner application. Recieved ¥"%s¥"", param);
            PartnerAIDLService.this.mHandler.sendMessage(msg);
            // *** POINT 5 *** Return only information that is granted to be disclosed to a partner application.
            return "Information disclosed to partner application (method from Service)";
        }

        public void unregisterCallback(IPartnerAIDLServiceCallback cb) {
            // *** POINT 2 *** Verify that the certificate of the requesting application has been registered in the own white list.
            if (!checkPartner()) {
                return;
            }
            if (cb != null) mCallbacks.unregister(cb);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        // *** POINT 3 *** Verify that the certificate of the requesting application has been registered in the own white list.
        // So requesting application must be validated in methods defined in AIDL every time.
        return mBinder;
    }

    @Override
    public void onCreate() {
        Toast.makeText(this, this.getClass().getSimpleName() + " - onCreate()", Toast.LENGTH_SHORT).show();
        // During service is running, inform the incremented number periodically.
        mHandler.sendEmptyMessage(REPORT_MSG);
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, this.getClass().getSimpleName() + " - onDestroy()", Toast.LENGTH_SHORT).show();
        // Unregister all callbacks
        mCallbacks.kill();
        mHandler.removeMessages(REPORT_MSG);
    }
}

PkgCertWhitelists.java

package org.jssec.android.shared;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;

public class PkgCertWhitelists {

    private Map<String, String> mWhitelists = new HashMap<String, String>();

    public boolean add(String pkgname, String sha256) {
        if (pkgname == null) return false;
        if (sha256 == null) return false;
        sha256 = sha256.replaceAll(" ", "");
        if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars
        sha256 = sha256.toUpperCase();
        if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
        mWhitelists.put(pkgname, sha256);
        return true;
    }

    public boolean test(Context ctx, String pkgname) {
        // Get the correct hash value which corresponds to pkgname.
        String correctHash = mWhitelists.get(pkgname);
        // Compare the actual hash value of pkgname with the correct hash value.
        return PkgCert.test(ctx, pkgname, correctHash);
    }
}

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();
    }
}

下面是使用伙伴服务的活动代码:

要点(使用服务):

6) 验证目标应用的证书是否已在自己的白名单中注册。

7) 仅返回公开给伙伴应用的信息。

8) 使用显式意图调用伙伴服务。

9) 即使数据来自伙伴应用,也要小心并安全地处理收到的结果数据。

ExclusiveAIDLUserActivity.java

package org.jssec.android.service.partnerservice.aidluser;

import org.jssec.android.service.partnerservice.aidl.IPartnerAIDLService;
import org.jssec.android.service.partnerservice.aidl.IPartnerAIDLServiceCallback;
import org.jssec.android.shared.PkgCertWhitelists;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.view.View;
import android.widget.Toast;

public class PartnerAIDLUserActivity extends Activity {

    private boolean mIsBound;
    private Context mContext;
    private final static int MGS_VALUE_CHANGED = 1;
    // *** POINT 6 *** Verify if the certificate of the target application has been registered in the own white list.
    private static PkgCertWhitelists sWhitelists = null;

    private static void buildWhitelists(Context context) {
        boolean isdebug = Utils.isDebuggable(context);
        sWhitelists = new PkgCertWhitelists();
        // Register certificate hash value of partner service application "org.jssec.android.service.partnerservice.aidl"
        sWhitelists.add("org.jssec.android.service.partnerservice.aidl", isdebug ?
            // Certificate hash value of debug.keystore "androiddebugkey"
            "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" :
            // Certificate hash value of keystore "my company key"
            "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA");
            // Register other partner service applications in the same way
    }

    private static boolean checkPartner(Context context, String pkgname) {
        if (sWhitelists == null) buildWhitelists(context);
        return sWhitelists.test(context, pkgname);
    }

    // Information about destination (requested) partner activity.
    private static final String TARGET_PACKAGE = "org.jssec.android.service.partnerservice.aidl";
    private static final String TARGET_CLASS = "org.jssec.android.service.partnerservice.aidl.PartnerAIDLService";

    private static class ReceiveHandler extends Handler{
        private Context mContext;
        public ReceiveHandler(Context context){
        this.mContext = context;
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MGS_VALUE_CHANGED: {
                String info = (String)msg.obj;
                Toast.makeText(mContext, String.format("Received ¥"%s¥" with callback.", info), Toast.LENGTH_SHORT).show();
                break;
            }
            default:
                super.handleMessage(msg);
                break;
        } // switch
    }

    private final ReceiveHandler mHandler = new ReceiveHandler(this);

    // Interfaces defined in AIDL. Receive notice from service
    private final IPartnerAIDLServiceCallback.Stub mCallback =
    new IPartnerAIDLServiceCallback.Stub() {
        @Override
        public void valueChanged(String info) throws RemoteException {
            Message msg = mHandler.obtainMessage(MGS_VALUE_CHANGED, info);
            mHandler.sendMessage(msg);
        }
    };

    // Interfaces defined in AIDL. Inform service.
    private IPartnerAIDLService mService = null;
    // Connection used to connect with service. This is necessary when service is implemented with bindService().
    private ServiceConnection mConnection = new ServiceConnection() {
        // This is called when the connection with the service has been established.
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = IPartnerAIDLService.Stub.asInterface(service);
            try{
                // connect to service
                mService.registerCallback(mCallback);
            }catch(RemoteException e){
                // service stopped abnormally
            }
            Toast.makeText(mContext, "Connected to service", Toast.LENGTH_SHORT).show();
        }

        // This is called when the service stopped abnormally and connection is disconnected.
        @Override
        public void onServiceDisconnected(ComponentName className) {
            Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.partnerservice_activity);
        mContext = this;
    }

    // --- StartService control ---
    public void onStartServiceClick(View v) {
        // Start bindService
        doBindService();
    }

    public void onGetInfoClick(View v) {
        getServiceinfo();
    }

    public void onStopServiceClick(View v) {
        doUnbindService();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        doUnbindService();
    }

    /**
    * Connect to service
    */
    private void doBindService() {
        if (!mIsBound){
        // *** POINT 6 *** Verify if the certificate of the target application has been registered in the own white list.
        if (!checkPartner(this, TARGET_PACKAGE)) {
            Toast.makeText(this, "Destination(Requested) sevice application is not registered in white list.", Toast.LENGTH_LONG).show();
            return;
        }
        Intent intent = new Intent();
        // *** POINT 7 *** Return only information that is granted to be disclosed to a partner application.
        intent.putExtra("PARAM", "Information disclosed to partner application");
        // *** POINT 8 *** Use the explicit intent to call a partner service.
        intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
        }
    }

    /**
    * Disconnect service
    */
    private void doUnbindService() {
        if (mIsBound) {
            // Unregister callbacks which have been registered.
            if(mService != null){
                try{
                    mService.unregisterCallback(mCallback);
                }catch(RemoteException e){
                    // Service stopped abnormally
                    // Omitted, since it' s sample.
                }
            }
            unbindService(mConnection);
            Intent intent = new Intent();
            // *** POINT 8 *** Use the explicit intent to call a partner service.
            intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
            stopService(intent);
            mIsBound = false;
        }
    }

    /**
    * Get information from service
    */
    void getServiceinfo() {
        if (mIsBound && mService != null) {
            String info = null;
            try {
                // *** POINT 7 *** Return only information that is granted to be disclosed to a partner application.
                info = mService.getInfo("Information disclosed to partner application (method from activity)");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            // *** POINT 9 *** Handle the received result data carefully and securely,
            // even though the data came from a partner application.
            // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
            Toast.makeText(mContext, String.format("Received ¥"%s¥" from service.", info), Toast.LENGTH_SHORT).show();
        }
    }
}

PkgCertWhitelists.java

package org.jssec.android.shared;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;

public class PkgCertWhitelists {

    private Map<String, String> mWhitelists = new HashMap<String, String>();

    public boolean add(String pkgname, String sha256) {
        if (pkgname == null) return false;
        if (sha256 == null) return false;
        sha256 = sha256.replaceAll(" ", "");
        if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars
        sha256 = sha256.toUpperCase();
        if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char
        mWhitelists.put(pkgname, sha256);
        return true;
    }

    public boolean test(Context ctx, String pkgname) {
        // Get the correct hash value which corresponds to pkgname.
        String correctHash = mWhitelists.get(pkgname);
        // Compare the actual hash value of pkgname with the correct hash value.
        return PkgCert.test(ctx, pkgname, correctHash);
    }
}

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();
    }
}

4.4.1.4 创建/使用内部服务

内部服务是除了内部应用以外的应用禁止使用的服务。 它们用于内部开发的应用,以便安全地共享信息和功能。 以下是使用Messenger绑定类型服务的示例。

要点(创建服务):

1) 定义内部签名权限。

2) 需要内部签名权限。

3) 不要定义意图过滤器,并将导出属性显式设置为true

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

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

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

7) 导出 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.service.inhouseservice.messenger" >
    <!-- *** POINT 1 *** Define an in-house signature permission -->
    <permission
    android:name="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION"
    android:protectionLevel="signature" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <!-- Service using Messenger -->
        <!-- *** POINT 2 *** Require the in-house signature permission -->
        <!-- *** POINT 3 *** Do not define the intent filter and explicitly set the exported attribute to true. -->
        <service
            android:name="org.jssec.android.service.inhouseservice.messenger.InhouseMessengerService"
            android:exported="true"
            android:permission="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION" />
    </application>
</manifest>

InhouseMessengerService.java

package org.jssec.android.service.inhouseservice.messenger;

import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;

public class InhouseMessengerService extends Service{

    // In-house signature permission
    private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION";
    // In-house certificate hash value
    private static String sMyCertHash = null;

    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {
            if (Utils.isDebuggable(context)) {
                // Certificate hash value of debug.keystore "androiddebugkey"
                sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
            } else {
                // Certificate hash value of keystore "my company key"
                sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
            }
        }
        return sMyCertHash;
    }

    // Manage clients(destinations of sending data) in a list
    private ArrayList<Messenger> mClients = new ArrayList<Messenger>();
    // Messenger used when service receive data from client
    private final Messenger mMessenger = new Messenger(new ServiceSideHandler(mClients));

    // Handler which handles message received from client
    private static class ServiceSideHandler extends Handler{
        private ArrayList<Messenger> mClients;

        public ServiceSideHandler(ArrayList<Messenger> clients){
            mClients = clients;
        }

        @Override
        public void handleMessage(Message msg){
            switch(msg.what){
                case CommonValue.MSG_REGISTER_CLIENT:
                    // Add messenger received from client
                    mClients.add(msg.replyTo);
                    break;
                case CommonValue.MSG_UNREGISTER_CLIENT:
                    mClients.remove(msg.replyTo);
                    break;
                case CommonValue.MSG_SET_VALUE:
                    // Send data to client
                    sendMessageToClients(mClients);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    /**
    * Send data to client
    */
    private static void sendMessageToClients(ArrayList<Messenger> mClients){
        // *** POINT 6 *** Sensitive information can be returned since the requesting application is inhouse.
        String sendValue = "Sensitive information (from Service)";
        // Send data to the registered client one by one.
        // Use iterator to send all clients even though clients are removed in the loop process.
        Iterator<Messenger> ite = mClients.iterator();
        while(ite.hasNext()){
            try {
                Message sendMsg = Message.obtain(null, CommonValue.MSG_SET_VALUE, null);
                Bundle data = new Bundle();
                data.putString("key", sendValue);
                sendMsg.setData(data);
                Messenger next = ite.next();
                next.send(sendMsg);
            } catch (RemoteException e) {
                // If client does not exits, remove it from a list.
                ite.remove();
            }
        }
    }

    public IBinder onBind(Intent intent) {
        // *** POINT 4 *** 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, "In-house defined signature permission is not defined by in-house application.", Toast.LENGTH_LONG).show();
            return null;
        }
        // *** POINT 5 *** Handle the received intent carefully and securely,
        // even though the intent was sent from an in-house 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(this, String.format("Received parameter ¥"%s¥".", param), Toast.LENGTH_LONG).show();
        return mMessenger.getBinder();
    }

    @Override
    public void onCreate() {
        Toast.makeText(this, "Service - onCreate()", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service - onDestroy()", 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 Permission
            if (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();
    }
}

要点 7:导出 APK 时,请使用与请求应用相同的开发人员密钥对 APK 进行签名。

下面是使用内部服务的活动代码:

要点(使用服务):

8) 声明使用内部签名权限。

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

10) 验证目标应用是否使用内部证书签名。

11) 由于目标应用是内部的,因此可以发送敏感信息。

12) 使用显式意图调用内部服务。

13) 即使数据来自内部应用,也要小心并安全地处理收到的结果数据。

14) 导出 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.service.inhouseservice.messengeruser" >
    <!-- *** POINT 8 *** Declare to use the in-house signature permission. -->
    <uses-permission
    android:name="org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <activity
            android:name="org.jssec.android.service.inhouseservice.messengeruser.InhouseMessengerUserActivity"
            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>

InhouseMessengerUserActivity.java

package org.jssec.android.service.inhouseservice.messengeruser;

import org.jssec.android.shared.PkgCert;
import org.jssec.android.shared.SigPerm;
import org.jssec.android.shared.Utils;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.widget.Toast;

public class InhouseMessengerUserActivity extends Activity {

    private boolean mIsBound;
    private Context mContext;
    // Destination (Requested) service application information
    private static final String TARGET_PACKAGE = "org.jssec.android.service.inhouseservice.messenger";
    private static final String TARGET_CLASS = "org.jssec.android.service.inhouseservice.messenger.InhouseMessengerService";
    // In-house signature permission
    private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.MY_PERMISSION";
    // In-house certificate hash value
    private static String sMyCertHash = null;

    private static String myCertHash(Context context) {
        if (sMyCertHash == null) {
            if (Utils.isDebuggable(context)) {
                // Certificate hash value of debug.keystore "androiddebugkey"
                sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255";
            } else {
                // Certificate hash value of keystore "my company key"
                sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA";
            }
        }
        return sMyCertHash;
    }

    // Messenger used when this application receives data from service.
    private Messenger mServiceMessenger = null;
    // Messenger used when this application sends data to service.
    private final Messenger mActivityMessenger = new Messenger(new ActivitySideHandler());

    // Handler which handles message received from service
    private class ActivitySideHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CommonValue.MSG_SET_VALUE:
                    Bundle data = msg.getData();
                    String info = data.getString("key");
                    // *** POINT 13 *** 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."
                    Toast.makeText(mContext, String.format("Received ¥"%s¥" from service.", info),
                    Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    }

    // Connection used to connect with service. This is necessary when service is implemented with bindSrvice().
    private ServiceConnection mConnection = new ServiceConnection() {

        // This is called when the connection with the service has been established.
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mServiceMessenger = new Messenger(service);
            Toast.makeText(mContext, "Connect to service", Toast.LENGTH_SHORT).show();
            try {
                // Send own messenger to service
                Message msg = Message.obtain(null, CommonValue.MSG_REGISTER_CLIENT);
                msg.replyTo = mActivityMessenger;
                mServiceMessenger.send(msg);
            } catch (RemoteException e) {
                // Service stopped abnormally
            }
        }

        // This is called when the service stopped abnormally and connection is disconnected.
        @Override
        public void onServiceDisconnected(ComponentName className) {
            mServiceMessenger = null;
            Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.inhouseservice_activity);
        mContext = this;
    }

    // --- StartService control ---
    public void onStartServiceClick(View v) {
        // Start bindService
        doBindService();
    }

    public void onGetInfoClick(View v) {
        getServiceinfo();
    }

    public void onStopServiceClick(View v) {
        doUnbindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        doUnbindService();
    }

    /**
    * Connect to service
    */
    void doBindService() {
        if (!mIsBound){
            // *** POINT 9 *** 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, "In-house defined signature permission is not defined by in-house application.", Toast.LENGTH_LONG).show();
                return;
            }
            // *** POINT 10 *** Verify that the destination application is signed with the in-house certificate.
            if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) {
                Toast.makeText(this, "Destination(Requested) service application is not in-house application.", Toast.LENGTH_LONG).show();
                return;
            }
            Intent intent = new Intent();
            // *** POINT 11 *** Sensitive information can be sent since the destination application is in-house one.
            intent.putExtra("PARAM", "Sensitive information");
            // *** POINT 12 *** Use the explicit intent to call an in-house service.
            intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
        }
    }

    /**
    * Disconnect service
    */
    void doUnbindService() {
        if (mIsBound) {
            unbindService(mConnection);
            mIsBound = false;
        }
    }

    /**
    * Get information from service
    */
    void getServiceinfo() {
        if (mServiceMessenger != null) {
            try {
                // Request sending information
                Message msg = Message.obtain(null, CommonValue.MSG_SET_VALUE);
                mServiceMessenger.send(msg);
            } catch (RemoteException e) {
                // Service stopped abnormally
            }
        }
    }
}

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 Permission
            if (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();
    }
}

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

posted @ 2018-03-22 18:38  绝不原创的飞龙  阅读(20)  评论(0编辑  收藏  举报  来源