2010-09-28 4 views
9

Ich möchte die neuen Lizenz (LVL) Sachen mit Android Marketplace verwenden, aber ich habe ein Performance-Problem mit dem Lager AESObfuscator. Der Konstruktor benötigt mehrere Sekunden, um auf einem Gerät ausgeführt zu werden (pure Agonie im Emulator). Da dieser Code ausgeführt werden muss, um auch nach zwischengespeicherten Lizenzantworten zu suchen, wird die Überprüfung der Lizenz beim Start stark beeinträchtigt.Irgendwo um schreckliche SecretKeyFactory Leistung mit LVL und AESObfuscator?

die LVL Beispiel-App Rennen, hier ist mein Barbar-Stil Profilierung AESObfuscator Konstruktor:

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

Die Ausgabe auf einem 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 Sekunden Dreschen (ca. 20 in Emulator , Äh). Ich kann es auf einer AsyncTask ausspielen, aber es tut nicht viel Gutes, da die App nicht wirklich laufen kann, bis ich die Lizenz validiert habe. Alles, was ich bekomme, ist ein hübscher, sieben Sekunden langer Fortschrittsbalken, während der Benutzer darauf wartet, dass ich die Lizenz überprüfe.

Irgendwelche Ideen? Roll meinen eigenen Obfuscator mit etwas einfacherem als AES, um meine eigenen Lizenzantworten zu cachen?

Antwort

6

Nach umfangreichem Suchen und Basteln scheint meine beste Problemumgehung zu sein, den AES-Schlüssel selbst zu erstellen, anstatt den PKCS # 5-Code in PBEKeySpec zu verwenden. Ich bin etwas erstaunt, dass andere Leute dieses Problem nicht gepostet haben.

Die Problemumgehungsmethode besteht darin, eine Reihe von identifizierenden Daten (Geräte-ID, IMEI, Paketname usw.) in einer Zeichenfolge zu kombinieren. Ich nehme dann den SHA-1-Hash dieser Zeichenfolge, um 20 Bytes des 24-Byte-AES-Schlüssels zu erhalten. Zugegebenermaßen gibt es nicht so viel Entropie wie PKCS # 5 und 4 Bytes des Schlüssels bekannt sind. Aber wer wird wirklich einen Kryptoangriff starten? Es ist immer noch ziemlich gut und es gibt viel schwächere Angriffspunkte in der LVL, trotz meiner anderen Versuche, es zu verhärten.

Da selbst das Erstellen der AES-Chiffre ein teurer Vorgang ist (2 Sekunden im Emulator), verschiebe ich auch die Erstellung der Verschlüsselungs- und Decryptor-Elemente, bis sie durch Aufrufe zum Verschleiern und Entschlüsseln benötigt werden. Wenn die App eine zwischengespeicherte Lizenzantwort verwendet, ist kein Verschlüssler erforderlich. Dies verkürzt den normalen Startmodus um ein Vielfaches.

Mein neuer Konstruktor ist unten. Wenn jemand die ganze Quelldatei haben möchte, schreib mir einfach eine Zeile.

/** 
    * @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

Ich habe es auch optimiert, aber alles in einer Klasse gehalten. Ich habe die Cipher statisch gemacht, so dass sie nur einmal erstellt werden müssen und dann den Keygen-Algorithmus es 128bit mit MD5 anstelle von SHA1 geändert haben. LicenseCheckerCallback wird nun innerhalb von einer halben Sekunde statt der 3 Sekunden zuvor ausgelöst.

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

Ok das funktioniert

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

Ist das nicht nur meine Antwort oben? –

2

Anstatt Umschreiben den obfuscator, macht es mehr Sinn, es in einem anderen Thread ausgeführt werden. Klar, Cracker können in der Zwischenzeit deine App nutzen, aber na und? 3 Sekunden sind nicht genug Zeit, um etwas Nützliches zu tun, aber für legitime Benutzer ist es eine lange Zeit, auf die Genehmigung der Lizenz zu warten.

+0

@Wytze: Ich schlage vor, dass Sie das als eine neue Frage stellen. –

0

Ich stieß auf das gleiche Problem.

Was ich tat, war die Lizenz Initialisierung in eine AsyncTask mit der niedrigsten Thread-Priorität zu setzen, die durch die Verwendung möglich ist:

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

im Verfahren

doInBackground 

Aber wenn die Lizenz nicht angezeigt gültiger Dialog wird dies dann im GUI-Thread gemacht.

So sieht meine Lizenzprüfung wie:

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

Die LicenseHandler Implementierung wie

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

    ... 

} 

Aber denken Sie daran: Wenn Ihr LicenseCheckerCallback jedes GUI-Element nicht zeigt, dann muss man, dass durch Methoden ausführen mit