Алгоритм формирования подписи для API v 2.0
Создать нормализованную строку запроса для использования на следующих стадиях:
- Отсортировать параметры по имени в utf8, сравнивая побайтово. Параметры берутся из GET URI или из тела POST запроса (когда
ContentType = application/xwwwformurlencoded
) - URLкодировать имена и значения параметров по следующим правилам:
- Не кодировать определённые в RFC 3986 незарезервированные символы. Эти символы:
Az
,az
,09
, минус-
, подчёркивание_
, точка.
и тильда~
. - Все остальные символы должны быть закодированы как %XY, где X и Y это шестнадцатеричные символы от 0 до 9 и от A до F (заглавные). Расширенные utf8 символы кодируются как %XY%ZA…
- Пробел кодируется как %20 (и не как +, как обычно делается в URL)
- Кодированные имена параметров отделяются от кодированных значений знаком равно (=, ASCIIкод 61), даже если параметр имеет пустое значение.
- Пары имя – значение разделяются символом амперсанда (
&
, ASCII – код 38).
- Не кодировать определённые в RFC 3986 незарезервированные символы. Эти символы:
- Создать строку для подписи в соответствии со следующей псевдо–грамматикой (где
\n
это ASCII – символов перевода строки):StringToSign = HTTPVerb + "\n" +
ValueOfHostHeaderInLowercase + "\n" +
HTTPRequestURI + "\n" +
CanonicalizedQueryString
# HTTPVerb - метод POST, GET, PUT или DELETE.
# ValueOfHostHeaderInLowercase - параметр host из заголовка HTTP запроса.
# HTTPRequestURI - компонент URI, абсолютный путь до, но не включая GET-параметров.
# Для пустого пути ожидается '/'.
# CanonicalizedQueryString - строка с предыдущего шага. - Рассчитать совместимый с RFC 2104 HMAC по только что созданной строке StringToSign, используя секретный ключ партнёра как ключ алгоритма, и SHA256 как способ хэширования.
- Сконвертировать полученную подпись в base64.
- Использовать результат как значение параметра
check
.
Пример расчёта
- PHP
- Python
- JAVA
function http_build_query_rfc_3986($queryData, $argSeparator='&')
{
$r = '';
$queryData = (array) $queryData;
if(!empty($queryData))
{
foreach($queryData as $k=>$queryVar)
{
$r .= $argSeparator.$k.'='.rawurlencode($queryVar);
}
}
return trim($r,$argSeparator);
}
function sign($method, $url, $params, $secretKey, $skipPort=False)
{
ksort($params, SORT_LOCALE_STRING);
$urlParsed = parse_url($url);
$path = $urlParsed['path'];
$host = isset($urlParsed['host'])? $urlParsed['host']: "";
if (isset($urlParsed['port']) && $urlParsed['port'] != 80) {
if (!$skipPort) {
$host .= ":{$urlParsed['port']}";
}
}
$method = strtoupper($method) == 'POST'? 'POST': 'GET';
$data = implode("\n",
array(
$method,
$host,
$path,
http_build_query_rfc_3986($params)
)
);
$signature = base64_encode(
hash_hmac("sha256",
"{$data}",
"{$secretKey}",
TRUE
)
);
return $signature;
}
// Пример вызова, где $req - массив с параметрами транзакции:
sign("GET", "https://partner.life-pay.ru/alba/input/", $req, 'secret');
from urllib.parse import urlparse,quote
import hashlib
import urllib
import base64
import hmac
def sign(method, url, params, secret_key, exclude=['check', 'mac']):
"""
Типовой метод для подписи HTTP запросов
"""
url_parsed = urlparse(url)
keys = [param for param in params if param not in exclude]
keys.sort()
result = []
for key in keys:
value = quote(
(params.get(key) or '').encode('utf-8'),
safe='~'
)
result.append('{}={}'.format(key, value))
data = "\n".join([
method,
url_parsed.hostname,
url_parsed.path,
"&".join(result)
])
secrkey = secret_key.encode('utf-8')
mesg=data.encode('utf-8')
print(secrkey,mesg)
digest = hmac.new(
secrkey,
mesg,
hashlib.sha256
).digest()
signature = base64.b64encode(digest)
print(signature.decode('utf-8'))
return signature
# Пример вызова
sign("GET", "https://partner.life-pay.ru/alba/input/", {'login': 'newlogin~_-.'}, '165165165sd');
info
Пробел кодируется как %20. И не как +, как обычно делается в URL.
package ru.life-paycb.alba;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.net.URI;
import java.net.URISyntaxException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
/**
* Подпись версии 2.0+
*/
public class AlbaSigner {
public static String sign(String method, String url, Map<String, String> params, String secretKey)
throws URISyntaxException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
URI uri = new URI(url);
List keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder sb = new StringBuilder();
for (String key: keys) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(String.format("%s=%s", key, URLEncoder.encode(params.get(key), "UTF-8")));
}
String urlParameters = sb.toString();
String data = method.toUpperCase() + "\n" +
uri.getHost() + "\n" +
uri.getPath() + "\n" +
urlParameters;
Mac hmacInstance = Mac.getInstance("HmacSHA256");
Charset charSet = Charset.forName("UTF-8");
SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(charSet.encode(secretKey).array(), "HmacSHA256");
hmacInstance.init(keySpec);
return DatatypeConverter.printBase64Binary(hmacInstance.doFinal(data.getBytes("UTF-8")));
}
}