Search code examples
androidfluttercryptographyx509certificatesha1

How to calculate SHA-1 fingerprint of an X.509 certificate in Flutter?


I am trying to fix this issue Improper Verification of App Signature at Runtime. for my flutter app.
Description of this issue is

Android apps are digitally signed. A digital signature, in this context, is a cryptographic construct that a developer applies to a piece of software to prove he/she wrote it. If an attacker has modified something in the app and re-sign it with its own signature, the app should not be able to run. The application should check the current signature of the app with the developer’s signature at runtime.

So I have implemented the following code in my app

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';

Future<void> verifyAppSignature() async {
  if (kDebugMode) {
    print('Skipping signature verification in debug mode.');
    return;
  }
  if (kReleaseMode) {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    String appSignature = await getPackageSignature(packageInfo.packageName);
    String developerSignature = 'my_sha-1_key'; 
// storing the sha key in code is not recommended

    if (appSignature != developerSignature) {
      print("appSign: $appSignature");
      // SystemNavigator.pop();
      // throw Exception('Release signature verification failed');
    }
  }
}

Future<String> getPackageSignature(String packageName) async {
  try {
    const MethodChannel channel = MethodChannel('app-release');
    final String signature = await channel.invokeMethod('getSignature');
    return signature;
  } on PlatformException catch (e) {
    return e.toString();
  }
}

Changes made to MainActivity.kt

package com.example.appname

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    private val CHANNEL = "app-release"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "getSignature") {
                val signature = getSignature()
                result.success(signature)
            } else {
                result.notImplemented()
            }
        }
    }

    private fun getSignature(): String {
        return try {
            val packageInfo: PackageInfo = packageManager.getPackageInfo(
                packageName,
                PackageManager.GET_SIGNATURES
            )

            val signature = packageInfo.signatures?.getOrNull(0)
            signature?.let {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    it.toCharsString()
                } else {
                    it.toString()
                }
            } ?: ""
        } catch (e: Exception) {
            e.printStackTrace()
            ""
        }
    }
}

But The problem here the appSignature returns a X.509 certificate fingerprint/hash and my developerSignature is a sha-1 fingerprint.

If there is a method to calculate the sha-1 fingerprint of X.509 certificate then it would be a big help.


Solution

  • I have figured it out.
    Inside MainActivity.kt I calculated the sha-1 fingerprint of the X.509 Certificate.


    imports

    import java.io.ByteArrayInputStream
    import java.security.cert.CertificateFactory
    import java.security.cert.X509Certificate
    

    then inside getSignature()

    signature?.let {
                    val certFactory = CertificateFactory.getInstance("X.509")
                    val x509Certificate = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
                    val sha1Bytes = x509Certificate.encoded.sha1()
    
                    // Convert the byte array to a hex string
                    sha1Bytes.joinToString("") { "%02x".format(it) }
                } ?: ""
    

    Kotlin extension function to compute the SHA-1 hash of a ByteArray.

        private fun ByteArray.sha1(): ByteArray {
            val digest = java.security.MessageDigest.getInstance("SHA-1")
            return digest.digest(this)
        }
    

    You can replace "SHA-1" with "SHA-256" or other algorithms if needed