2010-09-28 85 views
9

我正在使用Android Marketplace的新授权(LVL)内容,但我遇到了股票AESObfuscator的性能问题。具体来说,构造函数需要几秒钟才能在设备上运行(模拟器上的纯粹痛苦)。由于此代码需要运行以检查缓存的许可证响应,因此在启动时检查许可证会严重阻碍。LVL和AESObfuscator的糟糕的SecretKeyFactory性能的任何方式?

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

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秒:

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的输出,呃)。我可以把它放在AsyncTask上,但它在那里并没有太多好处,因为在我验证许可证之前,应用程序不能真正运行。我所得到的是一个不错的,美妙的七秒进度条,而用户等待我检查许可证。

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

回答

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

我也对它进行了优化,但将它全部保存在一个类中。我已经使密码的静态,所以他们只需要创建一次,然后用MD5而不是SHA1将keygen算法改为128bit。 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); 
     } 
    } 
} 
-2

好这部作品

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

这不就是我上面的答案吗? – 2011-07-10 22:05:42

2

而不是重新编写混淆,它更有意义,在另一个线程中运行它。当然,饼干可以同时使用你的应用程序,但那又如何? 3秒钟没有足够的时间让他们做任何有用的事情,但对于合法用户来说,等待许可批准是一个漫长的时间。

+0

@Wytze:我建议你把它作为一个新问题发布。 – 2012-07-27 00:28:41

0

我遇到了同样的问题。

我所做的是通过把许可证初始化与最低的线程优先级这是可能的一个的AsyncTask:

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