Android-SMS Retriever API-计算应用程序的哈希字符串问题

时间:2018-12-19 10:15:51

标签: android

我真的是Android新手,我正在尝试实现SMS Retriever API,以便在我的应用中使用OTP。

我正在遵循本指南: https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string

不幸的是,我陷入了“ 计算应用程序的哈希字符串”的部分

我在这里引用指南部分,并在每个问题下方引用我的问题:

  1. 以小写的十六进制字符串获取应用程序的公钥证书。例如,要从密钥库中获取十六进制字符串,请输入以下命令

    keytool -alias MyAndroidKey -exportcert -keystore MyProduction.keystore | xxd -p | tr -d "[:space:]"
    

我在哪里可以找到我的“公钥证书”,我应该在哪里运行此命令?

  1. 计算组合字符串的SHA-256和。

什么是SHA-256?计算它是什么意思?

  1. 使用Base64编码SHA-256和的二进制值。您可能需要首先从其输出格式中解码SHA-256和。

不明白,我应该在这里做什么?

12 个答案:

答案 0 :(得分:5)

import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;


/*
  This is a helper class to generate your message hash to be included in your SMS message.

  Without the correct hash, your app won't recieve the message callback. This only needs to be
  generated once per app and stored. Then you can remove this helper class from your code.
*/

public class AppSignatureHelper extends ContextWrapper {
    public static final String TAG = AppSignatureHelper.class.getSimpleName();
    private static final String HASH_TYPE = "SHA-256";
    public static final int NUM_HASHED_BYTES = 9;
    public static final int NUM_BASE64_CHAR = 11;

    public AppSignatureHelper(Context context) {
        super(context);
        getAppSignatures();
    }

    /**
     * Get all the app signatures for the current package
     * @return
     */
    public ArrayList<String> getAppSignatures() {
        ArrayList<String> appCodes = new ArrayList<>();

        try {
            // Get all package signatures for the current package
            String packageName = getPackageName();
            PackageManager packageManager = getPackageManager();
            Signature[] signatures = packageManager.getPackageInfo(packageName,
                    PackageManager.GET_SIGNATURES).signatures;

            // For each signature create a compatible hash
            for (Signature signature : signatures) {
                String hash = hash(packageName, signature.toCharsString());
                if (hash != null) {
                    appCodes.add(String.format("%s", hash));
                }

                Log.d(TAG, "Hash " + hash);

            }
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "Unable to find package to obtain hash.", e);
        }
        return appCodes;
    }

    private static String hash(String packageName, String signature) {
        String appInfo = packageName + " " + signature;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
            }
            byte[] hashSignature = messageDigest.digest();

            // truncated into NUM_HASHED_BYTES
            hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES);
            // encode into Base64
            String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
            base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR);

            Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash));
            return base64Hash;
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "hash:NoSuchAlgorithm", e);
        }
        return null;
    }
}

在您的项目中添加上述类,然后从您的 LoginActivity 调用它,如下所示

AppSignatureHelper appSignatureHelper = new AppSignatureHelper(LoginActivity.this);,那么您将获得11位数字的哈希值

答案 1 :(得分:5)

Google已创建a script来包装必要的CLI命令以生成应用程序哈希。

用法是:

./sms_retriever_hash_v9.sh --package "com.your.packagename" --keystore /path/to/your.keystore

示例输出:

$ ./sms_retriever_hash_v9.sh --package "com.your.packagename" --keystore debug.keystore

package name: com.your.packagename
keystore file: debug.keystore

File debug.keystore is found.

Enter keystore password:  


certificate in hex: 3082030d308201f5a003020102020475125fad300d06092a864886f70d01010b05003037310b30090603550406130255533110300e060355040a1307416e64726f6964311630140603550403130d416e64726f6964204465627567301e170d3135313132333231323734355a170d3435313131353231323734355a3037310b30090603550406130255533110300e060355040a1307416e64726f6964311630140603550403130d416e64726f696420446562756730820122300d06092a864886f70d01010105000382010f003082010a0282010100c7604e3b464d0c3f1b556aecfbfcd60b35bb8274909c3eac8825d909b47d44ad60f3dcbd3bdb270a91ed09a8f4c7d39a7da51519116ab2085fdc5761ab472c53860e71779dbf1ebdb5ce2d0140197ac9bcc6ab0e249440be09e233885b110a0fce4b04c903b7741cbc31207ceeb55f71f02b59c2771986238972610cf33e472c08d3b67147117f356617357300dac2655cfa3c056fcc12aa5837a22f9af82164008aae32564db25c2801a45cb66bc087fa8710d14f6448446bc43fb5938c30306959eb5e03dee3dfaf1c83d684338c213208b94a6ea2aa937ba00dd800cbe5b6e30a5a3752b95e5948b20eb6a7051768395e498d12cf2e507458e14e9433d7d70203010001a321301f301d0603551d0e04160414efd057879cfb3ed6c9122caa5d26a6da5f59aadd300d06092a864886f70d01010b0500038201010074004b26417b91333a0503e505030784172a5ac5ffa68d02d42f5991fa637365a3c4833707d062063210da0c16f32be730081420b4ec9563475a57f02f2bf0364cbdc01154e9921edd5140bb4218d7ec6fd3f062d1acacc7cc005c64b7f7e362601fea2a7571c395ecf071a0f10a1bf3c44aa874eb61375e11308ec318c81f4bbd701de2d2fcbbbf764507074da570636f740b379652afe386eb48f69407074b096f3ce03e1d7ac50d9b79169132b01d75389959255b530549a3179798503c83e153e6feb78a89ef80bfce197e23314740f1d55a0db140eb2a44d3acce82d41503b180b6e8ed28f2411f750f9308c72cd8867486ad64af593bc1f1fff5b30510

SHA-256 output in hex: 20e861ecc8550c1e608efc3006f82278025d5e3d7169b40c72b8c3dd0aa9cfd9

First 8 bytes encoded by base64: IOhh7MhVDB5

SMS Retriever hash code:  IOhh7MhVDB5

the raw script file保存在本地,然后运行chmod u+x sms_retriever_hash_v9.sh以使其可执行。

如果脚本链接消失,则脚本内容如下:

#!/bin/sh

# ------------------------------------------------------------------
# [Author] Title
#          Description
# ------------------------------------------------------------------

VERSION=0.1.0
SUBJECT=sms-retriever-hash-generator
USAGE="Usage: sms_retriever_hash_v9.sh --package package_name --keystore keystore_file"

# --- Options processing -------------------------------------------
if [ $# == 0 ] ; then
    echo $USAGE
    exit 1;
fi

# USE: apkblacklister.sh --source source.apk --target target.apk more files to scan

if [[ "$1" != "--package" ]]; then
  echo "Error: expected --package as first parameter"
  exit 1
fi
pkg="$2"
shift 2

if [[ "$1" != "--keystore" ]]; then
  echo "Error: expected --keystore as third parameter"
  exit 1
fi
keystore="$2"
shift 2



echo
echo "package name: $pkg"
echo "keystore file: $keystore"
echo 

if [ -e "$keystore" ]
then
  echo "File $keystore is found."
  echo
else
  echo "File $keystore is not found."
  echo
  exit 0;
fi

# Retrieve certificate from keystore file. Decoded with Base64 and converted to hex
cert=$(keytool -list -rfc -keystore $keystore | sed  -e '1,/BEGIN/d' | sed -e '/END/,$d' | tr -d ' \n' | base64 --decode | xxd -p | tr -d ' \n')

echo
echo "certificate in hex: $cert"


# concatenate input
input="$pkg $cert"

# 256 bits = 32 bytes = 64 hex chars
output=$(printf "$input" | shasum -a 256 | cut -c1-64)
echo
echo "SHA-256 output in hex: $output"

# take the beginning 72 bits (= 9 bytes = 18 hex chars)
output=$(printf $output | cut -c1-18)

# encode sha256sum output by base64 (11 chars)
base64output=$(printf $output | xxd -r -p | base64 | cut -c1-11)
echo
echo "First 8 bytes encoded by base64: $base64output"
echo
echo "SMS Retriever hash code:  $base64output"
echo

答案 2 :(得分:1)

第一种方式:

如果您要为在Play商店上签名的应用制作哈希字符串短信,请从Play商店下载应用签名证书。转到发布管理->应用程序签名,该文件夹将称为Deployment_cer.der:

然后使用此代码将此.der文件更改为jks文件

keytool -importcert -alias examplealias -file deployment_cert.der -keystore certificate.jks -storepass examplepass

信任证书,您将拥有要使用的证书而不是密钥库

现在您已拥有证书。实时运行的jks使用此代码生成11个哈希字符串:

keytool -exportcert -alias anything -keystore '/home/adminuser/Documents/user/certificate.jks' | xxd -p | tr -d "[:space:]" | echo -n com.elbarid.mobilepaymenthey `cat` | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11 

输入密码,然后将为上传的实时版本apk显示哈希字符串

这些代码是在Linux上工作的。现在,如果您想在Windows上工作,则可以替换xxd和tr形式的文件,download here

注意:如果您要测试应用程序是否需要上载存储,请使用尚未存储的.jks文件的密钥库更改密钥库

第二种方式:

您可以在应用中创建一个类来生成代码

public class SmsVerification extends ContextWrapper {
public static final String TAG = SmsVerify.class.getSimpleName();

private static final String HASH_TYPE = "SHA-256";
public static final int NUM_HASHED_BYTES = 9;
public static final int NUM_BASE64_CHAR = 11;

public SmsVerification (Context context) {
    super(context);
}

/**
 * Get all the app signatures for the current package
 * @return
 */
public ArrayList<String> getAppSignatures() {
    ArrayList<String> appCodes = new ArrayList<>();

    try {
        // Get all package signatures for the current package
        String packageName = getPackageName();
        PackageManager packageManager = getPackageManager();
        Signature[] signatures = packageManager.getPackageInfo(packageName,
                PackageManager.GET_SIGNATURES).signatures;

        // For each signature create a compatible hash
        for (Signature signature : signatures) {
            String hash = hash(packageName, signature.toCharsString());
            if (hash != null) {
                appCodes.add(String.format("%s", hash));
            }
        }
    } catch (PackageManager.NameNotFoundException e) {
        Log.e(TAG, "Unable to find package to obtain hash.", e);
    }
    return appCodes;
}

private static String hash(String packageName, String signature) {
    String appInfo = packageName + " " + signature;
    try {
        MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE);
        messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8));
        byte[] hashSignature = messageDigest.digest();

        // truncated into NUM_HASHED_BYTES
        hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES);
        // encode into Base64
        String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP);
        base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR);

        Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash));
        return base64Hash;
    } catch (NoSuchAlgorithmException e) {
        Log.e(TAG, "hash:NoSuchAlgorithm", e);
    }
    return null;
}

并在类内调用该函数:

ArrayList<String> appCodes = new ArrayList<>();
SmsVerify hash = new SmsVerify(activity);
appCodes= hash.getAppSignatures();
String yourhash = appCodes.get(0);

答案 3 :(得分:1)

在哪里可以找到我的“公钥证书”,我应该在哪里运行此命令?

当您运行Android应用时,必须先通过证书对apk进行签名,然后才能将其安装在设备中。如果您以调试模式运行应用程序,则默认情况下,apk由位于此位置$HOME/.android/debug.keystore的密钥库中的证书签名(如果需要,可以更改)。如果生成发行版APK,则必须指定密钥库位置。 More info here

什么是SHA-256?计算它是什么意思?

如Chintam Anand explained on Quora

  

SHA是SSL证书的组成部分,用于确保数据具有   未被修改。SHA通过计算密码来实现此目的   功能以及对给定数据的任何更改都将导致   不同的哈希值。结果,不同的哈希值是   确定数据是否已更改。

所提到的指南中的步骤之一显示了一个完整的命令,可以为您执行每个步骤。包括计算sha-256并对其值进行基本编码。运行sha256sumbase64命令时完成。

不明白,我应该在这里做什么?

您只需要用密钥库别名和密钥库位置替换MyAndroidKeyMyProductionKeys.keystore

keytool -exportcert -alias MyAndroidKey -keystore MyProductionKeys.keystore | xxd -p | tr -d "[:space:]" | echo -n com.example.myapp `cat` | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11

我最近写了一篇有关此问题的博客文章,其中涉及一些常见问题并提供了完整的示例。 check it out for more details

答案 4 :(得分:1)

注意:以下解决方案仅适用于Windows用户(它可能在其他操作系统中也可能不起作用)

我在Windows中遇到“无法识别为内部或外部命令的xxd”。解决此问题后,我开始在Windows中遇到“ tr:写入错误”问题。我花了一整天,但是找不到合适的解决方案。

最后,下面介绍对我有用的解决方案:

  1. 如果尚未安装git,请安装它。 (因为xxd,git中已经存在tr)。就我而言,它已经安装了。

  2. 在名为“ Path”的环境变量中添加Java bin目录路径。就我而言,该路径看起来像是-“ C:\ Program Files \ Java \ jdk-11.0.3 \ bin”。

  3. 现在,转到已下载应用程序的签名证书(deployment_cert.der)的文件夹。右键单击并选择“此处Git Bash”。它将打开git命令行,其中包含您的.der文件所在的文件夹路径。

注意:您还可以从任何地方打开git,但是在这种情况下,需要在步骤4中提到的命令中指定App Signing Certificate的绝对路径。

  1. Google original document中所述,我们首先必须使用以下命令(使用git命令行)将证书导入临时密钥存储区:

     keytool -importcert -file <APP_SIGNING_CERTIFICATE> -keystore <KEYSTORE_FILE> -alias <USER_DEFINED_ALIAS>
    

在这里

  • APP_SIGNING_CERTIFICATE:证书的绝对路径(或仅 如果您已经在目录中,请提供证书名称 您有证书)
  • KEYSTORE_FILE:扩展名为.keystore的任何名称
  • USER_DEFINED_ALIAS:任何名称,请记住它。
  1. 将要求您输入密码-输入密码。输入密码后,它将要求您信任证书,请键入“是”,然后按Enter。

  2. 作为最后一步,我们使用上面提到的别名Keystore文件生成哈希字符串。我们还必须在此步骤中指定应用程序包名称:

     keytool -exportcert -alias USER_DEFINED_ALIAS -keystore KEYSTORE_FILE | xxd -p | tr -d "[:space:]" | echo -n APP_PACKAGE_NAME `cat` | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11
    

在这里

  • USER_DEFINED_ALIAS:请提供步骤4中提供的别名。
  • KEYSTORE_FILE:请提供步骤4中提供的密钥存储文件名。
  • APP_PACKAGE_NAME:请输入您的Andriod应用程序的软件包名称。
  1. 将提示您再次输入密码(您在步骤4中输入的密码)。输入密码后,哈希字符串将显示在git命令行上。哇!

答案 5 :(得分:1)

在macOS上尝试过。

使用终端命令:

  1. 从Google Play控制台下载 deployment_cert.der
  2. 在终端keytool -importcert -file deployment_cert.der -keystore temp.keystore -alias 'your_alias'中运行命令
  3. 运行macOS的命令:(echo your.package && echo ' ' && keytool -exportcert -alias your_alias -keystore your.keystore | xxd -p | tr -d "[:space:]") | tr -d '\n' | shasum -a 256 | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11
  4. 完整commands

使用 python 脚本:

  1. 从Google Play控制台下载 deployment_cert.der
  2. 在终端keytool -importcert -file deployment_cert.der -keystore temp.keystore -alias 'your_alias'中运行命令
  3. 下载smshash.py
  4. 在终端python3 smshash.py temp.keystore 'your_alias' 'your_keystore_password' 'your.package'中运行命令

如果到smshash.py的链接不可用,请创建python文件。

import platform
import os
import sys
import argparse
import subprocess
from subprocess import Popen, PIPE
import base64
from shutil import which
try:
    import hashlib
except ImportError:
    sys.exit("please install 'hashlib' module: pip install hashlib")

#the parser
cmd_parser = argparse.ArgumentParser(description="Computing app's hash string for Android SMS handling")
cmd_parser.add_argument('keystore', type=str, help='Keystore file')
cmd_parser.add_argument('alias', type=str, help='Keystore alias')
cmd_parser.add_argument('keypass', type=str, help='Key password')
cmd_parser.add_argument('appid', type=str, help='Package name of the Android app')
args = cmd_parser.parse_args()

__encoding_name__ = "iso-8859-1" # Latin 1

def isWindows():
    return platform.system() == "Windows"

def cmdExist(program):
    return which(program) is not None

def exitWithError(error):
    print(error, file=sys.stderr)
    sys.exit()

def call(cmd):
    cmd = subprocess.Popen(cmd, shell=True, universal_newlines=True, stdout=PIPE, stderr=PIPE, encoding=__encoding_name__)
    cmdResult = cmd.communicate()
    return (cmd.returncode, cmdResult[0], cmdResult[1])

def getKeytoolCommand(withxxd = False):
    keytoolName = "keytool"
    if not cmdExist(keytoolName):
        exitWithError("Error: keytool command not found. Be sure the JDK is installed and available in the PATH")

    if withxxd:
        return keytoolName + " " + "-alias " + args.alias + " -exportcert -keystore " + args.keystore + " -storepass " + args.keypass + " | " + getxxdName() + " -p"
    else:
        return keytoolName + " " + "-alias " + args.alias + " -exportcert -keystore " + args.keystore + " -storepass " + args.keypass

def getxxdName():
    xxdName = "xxd"
    if isWindows():
        xxdName = "xxd_w"
    if not cmdExist(xxdName):
        exitWithError("Error: " + xxdName + " not found. If you are on Windows, the program xxd_w.exe must be placed in the current folder")
    return xxdName

def getSignature():
    keytoolCommand = getKeytoolCommand()
    returncode, out, err = call(keytoolCommand)
    if returncode != 0:
        print(out)
        print(err)
        exitWithError("keytool command failed. Please check the alias and the password are correct")
    return out

def getHexSignature():
    keytoolWithxxd = getKeytoolCommand(True)
    returncode, out, err = call(keytoolWithxxd)
    if returncode != 0:
        print(out)
        print(err)
        exitWithError("keytool | xxd command failed")
    return out

def removeWhitespaces(value):
    return "".join(value.split())

def appendApplicationId(value):
    return args.appid + " " + value

def computeSha256(value):
    m = hashlib.sha256()
    m.update(value.encode(__encoding_name__))
    return m.digest()

def formatSignature(signature):
    signatureNoSpaces = removeWhitespaces(signature)
    return appendApplicationId(signatureNoSpaces)

# Call getSignature() to check if the alias and password provided are correct: if not correct the program exits with error
signature = getSignature()
hexSignature = getHexSignature()

formattedSignature = formatSignature(hexSignature)
sha256 = computeSha256(formattedSignature)
base64 = base64.b64encode(sha256)

# The hash to use for the SMS is the first 11 chars
print(base64.decode(__encoding_name__)[0:11])

答案 6 :(得分:1)

您可以使用以下脚本从 *.keystore 文件或 *.der 文件(您可以从 Play 管理中心下载)生成哈希码:

#!/usr/bin/env bash

error() { printf "%s\n" "$1" >&2; exit 1; }

command -v keytool &> /dev/null || error "Command 'keytool' not found"
(( $# >= 2 )) || error "Usage: $(basename "$0") <file.der | file.keystore alias> appId"

file=$1

[[ -r $file ]] || error "'$file' doesn't exist or isn't readable"
if [[ $file = *.keystore ]]; then
  keystore=$file
  alias=$2
  [[ $3 ]] || error "Missing application id argument"
  app_id=$3
elif [[ $file = *.der ]]; then
  trap 'code=$?; rm -rf -- "$tmp_folder"; exit "$code"' EXIT SIGINT SIGQUIT SIGTERM
  tmp_folder=$(mktemp -d)
  keystore=$tmp_folder/tmp.keystore
  alias=temp
  app_id=$2
  keytool -importcert -file "$file" -keystore "$keystore" -alias "$alias"
else
  error "'$file' needs to be of type *.der or *.keystore"
fi

keytool -exportcert -keystore "$keystore" -alias "$alias" | xxd -p | tr -d "[:space:]" | printf '%s %s' "$app_id" "$(cat)" | sha256sum | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11

用法:

./script deployment_cert.der com.example.retriever
./script upload.keystore upload com.example.retriever

答案 7 :(得分:0)

对于 mac 用户,请注意生成哈希值的命令::

keytool -exportcert -alias “youralias” -keystore /Path/to/your/app/keys/your.keystore | xxd -p | tr -d “[:space:]” | echo -n your.package.name `cat` | shasum -a 256 | tr -d “[:space:]-” | xxd -r -p | base64 | cut -c1–11

请更改您的密钥库路径以及密钥库名称和软件包名称。

答案 8 :(得分:0)

对于Mac,我正是为此编写了ruby脚本:

https://github.com/kesha-antonov/android-sms-verification-hash

只需克隆存储库并运行脚本

$ ruby get_sms_verification_hash.rb --package com.yourcompany.appname --keystore ../AppName/android/app/release-key.keystore --alias release-key --storepass MyPass123

Your hash for verification sms: SQQpXpxMN75

答案 9 :(得分:0)

我的解决方法是使用上面作为答案添加的帮助程序类,导航至->构建->选择构建变体->选择发布构建变体(取决于项目的方式而变化)然后选择“发行版”后,您可以调用helper函数以获取发行版构建的哈希码,并在将otp发送到应用程序的短信中使用该哈希码

答案 10 :(得分:0)

我知道为时已晚,但这是在MAC终端上对我有用的命令。

keytool -exportcert -alias my_alias -keystore path_to_my_keystore | xxd -p | tr -d "[:space:]" | echo -n my_app_package_name `cat` | shasum -a 256 | tr -d "[:space:]-" | xxd -r -p | base64 | cut -c1-11

注意:

  1. 将上述 my_alias 更改为密钥库的别名
  2. 将上面的 path_to_my_keystore 更改为您的密钥库的确切路径
  3. 将上面的 my_app_package_name 更改为您应用的程序包名称

可以帮助某人:)

答案 11 :(得分:0)

考虑到API 28中的@RestController("/api") public class UserController {} @RestController public class WalletController {} @RestController public class DashboardController {} 弃用, Teja 的答案应重写为:

PackageManager.GET_SIGNATURES