Skip to content

Transfer

This page describes how to authenticate and sign transfer API requests using OAuth 1.0a (One Leg), a server-to-server authentication scheme based on RSA digital signatures.

The Transfer API uses OAuth 1.0a in its simplest “one-legged” form — no user interaction is required. Each request is signed with a 4096-bit RSA private key and includes an Authorization header containing the OAuth parameters and the computed signature.

OAuth 1.0a Basics

OAuth is a protocol originally published as RFC 5849 and used for securing access to APIs.

Tha transfer API call uses OAuth 1.0a in its simplest form, also known as “One Leg”. This implementation involves one single step, in which we rely on OAuth signatures for server-to-server authentication.

The next few sections give a brief overview of:

  • What it means to digitally sign requests in this context
  • The format of the expected Authorization header
  • What the OAuth credentials for your client application are.

Signed Requests

As explained previously, HTTP requests are signed by client applications. Digital signatures are particularly useful as they guarantee integrity, authenticity and allow for non-repudiation of incoming API calls.

The OAuth signature process:

  1. Normalizes requests (URL, body, …) and OAuth parameters into a single string to sign.

    The Signature Base String is a consistent reproducible concatenation of the request elements into a single string (…) used as an input in hashing or signing algorithms

  2. Signs this string using a client RSA private key and, in our case, the RSA-SHA256 method

  3. Adds an Authorization header to the requests (for sending OAuth parameters and the signature along with the request)

Upon reception of the request, the signature base string is recomputed and the RSA signature is verified using the client public key. The signature must be valid and the signed base string and recomputed base string must be identical.

Authorization Header

OAuth 1.0a uses the standard HTTP Authorization header with the authentication scheme set to OAuth. The header value contains signed OAuth parameters and the request signature value only your application can produce.

Let’s break a real-life example down:

Authorization: OAuth
oauth_body_hash="94cOcstEzvTvyBcNV94PCbo1b5IA35XgPf5dWR4OamU=",
oauth_nonce="32lqGrI0f0nQEW85",
oauth_signature="MhfaStcHU0vlIoeaBLuP14(...)qqd99lI56XuCk8RM5dDA%3D%3D",
oauth_consumer_key="ck_aXqayIybNdwMnzGIZMAkQYSq(...)139a87746d5b00000000000000",
oauth_signature_method="RSA-SHA256",
oauth_timestamp="1558370962",
oauth_version="1.0"

oauth_body_hash

This element is an extension to OAuth (also known as Google Body Hash) in order to systematically check the integrity of request bodies. It contains a base64-encoded SHA-256 digest of the body.

oauth_nonce and oauth_timestamp

The OAuth nonce is a random string, uniquely generated for each request and also unique within a given time window. The request timestamp is a positive integer representing the number of seconds since 1970. It must match the current time. In this example, 1558370962 is equivalent to 05/20/2019 @ 4:49pm (UTC). The timestamp and nonce pair must be unique, or the request will be treated as a replay attack.

oauth_signature

This is the base64-encoded RSA signature of the request, produced using your private signing key. More details in the signed requests section.

oauth_consumer_key

The consumer key is a string identifying your application. More details in the consumer key section.

oauth_signature_method

This parameter indicates the signature method used to compute the oauth_signature value. It must be RSA-SHA256.

oauth_version

The OAuth version used. It must be 1.0.

Consumer Key

Consumer keys are 68-character identifiers for client applications consuming the APIs. The consumer key is not a secret (unlike the signing key), but rather a kind of username which appears in the OAuth header value sent with each request (see oauth_consumer_key).

Signing Key

The signing key is a 4096-bit private RSA key used to generate request signatures and confirms ownership of the consumer key. This private key is part of a public/private key pair generated by you (or your web browser) and whose public key is certified by the system.

Example of unencrypted RSA key (PKCS#1 PEM):

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAx8k7j56VpkzxJT3qb23cYioMZ8VtGSTglwhXjyvags4VsSIH
oCCRd0Ich4UgNfat2adhwoPEw/bER/yaKhfFxiNYyjWruhIbvmzzgcz/w679JTuD
...
TTJVAoGBAMHkR/0+VAR8vvSEEENov43R7jnuggoQej5r9R/gG8UngoJkQ8b3aKGD
fRKHqz7nK1VgT8mh+M63gzL/UW8jSn2c1CbbojK/wU3FuaaRT5eY
-----END RSA PRIVATE KEY-----

How to generate private and public keys

Terminal window
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in private_key.pem -out public_key.pem

OAuth 1.0a libraries with Body Hash support

Languages

PHP

Below examples use a Mastercard PHP library for singing a request https://github.com/Mastercard/oauth1-signer-php

PHP with Guzzle client

use GuzzleHttp\Client;
use Mastercard\Developer\OAuth\OAuth;
$payload = json_encode(
[
'order' => [
'orderMerchantId' => '123',
'orderDescription' => 'test-123',
'orderAmount' => '10.12',
'orderCurrencyCode' => 'EUR',
],
'transferType' => 'ACCOUNT_TO_CARD',
'receiver' => [
'firstname' => 'Firstname',
'lastname' => 'lastname',
],
'receiverCard' => [
'cardNumber' => '5559472802292370',
],
]
);
// server url for transfer
$uri = 'https://SERVER_NAME/api';
// Private key in PEM FORMAT
// First line should be '-----BEGIN PRIVATE KEY-----'
$privateKey = openssl_pkey_get_private('file://private-key.key');
// your consumer key
$consumerKey = 'ck_m ... b';
$authHeader = OAuth::getAuthorizationHeader($uri, 'POST', $payload, $consumerKey, $privateKey);
$client = new Client([
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => $authHeader,
]
]);
$response = $client->post($uri,
['body' => $payload]
);
$tree = json_decode($response->getBody()->getContents(), true);
printf("order system id is %d\n", $tree["orderSystemId"]);

PHP with OpenAPI Generator

You can use OpenAPI Generator to generate a client library

Terminal window
openapi-generator-cli generate -i openapi-spec.yaml -g php -o out

Example code to send the transfer request:

use Mastercard\Developer\Signers\PsrHttpMessageSigner;
use Mastercard\Developer\OAuth\Utils\AuthenticationUtils;
use OpenAPI\Client\Configuration;
use OpenAPI\Client\Api\PaymentsApi;
$requestor_id = 4; // int | Requestor ID
$consumerKey = "consumerKey-1";
// from p12 keystore
$signingKey = AuthenticationUtils::loadSigningKey(
'./test_key_container.p12',
'mykeyalias',
'Password1');
// or
// Private key in PEM FORMAT
// First line should be '-----BEGIN PRIVATE KEY-----'
$signingKey = openssl_pkey_get_private('file://private-key.key');
$stack = new GuzzleHttp\HandlerStack();
$stack->setHandler(new GuzzleHttp\Handler\CurlHandler());
$stack->push(GuzzleHttp\Middleware::mapRequest([new PsrHttpMessageSigner($consumerKey, $signingKey), 'sign']));
$options = ['handler' => $stack];
$client = new GuzzleHttp\Client($options);
$config = Configuration::getDefaultConfiguration();
$apiInstance = new PaymentsApi(
$client,
$config
);
$transfer_request = new \OpenAPI\Client\Model\TransferRequest();
$order = new \OpenAPI\Client\Model\OrderInfo();
$order->setOrderAmount(10);
$order->setOrderCurrencyCode("EUR");
// fill other fields
$transfer_request->setOrder($order);
try {
$result = $apiInstance->paymentsTransferRequestorIdPost($requestor_id, $transfer_request);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling PaymentsApi->paymentsStatusRequestorIdPost: ', $e->getMessage(), PHP_EOL;
}

Java with Mastercard Library

You can use OpenAPI Generator to generate a client library

Terminal window
openapi-generator-cli generate -i openapi-spec.yaml -g java -o out

Adding Mastercard Java library to you project

Please see the Mastercard Java library for singing transfer requests https://github.com/Mastercard/oauth1-signer-java

<dependency>
<groupId>com.mastercard.developer</groupId>
<artifactId>oauth1-signer</artifactId>
<version>1.5.2</version>
</dependency>

Example using Mastercard library

package org.openapitools.client.api;
import com.mastercard.developer.interceptors.OkHttpOAuth1Interceptor;
import org.openapitools.client.ApiClient;
import org.openapitools.client.ApiException;
import org.openapitools.client.model.*;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.stream.Collectors;
import static java.nio.file.Files.readAllLines;
import static org.openapitools.client.model.TransferRequest.TransferTypeEnum.ACCOUNT_TO_CARD;
public class TransferExample {
public static void main(String[] args) throws ApiException {
String baseUrl = "https://SERVER_NAME/api";
int requestorId = 1; // your requestor id
String consumerKey = "ck_ab...yz"; // your consumer key
File privateKeyFile = new File("private-key.pem");
PrivateKey signingKey = loadPrivateKey(privateKeyFile);
ApiClient client = new ApiClient();
client.setBasePath(baseUrl);
client.setHttpClient(
client.getHttpClient()
.newBuilder()
.addInterceptor(new OkHttpOAuth1Interceptor(consumerKey, signingKey))
.build()
);
PaymentsApi api = new PaymentsApi(client);
TransferRequest transfer = new TransferRequest()
.transferType(ACCOUNT_TO_CARD)
.order(new OrderInfo()
.orderMerchantId("inv34578456")
.orderDescription("Withdrawal #5623445")
.orderPurpose("for account #2958283")
.orderAmount("100.00")
.orderCurrencyCode("USD")
)
.receiver(new CustomerInfo()
.firstname("Firstname")
.lastname("Lastname")
)
.receiverCard(new ReceiverCardInfo()
.cardNumber("4089204730803560")
);
StatusResponse status = api.paymentsTransferRequestorIdPost(requestorId, transfer);
System.out.println("status = " + status);
}
private static PrivateKey loadPrivateKey(File privateKeyFile) {
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cannot create key factory for RSA", e);
}
byte[] bytes;
try {
bytes = Base64.getDecoder().decode(
readAllLines(privateKeyFile.toPath())
.stream()
.filter(line -> !line.contains(" KEY-----"))
.collect(Collectors.joining()));
} catch (IOException e) {
throw new UncheckedIOException("Cannot read private key file from " + privateKeyFile.getAbsolutePath(), e);
}
try {
return keyFactory.generatePrivate(
new PKCS8EncodedKeySpec(
bytes
)
);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Cannot create private key from bytes", e);
}
}
}

Raw Java example

Here is the another example how to generate a signature in Java without any third party library.

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import static java.nio.file.Files.readAllLines;
public class TransferSignatureExample {
public static void main(String[] args) {
PrivateKey privateKey = PrivateKeyLoader.loadPrivateKey(new File("private-key-5.pem"));
String url = "https://SERVER_NAME/api/payments/transfer/5";
String consumerKey = "ck_yR...YnQ";
byte[] requestBody = "{}".getBytes(StandardCharsets.UTF_8);
TransferSignatureBuilder transferSignature = new TransferSignatureBuilder(privateKey);
String authorizationHeader = transferSignature.createAuthorizationHeader(
url
, consumerKey
, requestBody
);
System.out.println("authorizationHeader = " + authorizationHeader);
}
private static class TransferSignatureBuilder {
private final PrivateKey privateKey;
public TransferSignatureBuilder(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public String createAuthorizationHeader(String url, String customerKey, byte[] requestBody) {
ParamStringBuilder paramStringBuilder = new ParamStringBuilder();
String paramString = paramStringBuilder
.add("oauth_body_hash" , OauthFields.calculateSha256(requestBody))
.add("oauth_consumer_key" , customerKey)
.add("oauth_nonce" , OauthFields.createNonce())
.add("oauth_signature_method" , "RSA-SHA256")
.add("oauth_timestamp" , OauthFields.createTimestamp())
.add("oauth_version" , "1.0")
.buildParamString();
System.out.println("paramString = " + paramString);
String signatureBaseString = new SignatureBaseStringBuilder()
.add("POST")
.add(url)
.add(paramString)
.buildSignatureBaseString();
System.out.println("signatureBaseString = " + signatureBaseString);
String signatureBase64 = RsaSigner.sign(privateKey, signatureBaseString.getBytes(StandardCharsets.UTF_8));
System.out.println("signatureBase64 = " + signatureBase64);
return new AuthorizationHeaderBuilder()
.addParameters(paramStringBuilder.getParameters())
.addParameter("oauth_signature", signatureBase64)
.buildHeader();
}
}
private static class RsaSigner {
static String sign(PrivateKey privateKey, byte[] bytes) {
try {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(bytes);
byte[] signatureBytes = signer.sign();
return Base64.getEncoder().encodeToString(signatureBytes);
} catch (GeneralSecurityException e) {
throw new IllegalStateException("Unable to RSA-SHA256 sign the given string with the provided key", e);
}
}
}
private static class AuthorizationHeaderBuilder {
private final StringBuilder sb = new StringBuilder();
public AuthorizationHeaderBuilder addParameters(SortedMap<String, String> parameters) {
parameters.forEach(this::addParameter);
return this;
}
public AuthorizationHeaderBuilder addParameter(String name, String value) {
sb.append(",")
.append(name)
.append("=\"")
.append(value)
.append("\"");
return this;
}
public String buildHeader() {
return "OAuth " + sb.substring(1);
}
}
private static class OauthFields {
private static String createTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000L);
}
private static String createNonce() {
byte[] random = new byte[16];
ThreadLocalRandom.current().nextBytes(random);
return Base64.getUrlEncoder().withoutPadding().encodeToString(random);
}
private static String calculateSha256(byte[] requestBody) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to obtain SHA-256 message digest", e);
}
byte[] hash = digest.digest(requestBody);
return Base64.getEncoder().encodeToString(hash);
}
}
private static class SignatureBaseStringBuilder {
private final StringBuilder sb = new StringBuilder();
public SignatureBaseStringBuilder add(String parameter) {
sb.append("&")
.append(PercentEncoder.percentEncode(parameter));
return this;
}
public String buildSignatureBaseString() {
return sb.substring(1);
}
}
private static class ParamStringBuilder {
private final SortedMap<String, String> parameters = new TreeMap<>();
private final StringBuilder sb = new StringBuilder();
public ParamStringBuilder add(String name, String value) {
parameters.put(name, value);
sb.append("&")
.append(PercentEncoder.percentEncode(name))
.append("=")
.append(PercentEncoder.percentEncode(value));
return this;
}
public String buildParamString() {
return sb.substring(1);
}
public SortedMap<String, String> getParameters() {
return parameters;
}
}
private static class PercentEncoder {
public static String percentEncode(String text) {
try {
return URLEncoder.encode(text, "utf-8")
// URLEncoder encodes " " (space) to "+"
// RFC3986 encodes " " (space) to "%20"
.replace("+" , "%20")
// URLEncoder unreserved = ALPHA / DIGIT / "-" / "." / "_" / "*"
// RFC3986 unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
// so we fix it
.replace("*" , "%2A")
.replace("%7E", "~" );
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Unable to decode URL using utf-8 encoding", e);
}
}
}
private static class PrivateKeyLoader {
private static PrivateKey loadPrivateKey(File privateKeyFile) {
KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cannot create key factory for RSA", e);
}
byte[] bytes;
try {
bytes = Base64.getDecoder().decode(
readAllLines(privateKeyFile.toPath())
.stream()
.filter(line -> !line.contains(" KEY-----"))
.collect(Collectors.joining()));
} catch (IOException e) {
throw new UncheckedIOException("Cannot read private key file from " + privateKeyFile.getAbsolutePath(), e);
}
try {
return keyFactory.generatePrivate(
new PKCS8EncodedKeySpec(
bytes
)
);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Cannot create private key from bytes", e);
}
}
}
}