Виды ключей

Для полноценной работы необходимо иметь 2 вида ключей:

  1. Ключ для подписи запросов от торговца к Flexo. Торговец должен сгенерировать пару публичный-приватный ключ (смотри ниже). Приватный ключ хранится в секрете, им подписываются исходящие запросы. Публичный ключ передается в Flexo, с помощью него запросы проверяются на подлинность.

  2. Ключ для проверки уведомлений от Flexo к торговцу. Flexo подписывает уведомления своим приватным ключом, а торговец проверяет их публичным ключом. Публичный ключ можно взять со страницы описания уведомлений. Технически эту проверку можно опустить, но мы настоятельно не рекомендуем этого делать в целях безопасности.

ПРИМЕЧАНИЕ: Для тестовых операций ключ уже сгенерирован и на ходится на странице: Тестовый аккаунт.

Пример генерации RSA ключа для боевого аккаунта

openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -outform PEM -out public_key.pem

Формирование подписи

  1. Исходная строка для подписи получается путем конкатенации через \n следующих значений:

    1. HTTP метода в верхнем регистре

    2. URI (*)

    3. тела запроса

  2. Из строки формируется hash с помощью вашего приватного RSA ключа

(*) - URI берется без хоста, параметры должны быть UrlEncoded

Пример строки для подписи

var rawString = "POST\n/card/1-1/operations/purchase\n{}"

GET запрос: /card/1-1/operations/status?externalId=id#2&example=stub%stub
var rawString = "GET\n/card/1-1/operations/status?externalId=id%232&example=stub%25stub\n"

Пример на java

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public static PrivateKey loadPemPrivateKey(String privateKey)
            throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
    String privateKeyContent = privateKey
       .replaceAll("(-+BEGIN RSA PRIVATE KEY-+\\r?\\n?|-+END RSA PRIVATE KEY-+\\r?\\n?)", "")
       .replace("\n", "");

    byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent);

    Security.addProvider(new BouncyCastleProvider());
    KeyFactory keyFactory = KeyFactory.getInstance("SHA256withRSA", "BC");

    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes);

    return keyFactory.generatePrivate(privateKeySpec);
}

public static String sign(String method, String uri, String body, String privateKeyString) {
    try {
        PrivateKey privateKey = loadPemPrivateKey(privateKeyString);

        byte[] messageToSign = new StringBuilder()
            .append(method)
            .append("\n")
            .append(uri)
            .append("\n")
            .append(body)
            .toString()
            .getBytes();

        Signature signer = Signature.getInstance(signatureAlgorithmName);
        signer.initSign(privateKey);
        signer.update(messageToSign);
        byte[] binarySignature = signer.sign();

        String signature = Base64.getEncoder().encodeToString(binarySignature);

        return signature;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Пример на С#

private static RSACryptoServiceProvider LoadPemPrivateKey(String pemPrivateKey)
{
    using (var privateKeyTextReader = new StringReader(pemPrivateKey)) {
        var readKeyPair = (AsymmetricCipherKeyPair) new PemReader(privateKeyTextReader).ReadObject();
        var privateKeyParams = (RsaPrivateCrtKeyParameters) readKeyPair.Private;

        var parms = new RSAParameters {
            Modulus = privateKeyParams.Modulus.ToByteArrayUnsigned(),
            P = privateKeyParams.P.ToByteArrayUnsigned(),
            Q = privateKeyParams.Q.ToByteArrayUnsigned(),
            DP = privateKeyParams.DP.ToByteArrayUnsigned(),
            DQ = privateKeyParams.DQ.ToByteArrayUnsigned(),
            InverseQ = privateKeyParams.QInv.ToByteArrayUnsigned(),
            D = privateKeyParams.Exponent.ToByteArrayUnsigned(),
            Exponent = privateKeyParams.PublicExponent.ToByteArrayUnsigned()
        };

        var cryptoServiceProvider = new RSACryptoServiceProvider();
        cryptoServiceProvider.ImportParameters(parms);

        return cryptoServiceProvider;
    }
}

public static String sign(String messageToSign, String privateKey) {
    var messageBytes = Encoding.UTF8.GetBytes(messageToSign);
    var computedHash = new SHA256Managed().ComputeHash(messageBytes);
    var rsa = LoadPemPrivateKey(privateKey);
    var signature = rsa.SignHash(computedHash, "SHA256");

    return Convert.ToBase64String(signature);
}

Пример на php

function sign(string $method, string $uri, string $data, string $private_key_pem): string
{
    openssl_sign("{$method}\n{$uri}\n{$data}", $signature, $private_key_pem, OPENSSL_ALGO_SHA256);

    return base64_encode($signature);
}