O Mobile Gateway Service assina cada solicitação de API e a envia ao seu servidor de backend. Nesse servidor, verifique a assinatura para confirmar que a solicitação veio do gateway e não sofreu adulteração.
Ao ativar a verificação de assinatura para um grupo de APIs no console do gateway, o Mobile Gateway Service assina cada solicitação desse grupo. Crie as chaves pública e privada usadas na assinatura no próprio console do gateway.
Seu servidor de backend lê a assinatura no cabeçalho da solicitação, reconstrói localmente a string a ser assinada, aplica o mesmo algoritmo e compara o resultado com a assinatura recebida para determinar a validade da solicitação.
Como funciona
A verificação ocorre em três etapas:
Leia os cabeçalhos. O Mobile Gateway Service anexa a assinatura e o identificador da chave a todas as solicitações em dois cabeçalhos específicos.
Reconstrua a string a ser assinada. Monte a mesma string usada pelo gateway como entrada para a assinatura: o método HTTP, o valor Content-MD5 e a URL construída.
Verifique a assinatura. Aplique o algoritmo escolhido (MD5+salt, RSA, SM3 ou SM2) à string reconstruída e compare a saída com a assinatura recebida.
O gateway armazena a assinatura calculada no cabeçalho da solicitação X-Mgs-Proxy-Signature.
O identificador da chave secreta — usado para buscar o valor secreto correspondente — é enviado no cabeçalho da solicitação X-Mgs-Proxy-Signature-Secret-Key.
Construir a string a ser assinada
Montar a string a ser assinada
String stringToSign =
HTTPMethod + "\n" +
Content-MD5 + "\n" +
Url
HTTPMethod: O método HTTP em letras maiúsculas, comoPUTouPOST.-
Content-MD5: O valor MD5 do corpo da solicitação, calculado da seguinte forma:Se
HTTPMethodnão forPUTouPOST, o valor MD5 será uma string vazia ("").Caso o corpo da solicitação seja um formulário, o MD5 será uma string vazia
"". Caso contrário, prossiga para a próxima etapa.-
Calcule o valor MD5. Se a solicitação não tiver corpo,
bodyStreamserá a string"null".String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getBytes("UTF-8")));ImportanteMesmo que o valor de
Content-MD5seja uma string vazia (""), inclua a quebra de linha subsequente (\n) na string a ser assinada. Isso resulta em dois caracteres\nconsecutivos na string.
-
Url: Construída a partir do caminho, da query e dos parâmetros de formulário. Por exemplo, dada a solicitaçãohttp://ip:port/test/testSign?c=3&a=1com parâmetros de formuláriob=2&d=4, monte aUrlconforme descrito abaixo:Extraia o caminho — a parte após
ip:porte antes de?. Neste exemplo, o caminho é/test/testSign.Quando a solicitação não possui parâmetros de query ou de formulário, a
Urlcorresponde ao caminho.-
Ordene os parâmetros de query e de formulário por chave em ordem lexicográfica e concatene-os no formato
Key1=Value1&Key2=Value2&...&KeyN=ValueN. Neste exemplo, o resultado éa=1&b=2&c=3&d=4.NotaSe um parâmetro de query ou de formulário tiver vários valores, use apenas o primeiro
value. Monte a
Urlno formatoPath?Key1=Value1&Key2=Value2&...&KeyN=ValueN. Neste exemplo, aUrlé/test/testSign?a=1&b=2&c=3&d=4.
Verificar a assinatura
Utilize o mesmo algoritmo configurado ao definir o grupo de APIs no console do gateway.
-
Verifique a assinatura com o algoritmo MD5
String sign = "xxxxxxx"; // The signature from Mobile Gateway Service String salt ="xxx"; // The MD5 salt MessageDigest digest = MessageDigest.getInstance("MD5"); String toSignedContent = stringToSign + salt; byte[] content = digest.digest(toSignedContent.getBytes("UTF-8")); String computedSign = new String(Hex.encodeHexString(content)); boolean isSignLegal = sign.equals(computedSign) ? true : false; -
Verifique a assinatura com o algoritmo RSA
String sign = "xxxxxxx"; // The signature from Mobile Gateway Service String publicKey ="xxx"; // The RSA public key from Mobile Gateway Service PublicKey pubKey = KeyReader.getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes())); java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA"); signature.initVerify(pubKey); signature.update(stringToSign.getBytes("UTF-8")); boolean isSignLegal = signature.verify(Base64.decodeBase64(sign.getBytes("UTF-8"))); -
Verifique a assinatura com um algoritmo criptográfico chinês
Algoritmo SM3:
String sign = xxxx; String salt = xxxxxx; String toSignedContent = stringToSign + salt; byte[] srcData = toSignedContent.getBytes("UTF-8"); SM3Digest digest = new SM3Digest(); digest.update(srcData, 0, srcData.length); byte[] resultHash = new byte[digest.getDigestSize()]; digest.doFinal(resultHash, 0); String computedSign = Hex.encodeHexString(resultHash); boolean isSignLegal = sign.equals(computedSign);Algoritmo SM2:
String sign = xxxx; String pubKey = xxxx; Signature signature = Signature.getInstance("SM3withSM2", "LOCCSBC"); KeyPair keyPair = getSmKeyPair(pubKey); PublicKey publicKey = keyPair.getPublic(); signature.initVerify(publicKey); signature.update(stringToSign.getBytes("UTF-8")); return signature.verify(Hex.decodeHex(sign));public static KeyPair getSmKeyPair(String keyPairContent) throws IOException, InvalidKeySpecException, NoSuchProviderException, NoSuchAlgorithmException { KeyFactory keyFactory = KeyFactory.getInstance("SM2", "LOCCSBC"); byte[] keyBytes = getPKCS1fromPEMString(keyPairContent); SM2ParameterSpec spec = SM2NamedCurveTable.getParameterSpec("sm2p256v1", "1234567812345678"); SM2PrivateKeySpec sm2PrivateKeySpec = new SM2PrivateKeySpec(BigIntegers.fromUnsignedByteArray(keyBytes), spec); SM2PrivateKey prvkey = (SM2PrivateKey) keyFactory.generatePrivate(sm2PrivateKeySpec); SM2PublicKeySpec sm2PublicKeySpec = new SM2PublicKeySpec(prvkey.getQ(), spec); SM2PublicKey pubKey = (SM2PublicKey) keyFactory.generatePublic(sm2PublicKeySpec); return new KeyPair(pubKey, prvkey); }public static byte[] getPKCS1fromPEMString(String pemStr) { // pkcs1 try { PEMParser reader = new PEMParser(new StringReader(pemStr)); PemObject pemObject = reader.readPemObject(); reader.close(); ASN1InputStream asn1In = new ASN1InputStream(pemObject.getContent()); ASN1Sequence derSequence = (ASN1Sequence) asn1In.readObject(); asn1In.close(); // openssl format Object obj = derSequence.getObjectAt(1); if (obj instanceof ASN1OctetString){ return ((ASN1OctetString) obj).getOctets(); } // gmssl format DEROctetString derOctetString = (DEROctetString)derSequence.getObjectAt(2); ASN1Sequence sequence = (ASN1Sequence)ASN1Sequence.fromByteArray(derOctetString.getOctets()); return ((ASN1OctetString)sequence.getObjectAt(1)).getOctets(); } catch (Exception e) { throw new RuntimeException(e); } }
Exemplos de código
Para uma implementação completa, consulte HttpSignUtil.java.
Solução de problemas
Incompatibilidade de assinatura
Quando a verificação falha, a causa mais comum é a incompatibilidade entre a string a ser assinada construída pelo seu backend e a string usada pelo gateway.
Registre o valor exato de stringToSign que seu backend constrói e compare-o caractere por caractere com o valor computado pelo gateway. Verifique os seguintes pontos:
**Caractere
\nfinal após umContent-MD5vazio.** Mesmo quandoContent-MD5é uma string vazia, o separador\nainda deve estar presente na string a ser assinada. Isso gera dois caracteres\nconsecutivos — uma fonte frequente de incompatibilidade.Ordem de classificação dos parâmetros. Ordene os parâmetros de query e de formulário por chave em ordem lexicográfica antes da concatenação. Confirme se a sua classificação diferencia maiúsculas de minúsculas e corresponde à ordenação do gateway.
Corpo de formulário tratado como conteúdo. Se o corpo da solicitação for um formulário,
Content-MD5deve ser uma string vazia (""), e não o MD5 dos bytes do corpo do formulário.Valor secreto incorreto. Confirme se o salt ou a chave pública que seu backend utiliza corresponde ao valor configurado no console do gateway para o identificador de chave secreta em
X-Mgs-Proxy-Signature-Secret-Key.