Securing Android: Behind a few seconds of payment transaction … 💳📱🔐
Sofien Rahmouni on 2025-04-14
Securing Android: Behind a few seconds of payment transaction … 💳📱🔐

Over the past few months, I’ve been deeply involved in the development of an Android payment application, where security has been a fundamental pillar of the entire process.
Working on this project has given me invaluable insights into the intricate world of securing Android applications.
The protection levels on this Android Payment App, can be devided in Compilation Layer, Runtime Layer.
For free complete read access, please continue with this link : https://medium.com/@sofienrahmouni/securing-android-behind-a-few-seconds-of-payment-transaction-bf6817119d51?source=friends_link&sk=0117880616ac48dde2590f0b8138d766
Published in AndroidWeekly Blog also.
1. Protecting Your App from Reverse Engineering & Debugging
- Anti-Developer-Options: Developer options activation can be the first door for an attacker to start so we should check it :
- Anti-Debugging: Detect debuggers at runtime using
isDebuggerConnected()
:
// Check debbugable flag val isDebuggable =(context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
// Check debugger is connected at runtime import android.os.Debug.isDebuggerConnected
val isDebuggerConnected= isDebuggerConnected()
The debug check also can be done at the system level using native methodptrace()
:
bool = ptrace(PTRACE_TRACEME, 0, 0, 0) == -1;
- Anti-Root Detection: As known in the Android world, the key to every attack vector starts with obtaining privileged access as an admin or root user. Therefore, we must protect our application by implementing root detection :
- Obfuscation: To make the reverse engineering process more difficult, Android offers open-source obfuscation tools such as ProGuard and R8, as well as more advanced paid options like DexGuard, to make decompilation harder.
- Anti-Tampering: On the other hand, Google offers an interesting solution to verify app integrity using the Google Play Integrity API. It is based on a verdict attestation service, making it a more reliable solution compared to local environment checks. However, this solution is subject to limitations, as Google provides only 10,000 free tokens per day.
{ "requestDetails": { "requestPackageName": "com.payment.app", "nonce": "b64_encoded_nonce", "timestampMillis": 1710000000000 }, "appIntegrity": { "appRecognitionVerdict": "PLAY_RECOGNIZED", "packageName": "com.payment.app", "certificateSha256Digest": ["b64_encoded_cert_hash"] }, "deviceIntegrity": { "deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"] }, "accountDetails": { "appLicensingVerdict": "LICENSED" } }
Continuing with Anti-tampering protection, we should also implement APK signature verification. It is recommended to perform this check on a remote server by sending the current certificate signature and comparing it on the server side.
Here is below an example showing the mechanism to get signature application certificate :
object SignatureUtil { private val TAG = SignatureUtil::class.java.simpleName /** ** Get application signature to be used later in the check anti-tampering ** on Server side, in case have is different than setted in the BE means ** the application was changed/tampered. **/ fun getApkSigners(context: Context): List<Signature>? { return try { if (context .packageManager .getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES) .signingInfo?.hasMultipleSigners() == true ) { context .packageManager .getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES) .signingInfo?.apkContentsSigners ?.toList() } else { context .packageManager .getPackageInfo(context.packageName, PackageManager.GET_SIGNING_CERTIFICATES) .signingInfo?.signingCertificateHistory ?.toList() } } catch (e: PackageManager.NameNotFoundException) { Log.e(TAG, "Cannot read APK content signers", e) null } } }
And finally, here is new anti-tampering protection addition was added in Android 10, the useEmbeddedDex flag which is really simple to be used.
Here is an example how to use :
- In manifest xml file, it should putted on it like this :
- In the build gradle :
2. Securing Execution Environment
- Anti-Emulation: We should detect virtual environments VM detection Here is below method which check the multiple layers emulation environnements :
- Check Runtime Environment: During runtime, it is essential to validate environment variables, such as running processes, file access, and installed applications, as well as system properties, to detect any anomalies. This check should be performed outside the Android environment, on the backend side. Therefore, before executing any transaction-related actions, the current environment information should be sent for verification.
import android.system.ErrnoException import android.system.Os import android.system.OsConstants import android.util.Log import java.io.BufferedReader object SecureEnvironmentCheckerInfo { private const val TAG = "SecureEnvironmentCheckerInfo" // The verified paths are : // "/data/local/", // "/data/local/bin/", // "/data/local/xbin/", // "/sbin/", // "/system/bin/", // "/system/bin/.ext/", // "/system/bin/failsafe/", // "/system/sd/xbin/", // "/system/usr/we-need-root/", // "/system/xbin/", // "/system/etc/", // "/data/", // "/dev/" fun getFileAccesses(paths: Array<String>): HashMap<String, Boolean> { Log.d(TAG, "Checking file accesses using system calls") val fileAccesses = HashMap<String, Boolean>() for (path in paths) { fileAccesses[path] = try { Os.access(path, OsConstants.F_OK) } catch (e: ErrnoException) { false } } Log.d(TAG, "Checked ${fileAccesses.size} file accesses") return fileAccesses } fun getSystemProperties(): HashMap<String, String>? { Log.d(TAG, "Check system properties using property service") val systemCommand = "getprop" val content = getOutput(systemCommand, event) ?: return null val systemProperties = SystemPropertyParser.parse(content) if (systemProperties != null) { Log.d(TAG, "Retrieve${systemProperties.size} system properties") } else { Log.e(TAG, "Retrieve system properties from '$systemCommand' output") } return systemProperties } fun getInstalledPackages(): Array<String>? { Log.d(TAG, "Retrieve installed packages using package manager with user 0") val systemCommand = "pm list packages --user 0" val content = getOutput(systemCommand) ?: return null val installedPackages = PackageParser.parse(content) Log.d(TAG, "Retrieved ${installedPackages.size} installed packages") return installedPackages } private fun getOutput(systemCommand: String): String? { var process: Process? = null return try { process = Runtime.getRuntime().exec(systemCommand) val output = process!!.inputStream.bufferedReader().use(BufferedReader::readText) val exitValue = process.waitFor() if (exitValue == 0) { output } else { Log.e(TAG, "'$systemCommand' command terminated with exit value $exitValue") null } } catch (e: Exception) { Log.e(TAG, "Error occurred while executing '$systemCommand' command: $e") null } finally { process?.destroy() if (process?.isAlive == true) { process.destroyForcibly() } } } }
- Anti-Sideloading: Restrict APK installation from untrusted sources, we recommand to download the application from Google play by checking the vendor application :
- Anti-Multiwindow: Prevent multi-window mode to avoid overlay attacks :
3. Preventing Data Leakage & Unauthorized Access
In payment applications, data leakage and unauthorized access present significant security risks, potentially exposing sensitive user information such as Payment Card details (PAN, CVV, etc.). To mitigate these security vulnerabilities, it is crucial to implement the following protective measures:
- Log Protection: Avoid logging sensitive data using
Log.d()
; use structured logging withTimber…
In best case, we should remove any logs using as example proguard rules (in case, we need logs for monotring, we must encrypt it before send it to backend) :
- Secure Database & Preference : We can use SQLCipher for encrypted database storage or save encrypted data directly in your DB and for shared pref, we recammand to use this library from Google EncryptedSharedPreferences.
- Secure Binding Services: When using services, it is essential to restrict AIDL exposure by properly configuring export and enable flags. Additionally, applying signature-based permissions ensures that only trusted applications with the correct signature can interact with the service, enhancing security:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <permission android:name="com.payment.app.permission.BIND_SERVICE" android:protectionLevel="signature" /> <application android:name="com.payment.app.Application" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"> <activity android:name="com.payment.app.MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.payment.app.service.CoreService" android:exported="true" android:enabled="true" android:permission="com.payment.app.BIND_SERVICE" > <intent-filter> <action android:name="com.payment.app.action.BIND_SERVICE" /> </intent-filter> </service> </application> </manifest>
- Anti-Screenshot & Overlay Detection: Android purpose multiple mechanisme to protect from overlay attacks : Use
FLAG_SECURE
to block screenshots and to block overlays using this flag permission android.permission.HIDE_OVERLAY_WINDOWS(introduced in Android 12)
. For oldest Android version, we can implement a custom solution to inject event in random touch and intercept it.
import android.annotation.SuppressLint import android.app.Activity import android.app.Instrumentation import android.graphics.Point import android.os.Build import android.os.SystemClock import android.util.Log import android.view.MotionEvent import android.view.View import android.view.WindowManager import kotlinx.coroutines.* import kotlin.math.absoluteValue class OverlayDetection( screenSize: Point, private var onOverlayDetected: (() -> Unit)? = null ) { companion object { private val TAG = OverlayDetection::class.java.simpleName const val INJECTED_STATE = -35 private const val OFFSET_PERCENT = 10 private const val RETRY_DELAY_INJECT_EVENT = 1500L //1.5 seconds private fun getFlagsWindow() = MotionEvent.FLAG_WINDOW_IS_OBSCURED or MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED } private var lastCallNotCaught = false private var overlayDetectionRunning = true private val scope = CoroutineScope(Dispatchers.IO) private val instrumentation = Instrumentation() private var view: SecureViewContainer? = null private var activity: Activity? = null private val offset: Point = Point( (screenSize.x * OFFSET_PERCENT) / 100, (screenSize.y * OFFSET_PERCENT) / 100 ) private val range = Point( screenSize.x - (2 * offset.x), screenSize.y - (2 * offset.y), ) private var job: Job? = null fun setViewToProtect(view: SecureViewContainer) { Log.d(TAG, "OverlayDetection: protect view $view") this.view = view activity = view.context as Activity this.view?.setOnTouchListener { _: View?, event: MotionEvent -> onOverlayEventDetected(event) false } this.view?.setOnOverlayDetectionListener { onOverlayEventDetected(event) } } private fun hasSecureFlag(): Boolean = activity?.window?.attributes?.flags?.let { (it and WindowManager.LayoutParams.FLAG_SECURE) != 0 } ?: false private fun injectEvent() { if (lastCallNotCaught) { onOverlayDetected.invoke() } else { injectTouchEvent() } } private fun injectTouchEvent() = randomPoint().also { point -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { if (point.value != null) { Log.d(TAG, "Inject event at ${point.value}") lastCallNotCaught = true try { touch(point.value, MotionEvent.ACTION_DOWN) touch(point.value, MotionEvent.ACTION_UP) } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) onOverlayDetected.invoke() } } else { Log.e(TAG, "Random point can't be generated for inject event") onOverlayDetected.invoke() } } else { Log.w(TAG, "ANDROID 13: Touch injection deactivated at $point") } } private fun touch(point: Point, event: Int) { instrumentation.sendPointerSync( MotionEvent.obtain( SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), event, point.x.toFloat(), point.y.toFloat(), INJECTED_STATE ) ) } private fun randomPoint(): Rand.Result<Point> { val xRand = Rand.nextInt() val x = xRand.value ?: return Rand.Result(xRand.nativeResult!!) val yRand = Rand.nextInt() val y = yRand.value ?: return Rand.Result(yRand.nativeResult!!) val point = Point((x.absoluteValue % range.x) + offset.x, (y.absoluteValue % range.y) + offset.y) return Rand.Result(point) } fun start() { stop() overlayDetectionRunning = true lastCallNotCaught = false Log.d(TAG, "Start overlay protection") job = scope.launch { delay(1000L) view?.requestFocus() while (overlayDetectionRunning) { injectEvent() delay(RETRY_DELAY_INJECT_EVENT) } } } fun stop() { overlayDetectionRunning = false lastCallNotCaught = false Log.d(TAG, "Stop overlay protection") job?.cancel() job = null } fun forbidMultiWindowMode(activity: Activity): Boolean = activity.isInMultiWindowMode fun onOverlayEventDetected(event: MotionEvent) { Log.d(TAG, "Received motion event: state(${event.metaState}) flags(${event.flags})") when { !hasSecureFlag() -> onOverlayEventDetected(event) event.metaState != INJECTED_STATE -> Unit event.flags == MotionEvent.ACTION_MOVE -> Unit // Workaround to avoid false/positive toast overlay detection (event.flags and getFlagsWindow()) != 0 -> onOverlayDetected.invoke() } lastCallNotCaught = false } }
import android.content.Context import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import androidx.constraintlayout.widget.ConstraintLayout class SecureViewContainer @JvmOverloads constructor( context: Context, attributeSet: AttributeSet? = null, defStyle: Int = 0, ) : ConstraintLayout( context, attributeSet, defStyle ) { companion object { private val TAG = SecureViewContainer::class.java.simpleName } private var onOverlayDetectionListener: (() -> Unit)? = null fun setOnOverlayDetectionListener(onOverlayDetectionListener: () -> Unit) { this.onOverlayDetectionListener = onOverlayDetectionListener } override fun onFilterTouchEventForSecurity(ev: MotionEvent): Boolean { val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0 Log.d(TAG, "onFilterTouchEventForSecurity: " + "isObscured: $checkIsObscured, isPartiallyObscured: $checkIsPartiallyObscured") if (checkIsObscured || checkIsPartiallyObscured) { onOverlayDetectionListener?.invoke() } return super.onFilterTouchEventForSecurity(ev) } }
<com.payment.app.SecureViewContainer xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/icon" android:layout_width="200dp" android:layout_height="200dp" android:src="@drawable/icon" /> </com.payment.app.SecureViewContainer>
- Anti-Camera & Microphone Abuse: Restrict background access using Android 10+ APIs and put some mechanism detection when camera is open and used by another threat application, here is an example to handle using the camera by another application:
- Anti-Accessibility Exploits: When Android introduced the Accessibility API, it was intended for beneficial use cases. However, over time, it has been exploited and considered a potential backdoor. To mitigate this risk and prevent unauthorized access to on-screen information via the Accessibility API.
import android.graphics.Rect import android.view.View import android.view.accessibility.AccessibilityNodeInfo import androidx.core.view.ViewCompat class SecuredAccessibilityDelegateHelper(val view: View) : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) { super.onInitializeAccessibilityNodeInfo(host, info) view.importantForAccessibility = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS info.isEnabled = false info.setBoundsInScreen(Rect(0, 0, 0, 0)) info.text = "" } }
class SecuredText(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) { init { accessibilityDelegate = SecuredAccessibilityDelegateHelper(this) } }
<com.payement.app.SecuredText android:id="@+id/text" android:layout_width="wrap_content"xxx android:layout_height="30dp" android:gravity="center" android:textColor="@color/white" android:textSize="15sp" android:textStyle="bold" />
4. Strengthening Authentication & Secure Communication
- Biometric Authentication: Starting Android 6.0 the Biometric API exist for strong user authentication like fingerprint, face, or iris which be used to verify the identity of the user before doing any transaction.
- Secure Connection (TLS/SSL): Communication must occur over a secure channel using protocols such as TLS/SSL. It is highly recommended to enforce TLS 1.2 or higher (preferably TLS 1.3 if supported by the server). Additionally, implementing certificate pinning is mandatory to prevent man-in-the-middle attacks. Below is the process for certificate enrollment :
- HTTPS/SSL Security: To prevent Man-in-the-Middle (MITM) attacks, Android 7 (API 24) introduced the Network Security Config, allowing developers to customize network security settings within their applications. Below is an example demonstrating how to configure it properly at manifest file:
At network security config file :
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">payment-domain.com</domain> <pin-set> <pin digest="SHA-256">ZEDJEEKKEKLEE+fibTqbIsWNR/X7CWNVW+CEEA=</pin> <pin digest="SHA-256">BFDBDFDFVDccvVFVVcfdesdsdvsdsdvsd=</pin> </pin-set> </domain-config> </network-security-config>
- Secure Tokens (JWE, UUID): In some cases of Android authentication processes, tokenization is required. There are different types of tokens, such as JWT, JWS, JWE, and JWK. For security reasons, we recommend using the most suitable and secure type based on the application’s needs as encrypted token JWE.
- API Key Protection: Every connection that requires API keys should ensure they are stored securely, either as a Gradle property or an environment variable. Google provides an interesting plugin, Secrets Gradle Plugin, to help manage sensitive data securely. Additionally, it is recommended to restrict API key usage by specifying the allowed package ID in the API provider settings..
- Secure Cryptography: In the programming world, cryptography is a vast and complex topic that cannot be fully covered in this article. However, there are key recommendations from Android security guidelines and well-known security organizations such as NIST (USA), ECRYPT (EU), and CRYPTREC (Japan) for selecting the appropriate cryptographic algorithms and best practices :

From NIST :

From ECRYPT

From CRYPTREC

and finally, to store the encrypted locally, it’s required to generate it by the Android Keystore API using Hardware-backed Keystore
- Key Rotation: Programming security is time-sensitive, and in this context, cryptographic keys must be regularly updated to mitigate potential risks. It is recommended not to exceed a 90-day lifespan for secure keys to ensure optimal protection.
- Use Secure Random: When generating secure keys, one crucial parameter that must not be ignored is the random field. To generate it, it should come from trusted entropy sources such as the SecureRandom API. Therefore, we must avoid using any other sources for generating random values.
- Secure Elements & TEE (Trusted Execution Environment): As said in the last point, Android Keystore should be generated on Trusted Execution Environment (TEE) storage backed in StrongBox introduced in Android 9 (for lowest version, we can use software white box but it’s must be respect the security standard).
- GMS Security Updates: Google provides GMS (Google Mobile Services), which checks for any security updates. To verify this, we should check the currently installed version of Google Play Services. Below is the minimum version that should be present on the device :
- Backup Security: In Android, to retrieve your preferences after reinstalling an application, we use the backup mechanism. However, this represents a security issue, which is why it has been deprecated starting from Android 12 and may be removed in future versions. We recommend disabling auto-backups for sensitive data by setting
android:allowBackup="false"
. If there is a need to use backups, make sure not to store any sensitive data. - CVE Patching & Dependency Updates: In Android, we have a higher probability to use third party library which can represent a vulenrability risk, so before integrating any library, we must check the origin , the maintainers community, the latest support date .. and after that we should regularly update and check for CVEs and finally use only Stable version.
- Android Security Support patch : Every version of Android has a security lifecycle and especially end of life security support so we the minmum Android version must be respect following this example table taken from this reference https://endoflife.date/android :
7. Advanced Security Techniques
- Secure OpenGL: In some context , we need to draw some sensitive information in the screen as displaying the random PIN so to protect rendering data to be read illegaly from the RAM memory, Android purpose this flag EGL_PROTECTED_CONTENT_EXT used to protect the rendering data context.
import android.content.Context import android.opengl.EGL14.* import android.opengl.GLSurfaceView import android.util.Log import com.visa.BuildConfig import javax.microedition.khronos.egl.* /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important * details: * * - The class must use a custom context factory to enable 2.0 rendering. * See ContextFactory class definition below. * * - The class must use a custom EGLConfigChooser to be able to select * an EGLConfig that supports 2.0. This is done by providing a config * specification to eglChooseConfig() that has the attribute * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag * set. See ConfigChooser class definition below. * * - The class must select the surface's format, then choose an EGLConfig * that matches it exactly (with regards to red/green/blue/alpha channels * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. * * - The class use some OpenGL protection extension if it's exist on the Android Device otherwise * the used context is standard. * About this mechanism of this protected extension EGL_PROTECTED_CONTENT_EXT = 0x32C0 : * * The attribute EGL_PROTECTED_CONTENT_EXT can be applied to EGL contexts, * EGL surfaces and EGLImages. If the attribute EGL_PROTECTED_CONTENT_EXT * is set to EGL_TRUE by the application, then the newly created EGL object * is said to be protected. A protected context is required to allow the * GPU to operate on protected resources, including protected surfaces and * protected EGLImages. * * GPU operations are grouped into pipeline stages. Pipeline stages can be * defined to be protected or not protected. Each stage defines * restrictions on whether it can read or write protected and unprotected * resources, as follows: * When a GPU stage is protected, it: * - Can read from protected resources * - Can read from unprotected resources * - Can write to protected resources * - Can NOT write to unprotected resources * * When a GPU stage is not protected, it: * - Can NOT read from protected resources * - Can read from unprotected resources * - Can NOT write to protected resources * - Can write to unprotected resources * */ internal class GL2JNIView( context: Context?, ) : GLSurfaceView(context) { companion object { private val TAG = GL2JNIView::class.java.simpleName private const val EGL_CONTEXT_CLIENT_ATTR_VERSION = 0x3098 private const val EGL_CONTEXT_CLIENT_VALUE_VERSION = 2 private const val EGL_EXTENSION_PROTECTED_CONTENT_NAME = "EGL_EXT_protected_content" private const val EGL_EXT_PROTECTED_CONTENT = 0x32C0 } init { val isSecureExtensionSupported = isSecureExtensionSupported() Log.d(TAG, "OpenGl Secure extension supported : $isSecureExtensionSupported") if (BuildConfig.DEBUG) { debugFlags = DEBUG_CHECK_GL_ERROR or DEBUG_LOG_GL_CALLS } setEGLContextFactory(ContextFactory(isSecureExtensionSupported)) setEGLWindowSurfaceFactory(WindowSurfaceFactory(isSecureExtensionSupported)) } internal inner class ContextFactory(private val secureContext: Boolean) : EGLContextFactory { override fun createContext(egl: EGL10, display: EGLDisplay, config: EGLConfig): EGLContext { val attrList = if (secureContext) { intArrayOf(EGL_CONTEXT_CLIENT_ATTR_VERSION, EGL_CONTEXT_CLIENT_VALUE_VERSION, EGL_EXT_PROTECTED_CONTENT, EGL_TRUE, EGL10.EGL_NONE) } else { intArrayOf(EGL_CONTEXT_CLIENT_VERSION, EGL_CONTEXT_CLIENT_VALUE_VERSION, EGL10.EGL_NONE) } val context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrList) if (context == EGL10.EGL_NO_CONTEXT) { Log.e(TAG, "Error creating EGL context.") } checkEglError("eglCreateContext") return context } override fun destroyContext(egl: EGL10, display: EGLDisplay, context: EGLContext) { if (!egl.eglDestroyContext(display, context)) { Log.e("DefaultContextFactory", "display: $display context: $context") } } } internal inner class WindowSurfaceFactory(private val secureWindowSurface: Boolean) : EGLWindowSurfaceFactory { override fun createWindowSurface( egl: EGL10, display: EGLDisplay, config: EGLConfig, nativeWindow: Any, ): EGLSurface? { var result: EGLSurface? = null try { val attrList = if (secureWindowSurface) { intArrayOf(EGL_EXT_PROTECTED_CONTENT, EGL_TRUE, EGL10.EGL_NONE) } else { intArrayOf(EGL10.EGL_NONE, EGL10.EGL_NONE, EGL10.EGL_NONE) } result = egl.eglCreateWindowSurface(display, config, nativeWindow, attrList) checkEglError("eglCreateWindowSurface") } catch (e: IllegalArgumentException) { Log.e(TAG, "eglCreateWindowSurface", e) } return result } override fun destroySurface(egl: EGL10, display: EGLDisplay, surface: EGLSurface) { egl.eglDestroySurface(display, surface) } } private fun isSecureExtensionSupported(): Boolean { val display: android.opengl.EGLDisplay? = eglGetDisplay(EGL_DEFAULT_DISPLAY) val extensions = eglQueryString(display, EGL10.EGL_EXTENSIONS) return extensions != null && extensions.contains(EGL_EXTENSION_PROTECTED_CONTENT_NAME) } private fun checkEglError(methodName: String) { val eglResult = eglGetError() if (eglResult == EGL_SUCCESS) { val message = "$methodName succeeded without error (EGL_SUCCESS)" Log.d(TAG, message) } else { val errorHexString = "0x${Integer.toHexString(eglResult)}" val errorDesc = eglResultDesc(eglResult) val message = "$methodName: EGL error $errorHexString encountered. $errorDesc" Log.e(TAG, message) } } // https://registry.khronos.org/EGL/sdk/docs/man/html/eglGetError.xhtml private fun eglResultDesc(eglResult: Int) = when (eglResult) { EGL_SUCCESS -> "The last function succeeded without error." EGL_NOT_INITIALIZED -> "EGL is not initialized, or could not be initialized, for the specified EGL display connection." EGL_BAD_ACCESS -> "EGL cannot access a requested resource (for example a context is bound in another thread)." EGL_BAD_ALLOC -> "EGL failed to allocate resources for the requested operation." EGL_BAD_ATTRIBUTE -> "An unrecognized attribute or attribute value was passed in the attribute list." EGL_BAD_CONTEXT -> "An EGLContext argument does not name a valid EGL rendering context." EGL_BAD_CONFIG -> "An EGLConfig argument does not name a valid EGL frame buffer configuration." EGL_BAD_CURRENT_SURFACE -> "The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid." EGL_BAD_DISPLAY -> "An EGLDisplay argument does not name a valid EGL display connection." EGL_BAD_SURFACE -> "An EGLSurface argument does not name a valid surface (window, pixel buffer or pixmap) configured for GL rendering." EGL_BAD_MATCH -> "Arguments are inconsistent (for example, a valid context requires buffers not supplied by a valid surface)." EGL_BAD_PARAMETER -> "One or more argument values are invalid." EGL_BAD_NATIVE_PIXMAP -> "A NativePixmapType argument does not refer to a valid native pixmap." EGL_BAD_NATIVE_WINDOW -> "A NativeWindowType argument does not refer to a valid native window." EGL_CONTEXT_LOST -> "A power management event has occurred. The application must destroy all contexts and reinitialise OpenGL ES state and objects to continue rendering." else -> "" } }
- Multiprocess Security: To avoid a global disaster in the event that reading data from memory is compromised, we recommend defining a multiprocess mechanism using
isolatedProcess
. This helps to prevent unauthorized access by ensuring that each process operates in a separate environment, making it more difficult for one process to access the memory or data of another..
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.payement.app" xmlns:tools="http://schemas.android.com/tools"> <application> <service android:name="com.payement.app.service.IsoltedProcessService" android:process=":isoltedProcessProcess" /> </application> </manifest>
- Bonus: Here is some useful resources to be up to date by security world of Android: android_securecoding_en.pdf , security-best-practices, www-project-mobile-app-security.
By reading this article, you can get an idea of how a simple payment transaction, which takes only a few seconds, goes through a comprehensive security workflow. Also, keep in mind that there is no final version of security — it’s crucial to continuously stay aligned and updated with the evolving security landscape of Android.
And finally remember, in every update of your security layer, don’t forget this quote:
“Don’t let security kill your business, try to find the right balance.”
For any question & suggestion, Let’s discuss !