9

我希望在 Android Marketplace 中使用新的许可 (LVL) 内容,但我遇到了库存 AESObfuscator 的性能问题。具体来说,构造函数需要几秒钟才能在设备上运行(模拟器上的纯粹痛苦)。由于此代码甚至需要运行以检查缓存的许可证响应,因此严重阻碍了在启动时检查许可证。

运行 LVL 示例应用程序,这是我对 AESObfuscator 构造函数的野蛮风格分析:

public AESObfuscator(byte[] salt, String applicationId, String deviceId) {
        Log.w("AESObfuscator", "constructor starting");
        try {
            Log.w("AESObfuscator", "1");
            SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM);
            Log.w("AESObfuscator", "2");
            KeySpec keySpec =
                new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256);
            Log.w("AESObfuscator", "3");
            SecretKey tmp = factory.generateSecret(keySpec);
            Log.w("AESObfuscator", "4");
            SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
            Log.w("AESObfuscator", "5");
            mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM);
            Log.w("AESObfuscator", "6");
            mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV));
            Log.w("AESObfuscator", "7");
            mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM);
            Log.w("AESObfuscator", "8");
            mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV));
        } catch (GeneralSecurityException e) {
            // This can't happen on a compatible Android device.
            throw new RuntimeException("Invalid environment", e);
        }
        Log.w("AESObfuscator", "constructor done");
    }

Nexus One 上的输出:

09-28 09:29:02.799: INFO/System.out(12377): debugger has settled (1396)
09-28 09:29:02.988: WARN/AESObfuscator(12377): constructor starting
09-28 09:29:02.988: WARN/AESObfuscator(12377): 1
09-28 09:29:02.999: WARN/AESObfuscator(12377): 2
09-28 09:29:02.999: WARN/AESObfuscator(12377): 3
09-28 09:29:09.369: WARN/AESObfuscator(12377): 4
09-28 09:29:09.369: WARN/AESObfuscator(12377): 5
09-28 09:29:10.389: WARN/AESObfuscator(12377): 6
09-28 09:29:10.398: WARN/AESObfuscator(12377): 7
09-28 09:29:10.398: WARN/AESObfuscator(12377): 8
09-28 09:29:10.409: WARN/AESObfuscator(12377): constructor done
09-28 09:29:10.409: WARN/ActivityManager(83): Launch timeout has expired, giving up wake lock!
09-28 09:29:10.458: INFO/LicenseChecker(12377): Binding to licensing service.

7 秒的颠簸(在模拟器中大约 20 秒,呃)。我可以在 AsyncTask 上拆分它,但它在那里并没有多大用处,因为在我验证许可证之前,该应用程序无法真正运行。当用户等待我检查许可证时,我得到的只是一个漂亮的、漂亮的七秒进度条。

有任何想法吗?使用比 AES 更简单的东西滚动我自己的混淆器来缓存我自己的许可证响应?

4

5 回答 5

6

经过大量搜索和修改,我最好的解决方法似乎是自己创建 AES 密钥,而不是使用 PBEKeySpec 中的 PKCS#5 代码。我有点惊讶其他人没有发布这个问题。

解决方法是将一堆识别数据(设备ID、IMEI、包名等)组合成一个字符串。然后,我使用该字符串的 SHA-1 哈希来获取 24 字节 AES 密钥的 20 个字节。诚然,没有 PKCS#5 那么多的熵和 4 个字节的密钥是已知的。但是,真的,谁会发起加密攻击呢?尽管我在其他方面尝试强化它,但它仍然很合理,并且 LVL 中的攻击点要弱得多。

由于即使创建 AES 密码似乎也是一项昂贵的操作(在模拟器上需要 2 秒),所以我还将加密器和解密器成员的创建推迟到调用混淆和反混淆之前需要它们。当应用程序使用缓存的许可证响应时,它不需要加密器;这从最常见的启动模式中减少了相当多的周期。

我的新构造函数如下。如果有人想要整个源文件,请给我留言。

   /**
    * @param initialNoise device/app identifier. Use as many sources as possible to
    *    create this unique identifier.
    */
   public PixieObfuscator(String initialNoise) {
        try {
            // Hash up the initial noise into something smaller:
            MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
            md.update(initialNoise.getBytes());
            byte[] hash = md.digest();

            // Allocate a buffer for our actual AES key:
            byte[] aesKEY = new byte[AES_KEY_LENGTH];   

            // Fill it with our lucky byte to take up whatever slack is not filled by hash:
            Arrays.fill(aesKEY, LUCKY_BYTE);

            // Copy in as much of the hash as we got (should be twenty bytes, take as much as we can):
            for (int i = 0; i < hash.length && i < aesKEY.length; i++)
                aesKEY[i] = hash[i];

            // Now make the damn AES key object:
              secret = new SecretKeySpec(aesKEY, "AES");
        }
        catch (GeneralSecurityException ex) {
            throw new RuntimeException("Exception in PixieObfuscator constructor, invalid environment");
        }
   }
于 2010-09-29T17:03:03.240 回答
3

我也对其进行了优化,但将其全部集中在一个类中。我已将密码设为静态,因此他们只需创建一次,然后使用 MD5 而不是 SHA1 将密钥生成算法更改为 128 位。LicenseCheckerCallback 现在在半秒内触发,而不是之前的 3 秒等待。

public class AESObfuscator implements Obfuscator {

private static final String KEYGEN_ALGORITHM = "PBEWithMD5And128BitAES-CBC-OpenSSL";
// Omitted all other the other unchanged variables

private static Cipher mEncryptor = null;
private static Cipher mDecryptor = null;

public AESObfuscator(byte[] salt, String applicationId, String deviceId) {

    if (null == mEncryptor || null == mDecryptor) {
        try {
            // Anything in here was unchanged
        } catch (GeneralSecurityException e) {
            // This can't happen on a compatible Android device.
            throw new RuntimeException("Invalid environment", e);
        }
    }
}
于 2010-11-12T14:33:31.437 回答
2

与其重写混淆器,不如在另一个线程中运行它更有意义。当然,破解者可以同时使用你的应用程序,但那又怎样?3 秒的时间不足以让他们做任何有用的事情,但对于合法用户来说等待许可证批准的时间却很长。

于 2011-11-11T16:11:41.140 回答
0

我遇到了同样的问题。

我所做的是将许可证初始化放入具有最低线程优先级的异步任务中,这可以通过使用:

android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);

在方法中

doInBackground

但是当显示许可证无效对话框时,这将在 GUI 线程中完成。

所以我的许可证检查看起来像:

public class LicenseHandler {

    private LicenseHandlerTask task;

public LicenseHandler(final Activity context) {
    super();
    task = new LicenseHandlerTask(context);
    task.execute();
}
/**
 * This will run the task with the lowest thread priority because the
 * AESObfuscator is very slow and will have effect on the performance of the
 * app.<br>
 * 
 */
private static class LicenseHandlerDelay extends
        AsyncTask<Void, Void, ImplLicenseHandler> {
    private final Activity context;

    public LicenseHandlerDelay(final Activity context) {
        this.context = context;
    }

    @Override
    protected ImplLicenseHandler doInBackground(final Void... params) {
        // set the lowest priority available for this task
          android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);

        ImplLicenseHandler checker = new ImplLicenseHandler(context);
        return checker;
    }

    @Override
    protected void onPostExecute(final ImplLicenseHandler result) {
                    checker.check();
    }

}

/**
 * cancels the background task for checking the license if it is running
 */
public void destroy() {
    try {
        if (null != task) {
            task.cancel(true);
            task = null;
        }
    } catch (Throwable e) {
        // regardless of errors
    }
}
}

LicenseHandler 实现看起来像

public class ImplLicenseHandler {

    ...

    private Context mContext = null;
    private AndroidPitLicenseChecker mChecker = null;
    private LicenseCheckerCallback mLicenseCheckerCallback = null;

    public ImplLicencseHandler(Context context){
            this.mContext = context;
            final ServerManagedPolicy googleLicensePolicy = new LicensePolicy(
            mContext, new AESObfuscator(ImplLicenseHandler.SALT,mContext.getPackageName(), ImplLicenseHandler.DEVICE_ID));
            mChecker = new AndroidPitLicenseChecker(mContext,
            mContext.getPackageName(),
            ImplLicenseHandler.ANDROIDPIT_PUBLIC_KEY, googleLicensePolicy,
            ImplLicenseHandler.GOOGLE_LICENSE_PUBLIC_KEY);
            mLicenseCheckerCallback = new LicenseCheckerCallback();
     }

     public void check(){
            mContext.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                         mChecker.checkAccess(mLicenseCheckerCallback);
                    }
            });
     }

     ...

}

但请记住:如果您的 LicenseCheckerCallback 确实显示了任何 GUI 元素,那么您必须使用

context.runOnUIThread(action);
于 2014-03-09T16:32:52.080 回答
-3

好的,这行得通

public class AESObfuscator implements Obfuscator {

private static final String KEYGEN_ALGORITHM = "PBEWithMD5And128BitAES-CBC-OpenSSL";
// Omitted all other the other unchanged variables

private static Cipher mEncryptor = null;
private static Cipher mDecryptor = null;

public AESObfuscator(byte[] salt, String applicationId, String deviceId) {

    if (null == mEncryptor || null == mDecryptor) {
        try {
            // Anything in here was unchanged
        } catch (GeneralSecurityException e) {
            // This can't happen on a compatible Android device.
            throw new RuntimeException("Invalid environment", e);
        }
    }
}
于 2011-04-13T01:35:13.893 回答