有什么方法可以解决 LVL 和 AESObfuscator 糟糕的 SecretKeyFactory 性能问题吗?

发布于 2024-09-24 23:14:11 字数 2582 浏览 2 评论 0原文

我希望在 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 更简单的东西来滚动我自己的混淆器来缓存我自己的许可证响应?

I'm looking to use the new licensing (LVL) stuff with Android Marketplace, but I'm running into a performance problem with the stock AESObfuscator. Specifically, the constructor takes several seconds to run on a device (pure agony on emulator). Since this code needs to run to even check for cached license responses, it puts a serious damper on checking the license at startup.

Running the LVL sample app, here's my barbarian-style profiling of AESObfuscator's constructor:

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

The output on a 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 seconds of thrashing (about 20 in emulator, ugh). I can spin it off on an AsyncTask, but it doesn't do much good there, since the app can't really run until I've validated the license. All I get is a nice, pretty seven seconds of progress bar while the user waits for me to check the license.

Any ideas? Roll my own obfuscator with something simpler than AES to cache my own license responses?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

没有心的人 2024-10-01 23:14:11

经过广泛的搜索和修改,我最好的解决方法似乎是自己创建 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");
        }
   }

After extensive searching and tinkering, my best workaround seems to be to create the AES key on my own, rather than using the PKCS#5 code in PBEKeySpec. I am somewhat amazed that other people have not posted this problem.

The workaround method is to combine a bunch of identifying data (device id, IMEI, package name, etc) into a string. I then take the SHA-1 hash of that string to get 20 bytes of the 24-byte AES key. Admittedly, there's not as much entropy as PKCS#5 and 4 bytes of the key are known. But, really, who is going to mount a crypto attack? It's still pretty sound and there are much weaker attack points in the LVL, despite my other attempts at hardening it.

Since even creating the AES cipher seems to be an expensive (2 secs on emulator) operation, I also defer creation of the encryptor and decryptor members until they are needed by calls to obfuscate and deobfuscate. When the app is using a cached license response, it does not need an encryptor; this cuts quite a bit of cycle out of the most common startup mode.

My new constructor is below. If anyone wants the whole source file, just drop me a line.

   /**
    * @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");
        }
   }
与之呼应 2024-10-01 23:14:11

我也对其进行了优化,但将其全部保留在一个类中。我已经将密码设置为静态,因此它们只需创建一次,然后使用 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);
        }
    }
}

I've also optimized it, but kept it all in one class. I've made the Cipher's static so they only have to created once and then changed the keygen algorithm it 128bit with MD5 instead of SHA1. LicenseCheckerCallback now fires within a half a second instead of the 3 second wait before.

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);
        }
    }
}
逆光下的微笑 2024-10-01 23:14:11

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

Rather than re-writing the obfuscator, it makes more sense to run it in another thread. Sure, crackers can use your app in the meantime, but so what? 3 seconds is not enough time for them to do anything useful, but it is a looong time for legitimate users to wait for license approval.

面如桃花 2024-10-01 23:14:11

我遇到了同样的问题。

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

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

I ran into to the same issue.

What I did was to put the license initialization into a asynctask with the lowest thread priority that is possible by using:

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

in the method

doInBackground

But when showing the license not valid dialog this will then be done in the GUI thread.

So my license check looks like:

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
    }
}
}

The LicenseHandler implementation looks like

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

     ...

}

But remember: if your LicenseCheckerCallback does show any GUI element, then you have to execute that methods by using

context.runOnUIThread(action);
黑白记忆 2024-10-01 23:14:11

好的,这有效

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

Ok this works

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);
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文