2010-09-28 74 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);