Android: 如何以编程方式获取SHA1/MD5指纹?

12

我正在试图实现一种与我的后端服务器通信的方法,并确保只有我的应用程序才能调用后端并获得响应。

因此,我的想法是,我只需通过HTTPS POST请求发送SHA1 / MD5指纹,并在后端服务器上进行验证。 如果指纹匹配,服务器将回答。

所以我第一个问题是:如何在运行时编程获取这些指纹? 这是否可行?

第二个问题是:真的可以这么简单吗?还是我真的需要设置OAuth服务器(或使用Google API)?……问题是,我认为对于我的用例来说,OAuth有点过度kill,而且我不想处理到期/刷新令牌等内容。


“在运行时,我如何以编程方式获取它们?” ——您尚未说明要获取SHA1/MD5值的内容。 “难道可以这么简单吗?”—— 可能不是。如果这是一个固定值,那么其他人只需硬编码相同的值即可。如果这是某种挑战-响应形式,那么其他人可以使用相同的数据实现相同的算法。您的APK文件可以被任何想要阅读它的人读取。 - CommonsWare
SHA1指纹可能就足够了 - 请参见我在@GabeSechan答案中的评论 - Nico
@CommonsWare 我有一个需求,我需要将我的 API 提供给其他应用程序使用。从安全和计费方面考虑,我需要确保已经认证的应用程序才可以访问 API。我想知道 Facebook 和 Google 如何使用我们在应用程序创建过程中提供的 SHA-1。您能否分享一些关于实现这一点的信息吗? - Calvin
@Calvin:我一点头绪都没有,抱歉。 - CommonsWare
4个回答

19

我已经补充了Zulqumain Jutt提出的解决方案,以便能够得到常见形式的结果,例如:

KeyHelper:MD5 56:ff:2f:1f:55:fa:79:3b:2c:ba:c9:7d:e3:b1:d2:af

public class KeyHelper {

    /**
     * @param key string like: SHA1, SHA256, MD5.
     */
    @SuppressLint("PackageManagerGetSignatures") // test purpose
    static void get(Context context, String key) {
        try {
            final PackageInfo info = context.getPackageManager()
                    .getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES);

            for (Signature signature : info.signatures) {
                final MessageDigest md = MessageDigest.getInstance(key);
                md.update(signature.toByteArray());

                final byte[] digest = md.digest();
                final StringBuilder toRet = new StringBuilder();
                for (int i = 0; i < digest.length; i++) {
                    if (i != 0) toRet.append(":");
                    int b = digest[i] & 0xff;
                    String hex = Integer.toHexString(b);
                    if (hex.length() == 1) toRet.append("0");
                    toRet.append(hex);
                }

                Log.e(KeyHelper.class.getSimpleName(), key + " " + toRet.toString());
            }
        } catch (PackageManager.NameNotFoundException e1) {
            Log.e("name not found", e1.toString());
        } catch (NoSuchAlgorithmException e) {
            Log.e("no such an algorithm", e.toString());
        } catch (Exception e) {
            Log.e("exception", e.toString());
        }
    }
}

10

您可以生成一个类似下面示例的内容:

private void getKeyHash(String hashStretagy) {
        PackageInfo info;
        try {
            info = getPackageManager().getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNATURES);
            for (Signature signature : info.signatures) {
                MessageDigest md;
                md = MessageDigest.getInstance(hashStretagy);
                md.update(signature.toByteArray());
                String something = new String(Base64.encode(md.digest(), 0));
                Log.e("KeyHash  -->>>>>>>>>>>>" , something);

               // Notification.registerGCM(this);
            }
        } catch (PackageManager.NameNotFoundException e1) {
            Log.e("name not found" , e1.toString());
        } catch (NoSuchAlgorithmException e) {
            Log.e("no such an algorithm" , e.toString());
        } catch (Exception e) {
            Log.e("exception" , e.toString());
        }
    }

使用方法:

getKeyHash("SHA");
getKeyHash("MD5");

第一个回答:您可以使用上述方法,它是安全且唯一的,我一直在使用。

第二个回答:您可以使用授权密钥,但完全取决于您自己,您觉得哪种方式更舒适。


我还没有尝试过,但我猜问题就像@GabeSechan所提到的那样,任何应用程序都可以要求我的指纹,这对我的目的来说非常糟糕。 - Nico
@Nico 是的,任何人都可以。这实际上是哈希函数有用的地方 - 我可以计算文件的SHA哈希(或md5哈希),并检查它是否与您所说的应该是相同的文件,并且可以合理地确定它是相同的文件。它通过公开验证来工作,但由于同样的原因,它在身份验证方面失败了。 - Gabe Sechan
我该如何获取发布密钥库哈希以进行比较? - Web.11

6

Kotlin版本的Artur示例密钥字符串:“SHA1”或“SHA256”或“MD5”。

fun getSig(context: Context, key: String) {
            try {
                val info = context.packageManager.getPackageInfo(
                    BuildConfig.APPLICATION_ID,
                    PackageManager.GET_SIGNATURES
                )
                for (signature in info.signatures) {
                    val md = MessageDigest.getInstance(key)
                    md.update(signature.toByteArray())
                    val digest = md.digest()
                    val toRet = StringBuilder()
                    for (i in digest.indices) {
                        if (i != 0) toRet.append(":")
                        val b = digest[i].toInt() and 0xff
                        val hex = Integer.toHexString(b)
                        if (hex.length == 1) toRet.append("0")
                        toRet.append(hex)
                    }
                    val s = toRet.toString()
                    Log.e("sig", s)
                    
                }
            } catch (e1: PackageManager.NameNotFoundException) {
                Log.e("name not found", e1.toString())
            } catch (e: NoSuchAlgorithmException) {
                Log.e("no such an algorithm", e.toString())
            } catch (e: Exception) {
                Log.e("exception", e.toString())
            }
}

5
你试图做的事情是不可能的。 你发送到服务器的任何id都可以被其他应用程序复制。这就是为什么你需要有密码的用户,这些密码不在应用程序中——来自外部来源的密码是确保请求有效的唯一方法。而且这只证明了用户是有效的,而不是来自你的应用程序。

5
从数学上讲,只凭一个外部代理人提供的信息是无法证明其身份的。其他任何人都可以通过获取您应用程序的sha指纹并使用它来撒谎关于他们自己的sha指纹。 - Gabe Sechan
1
正确的做法是对用户进行身份验证,并提供一组API,仅允许他们执行您希望他们执行的操作(每个操作都要进行适当的授权检查)。然后,您无需关心他们是否正在使用您的应用程序。如果他们真的想全力以赴并编写自己的客户端,那又如何呢? - Gabe Sechan
1
在我的工作流程中,如果出现类似的情况,我不能让用户输入密码。我需要应用程序供应商访问我的服务器API,但是我的服务器根本没有用户信息。我会为应用程序供应商授权使用他们特定的1个应用程序的API。我不想在应用程序中硬编码任何内容。我可以从Android应用程序密钥库中派生一些内容,并且已经在服务器上有一些内容,以知道请求来自真实的供应商。 - Calvin
2
@GabeSechan 要获取SHA-1指纹并在后端进行注册,您需要使用用于签署应用程序的密钥库文件、密钥库密码以及别名/密码组合。如果攻击者成功窃取了您的密钥库,则可能会面临更大的问题,就像从服务器窃取私有证书一样。我不是说这是最终的身份验证协议,但我认为它非常安全,这也是GCP使用它的原因。 - MatPag
2
如果像你说的那样容易,为什么没有很多报告谈论使用GCP的大型应用程序的API配额泄漏? 如果窃取API配额这么容易,没有人会使用GCP,如果是这种情况,为什么Google选择这种身份验证方法? - MatPag
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接