¿Cómo ahorrar mejor el estado de compra de InApp localmente?
Estoy a punto de terminar mi primera aplicación, y una última cosa es implementar la facturación de IAP, por lo que estoy leyendo bastante acerca del tema (incluyendo problemas de seguridad como encriptación, ofuscación y otras cosas).
Mi aplicación es una versión gratuita, con la posibilidad de actualizar a la versión completa a través de IAP, por lo que sólo habría un elemento de compra administrado "premium". Tengo algunas preguntas sobre esto:
- ¿Puede mi aplicación Android imitar el intento del sistema?
- ¿El facebook para Android usa https?
- ¿Cómo almacenar de forma segura las credenciales (contraseña) en la aplicación de Android?
- ¿Cómo funciona la verificación de claves API de Google Maps para Android v2?
- ¿Es seguro guardar los tokens de acceso / actualización de Oauth2 en las Preferencias compartidas de Android?
En el ejemplo de la API de Google IAP (trivialdrivesample), siempre hay un cheque IAP en MainActivity para ver si el usuario compró la versión premium, realizada a través de
MHelper.queryInventoryAsync (mGotInventoryListener);
Mi primera preocupación: Esto significa que el usuario siempre necesita tener una conexión de Internet / datos en la aplicación de inicio, para poder cambiar a la versión premium de la derecha? ¿Qué pasa si el usuario no tiene una conexión a Internet? Él iría con la versión lite supongo, que me encontraría molesto.
Así que pensé en cómo guardar el estado de isPremium localmente, ya sea en SharedPrefs o en la base de datos de aplicaciones. Ahora, sé que no se puede detener a un hacker para hacer ingeniería inversa de la aplicación, no importa lo que, incluso así porque no soy dueño de un servidor para hacer algún lado del servidor de validación.
Sin embargo, uno simplemente no puede guardar una bandera "isPremium" en algún lugar, ya que sería demasiado fácil de detectar.
Así que estaba pensando en algo como esto:
- El usuario compra Premium
- App obtiene el IMEI / Device-ID y XOR lo codifica con una clave de cadena codificada, lo guarda localmente en la base de datos de la aplicación.
Ahora, cuando el usuario vuelve a iniciar la aplicación:
- La aplicación obtiene la cadena codificada de la base de datos, la decodifica y comprueba si decodedString == IMEI. Si sí -> prima
- Si no, entonces la queryInventoryAsync normal será llamada para ver si el usuario compró premium.
¿Qué piensas de ese enfoque? Sé que no es super seguro, pero para mí es más importante que el usuario no está molesto (como con conexión a Internet obligatoria), que la aplicación será unhackable (que es imposible de todos modos). ¿Tienes otros consejos?
Otra cosa, que actualmente no tengo ni idea, es cómo restaurar el estado de la transacción cuando el usuario desinstala / reinstala la aplicación. Sé que la API tiene algún mecanismo para eso, y aditionally mi base de datos se puede exportar e importar a través de la aplicación (por lo que la bandera de isPremium codificado sería también exportable / importable). Ok, supongo que sería otra pregunta, cuando llegue el momento 😉
Cualquier idea y comentario a este enfoque son bienvenidos, ¿crees que es una buena solución? ¿O estoy perdiendo algo / dirigiéndome hacia alguna dirección equivocada?
- No se pudo enviar la solicitud de Amazon AppStore: "La información sensible como la contraseña se repite en texto sin cifrado"
- Proveedor de seguridad de registro de Android
- Preguntas relacionadas con la seguridad y el diseño de la facturación en la aplicación
- Kotlin es más difícil de realizar ingeniería inversa que java
- Android M - Llavero como almacenamiento para nombre de usuario / contraseña
- Cómo proteger los datos de intención mientras lo envía a través de aplicaciones
- En seguridad de facturación de aplicaciones
- ¿Solicitar permiso personalizado con nivel de protección = firma con una firma que no coincide?
Yo también estaba haciendo las mismas investigaciones, pero durante mis pruebas me di cuenta de que no es necesario almacenarlo, como Google hacer todo el almacenamiento en caché que necesita y sospecho (aunque no lo han investigado) que lo están haciendo de manera segura Como posible (viendo como en su interés también!)
Así que aquí es lo que hago
// Done in onCreate mHelper = new IabHelper(this, getPublicKey()); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if (!result.isSuccess()) { // Oh noes, there was a problem. Log("Problem setting up In-app Billing: " + result); } else { Log("onIabSetupFinished " + result.getResponse()); mHelper.queryInventoryAsync(mGotInventoryListener); } } }); // Called by button press private void buyProUpgrade() { mHelper.launchPurchaseFlow(this, "android.test.purchased", 10001, mPurchaseFinishedListener, ((TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()); } // Get purchase response private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if (result.isFailure()) { Log("Error purchasing: " + result); return; } else if (purchase.getSku().equals("android.test.purchased")) { Log("onIabPurchaseFinished GOT A RESPONSE."); mHelper.queryInventoryAsync(mGotInventoryListener); } } }; // Get already purchased response private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { // handle error here Log("Error checking inventory: " + result); } else { // does the user have the premium upgrade? mIsPremium = inventory.hasPurchase("android.test.purchased"); setTheme(); Log("onQueryInventoryFinished GOT A RESPONSE (" + mIsPremium + ")."); } } };
Entonces, ¿qué sucede aquí?
El IAB está configurado y llama a startSetup
, en una terminación exitosa (siempre y cuando se ha ejecutado una vez con una conexión a Internet y está configurado correctamente siempre tendrá éxito) llamamos queryInventoryAsync
para averiguar lo que ya está comprado Ha sido llamado mientras está en línea siempre funciona sin conexión).
Así que si una compra se completa correctamente (como sólo se puede hacer mientras está en línea) que llamamos queryInventoryAsync
para asegurarse de que se ha llamado mientras está en línea.
Ahora no hay necesidad de almacenar nada que ver con las compras y hace que su aplicación sea mucho menos hackable.
He probado de muchas maneras, modo de vuelo, apagar los dispositivos de nuevo y lo único que lo estropea es la eliminación de datos en algunas de las aplicaciones de Google en el teléfono (No es probable que suceda!).
Por favor contribuya a esto si tiene diferentes experiencias, mi aplicación todavía está en la fase de pruebas tempranas.
Refactore la respuesta de ne0 en un método estático, incluyendo los comentarios de snark.
Llamo a este método cuando empiece mi aplicación – necesitarás habilitar tus funciones en TODO
/** * This is how you check with Google if the user previously purchased a non-consumable IAP * @param context App Context */ public static void queryPlayStoreForPurchases(Context context) { final IabHelper helper = new IabHelper(context, getPublicKey()); helper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if (!result.isSuccess()) { Log.d("InApp", "In-app Billing setup failed: " + result); } else { helper.queryInventoryAsync(false, new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { // If the user has IAP'd the Pro version, let 'em have it. if (inventory.hasPurchase(PRO_VERSION_SKU)) { //TODO: ENABLE YOUR PRO FEATURES!! Log.d("IAP Check", "IAP Feature enabled!"); } else { Log.d("IAP Check", "User has not purchased Pro version, not enabling features."); } } }); } } }); }
Esto funcionará a través de reinicios y sin una conexión de red, siempre que el usuario haya comprado el elemento.
Ya que ya sabes que es imposible hacer que sea unhackable con este sistema, yo recomendaría no tratar de evitar la piratería. Lo que usted está proponiendo se conoce como "Seguridad a través de la oscuridad" y suele ser una mala idea.
Mi consejo sería intentar queryInventoryAsync()
primero, y solo revisar su bandera 'isPremium' si no hay conexión a Internet.
También hay algunas otras posibles formas de hacer esto, como tener aplicaciones independientes gratuitas y premium, en lugar de una compra en la aplicación. Cómo otras personas manejan esto y las herramientas que Google pone a disposición podría justificar una investigación.
queryInventoryAsync
tomará automáticamente en cuenta desinstalar y reinstala, ya que realiza un seguimiento de las compras para el usuario conectado.
- ¿Cómo obtener el contexto actual?
- Android Market In-App-Purchase: ¿Cómo obtener la moneda que un usuario pagará?