Nhảy tới nội dung

Open API Security

OPEN SIGNATURE

Introduction

For Integrity Data Guarantees, MoMo requires the data of Open API Request must be signed, and the data in Response Body returned from Open Platform must also be signed. This section will guide you through the process of signing a request and validating the response data.

Sign a request

Before calling an API, the developer must sign a request and add the generated signature along with the following Request Headers.

HeaderContent
OP-SignatureThis is the generated signature.
For example:
kyC0AXHeo0wsy7cFioEYtbOUnPF9QmEW....Gy-lomQyQw
M-TimestampThis specifies the time when a request is sent.
Note: This field must be accurate to milliseconds.
For example: 1652707899536

Example code to create OP-Signature:

// Java 11
import com.google.gson.JsonObject;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class DemoSignature {

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {

long mTimestamp = 1652707899536L;
String openSecretKey = "Mk3BqLnh2MfJanLU05yHOkOHcENeMez8NyA";
String openPrivateKey = "MIIE…";

// Request body
JsonObject jsonData = new JsonObject();
jsonData.addProperty("test", "test");
String data = jsonData.toString();

// Get decoded Private Key
byte[] privateKeyBuffer = Base64.getUrlDecoder().decode(openPrivateKey);
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBuffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

// Init sign
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);

// Get OP-Signature
signature.update((data + mTimestamp + openSecretKey).getBytes(StandardCharsets.UTF_8));
byte[] signData = signature.sign();
String opSignature = Base64.getUrlEncoder().encodeToString(signData);

System.out.println(mTimestamp);
System.out.println(opSignature);
}
}
// Javascript
// npm install node-rsa@1.1.1
const NodeRSA = require('node-rsa');

function base64URLEncode(data) {
const base64 = Buffer.from(data, "utf8").toString("base64");
return base64.replace(/\+/g, "-").replace(/\//g, "_");
}

const timestamp = 1652707899536;
const openSecretKey = "Mk3BqLnh2MfJanLU05yHOkOHcENeMez8NyA";
const openPrivateKey = "MIIE…";

// Request body
const body = {
'test': 'test'
};
const payload = JSON.stringify(body) + timestamp + openSecretKey;

// Get decoded Private Key
const privateKey = new NodeRSA(openPrivateKey, 'pkcs8-private-pem');

// Get OP-Signature
const signedPayload = privateKey.sign(payload);
const opSignature = base64URLEncode(signedPayload);
console.log(opSignature);
// PHP
private function base64url_decode($base64url)
{
$base64 = strtr($base64url, '-_', '+/');
$plainText = base64_decode($base64);
return ($plainText);
}

private function generateSignature($payload)
{
$openPrivateKey = '<<momo_mini_app_open_privatekey>>';
$openPrivateKeyDecodeBase64 = $this->base64url_decode($openPrivateKey);
$openPrivateKeyEncodeBase64 = base64_encode($openPrivateKeyDecodeBase64);
$privateKeyChunkSplit = chunk_split($openPrivateKeyEncodeBase64, 64, "\n");
$privateKey = "-----BEGIN RSA PRIVATE KEY-----\n$privateKeyChunkSplit-----END RSA PRIVATE KEY-----";
openssl_sign($payload, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature);
$signature = str_replace("+", "-", $signature);
$signature = str_replace("/", "_", $signature);
return $signature;
}

Verify a response

After you receive a response, you need to verify the signature of the response by using the openPublicKey. A response consists of response headers and the response body. For example:

The Response Header sample

HeaderContent
OP-SignatureSignature of Response
M-TimestampSpecifies the time when a response is sent.
Note: This field must be accurate to milliseconds.

The Response Body sample

{
“result”: “test”,
“errorCode”: 0,
“errorDesc”: “Success”
}

Example code to verify OP-Signature by openPublicKey

// Java 11
import com.google.gson.JsonObject;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class OpenRSACryptoUtils {

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {

String openSecretKey = "Mk3BqLnh2MfJanLU05yHOkOHcENeMez8NyA";
String openPublicKey = "MIIB…";

// From response body
JsonObject jsonData = new JsonObject();
jsonData.addProperty("test", "test");
String data = jsonData.toString();

// From response headers
long mTimestamp = 1652707899536L;
String opSignature = "kyC0……";

// Get decoded Public Key
byte[] publicKeyBytes = Base64.getUrlDecoder().decode(openPublicKey);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory publicKeyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = publicKeyFactory.generatePublic(publicKeySpec);

// Init verify
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);

// Verify OP-Signature
sign.update((data + mTimestamp + openSecretKey).getBytes(StandardCharsets.UTF_8));
System.out.println(sign.verify(Base64.getUrlDecoder().decode(opSignature))); // true
}
}
// Javascript
// npm install node-rsa@1.1.1
const NodeRSA = require('node-rsa');

function base64URLDecode(base64) {
const data = Buffer.from(base64.replace(/-/g, "+").replace(/_/g, "/"), "base64");
return data;
}

const openSecretKey = "Mk3BqLnh2MfJanLU05yHOkOHcENeMez8NyA";
const openPublicKey = "MIIB…";

// From response headers
const timestamp = 1652707899536;
const opSignature = "kyC0……";

// From response body
const body = {
'test': 'test'
};

const payload = JSON.stringify(body) + timestamp + openSecretKey;

// Get decoded Public key
const publicKey = new NodeRSA(openPublicKey, 'pkcs8-public-pem');

// Decode the base64-encoded Signature.
const opSigBuffer = base64URLDecode(opSignature);

// Verify the signature using the provided PUBLIC key.
const isVerified = publicKey.verify(payload, opSigBuffer);
console.log(isVerified); // true

DATA ENCRYPTION AND DECRYPTION

Introduction

For Data Concealment Guarantees, MoMo requires the data of OpenAPI Request must be encrypted, and the data in the Response Body returned from Open Platform must also be encrypted.

This section will guide you through the process of encrypting a request and decrypting the response sent by MoMo.

Encrypt a request

**Step 1: **Generate a Symmetric Key to encrypt the Request Data

In the code below, an instance of SecretKey will be generated, use this key to encrypt the Request Data that will be sent to the Open Platform.

You also need to encrypt this key with openPublicKey (refer to the Open Signature section above) and send the Encrypted Key in the Request Header “requestKey”.

// In Java 11

private final String PUBLIC_KEY = "MIIBI…";

KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(192); // The AES key size in number of bits
SecretKey secretKey = generator.generateKey();

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, OpenRSACryptoUtils.publicKey(PUBLIC_KEY));

String encryptedKey = Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded()));
System.out.println("ENCRYPTED KEY: " + encryptedKey);

Step 2: Encrypt Request Data with the SecretKey in Step 1

// In Java 11


String _key = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("RAW KEY: " + _key); // Zr5vW7mr8rKZ67zvoL8uYB12dcFrpj90

SecretKeySpec keySpec = new SecretKeySpec(_key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);

Cipher cipherAES = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherAES.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

byte[] results = cipherAES.doFinal(_request.getBytes(StandardCharsets.UTF_8));
String encryptedData = Base64.getEncoder().encodeToString(results);
System.out.println("ENCRYPTED REQUEST DATA: " + encryptedData);

Step 3: Send the encryptedKey from Step 1 in HTTP Header ‘requestKey’ and encryptedData from Step 2 in Response Body or in Query Param ‘data

REQUEST HTTP Method : GET/POST

HTTP Header

ParameterData typeRequiredDescription
mediaTypeIdStringYesSet the value for this Header with miniAppID - the ID of the Mini App that calls OpenAPIs
encryptedBooleanYestrue: if Request Data is encrypted.
false: if Request Data is unencrypted.
requestKeyStringYesEncrypted Symmetric Key that was generated in Step 1

In GET method:

HTTP URL: https://{host}/gateway/open/v1/{api_name}/data=encryptedData

In POST method:

HTTP Request Body = encryptedData

Decrypt a response

**Step 1: **Check HTTP Header ‘encrypted,’ if it is true, Response Body is encrypted, if it is false then Response Body is not encrypted

RESPONSE

HTTP Header

ParameterData typeRequiredDescription
encryptedBooleanxtrue: if Response Body is encrypted.
false: if Response Body is unencrypted.

Step 2: Decrypt the Response Body received from Open Platform with SecretKey from Step 1 (of section _Encrypt a Request_)

// In Java 11

String responseBody = “...”;


String _key = Base64.getEncoder().encodeToString(secretKey.getEncoded());
System.out.println("RAW KEY: " + _key); // Zr5vW7mr8rKZ67zvoL8uYB12dcFrpj90

SecretKeySpec keySpec = new SecretKeySpec(_key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]);

Cipher cipherAES = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherAES.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

byte[] decodedValue = Base64.getDecoder().decode(responseBody.getBytes());
byte[] decryptedVal = cipherAES.doFinal(decodedValue);
String decryptedBody = new String(decryptedVal);
System.out.println("ENCRYPTED REQUEST BODY: " + decryptedBody);

**Sample Encrypted Response Body **

bAbsmPp90EfmSm1pLLxle4rQ0akGsoBZvnBU+oGdYIZYG3Qfa3RTdKmT80LHH31fmRfMkmgbGZSqw1tkKvecr8W488Li57vVxpSIC8QlK5Y=

Sample Raw Response Body

{
"errorCode":0,
"errorDesc":"SUCCESS",
"responseMsg":"5151429912561778"
}