# Authentication Guide
# Request URL
// Depends on specific AI capability
https://rest-api.xfyun.cn/v2/***
# Signature and Authentication
API calls must sign HTTP requests. The server identifies and validates users through signatures. Users apply to the server for a credential, which is a key/secret key pair. The client concatenates Method, Accept, Date, other Header fields, and URL in a specified manner, then signs the request using a hash algorithm (such as HMAC-SHA256) and the user's secret. Finally, place the key, algorithm used, header fields involved in signing, and the calculated signature into the "Authorization" header field.
# Request Header Example:
Content-Type: application/json
Accept: application/json;version=1.0
Date: Tue, 26 Jun 2018 12:27:03 UTC
Host: "your host"
Digest: SHA-256=xxxxxxxxxxxxxxxxxxxxxxxx
Authorization: hmac api_key="your_key", algorithm="hmac-sha256", headers="host date request-line", signature="base64_digest"
# Authentication Method
The client needs to use the hmac-sha256 algorithm to calculate a digest of relevant request parameters to generate a signature, building the Authorization header. The server parses the Authorization header and calculates the signature in the same way, comparing whether the signatures match. If they match, authentication passes.
# Header Detailed Description
| Signature Parameter | Description |
|---|---|
| Date | Request date, UTC timezone. Format example: Tue, 26 Jun 2018 12:27:03 UTC |
| Host | Request host, this header is required for signature calculation |
| Authorization | Authentication parameter, specific construction method as follows |
| Digest | Body digest, calculation method is "SHA256="+sha256(${body}) |
# Signature Generation Formula
api_key="${api_key}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"
Explanation:
- api_key: API key applied from the open platform
- algorithm: Signature calculation algorithm, supports hmac-sha256
- headers: Parameters required for signature, must include at least host, date, and request-line
- signature: Calculated signature
# Authentication Detailed Process
# 1. Build Signature Origin String (signature_origin_str)
The format of signature_origin_str is:
host: ${host}
date: ${date}
${method} ${path} HTTP/1.1
digest: ${digest}
- host: Request host header, e.g., iat-api.xfyun.cn. Must be placed in the request header, and the host used in signature calculation must be completely consistent with the header in the HTTP message.
- date: Current timestamp, format is Wed, 08 Jun 2022 08:12:15 UTC. Must be placed in the request header.
- method: Request method. Supports GET, POST, DELETE, PATCH, PUT. Must be consistent with the current HTTP request method.
- path: Request path. Does not include the query string part ('?' and following). For example, when URL is '/v2/iat?a=b&c=d', only take '/v2/iat' as the path value.
- digest: Request body digest part, calculation method is "SHA256="+sha256(${body})
Note: There is a space after ':', '\n' is a newline character. HTTP/1.1 is the HTTP protocol version. If the client uses HTTP/1.0 protocol, the value should be changed to HTTP/1.0.
# 2. Calculate Signature
Calculation method: Use hmac-sha256 algorithm to calculate the signature of signature_origin_str, and perform base64 encoding.
signature = base64(hmac-sha256(${signature_origin_str}, ${api_secret}))
Where api_secret is the secret part of the key pair obtained from the platform side, and signature_origin_str is the parameters concatenated in the previous step.
# 3. Concatenate Authorization Header
authorization_raw = api_key="${api_key}", algorithm="hmac-sha256", headers="host date request-line digest", signature="${signature}"
# Signature Generation Example
Suppose there is the following request:
- Request URL: http://iat-api.xfyun.cn/v2/iat
- Request method: POST
- User's api_key = 5ccdf2b4d1b5cdf81846697bf8bcd05d
- api_secret = B00TFRS9KDCfTrdX5JQwhVSXaFoHLy34
- Current time: Wed, 08 Jun 2022 09:00:06 UTC
- Request body content: hello world
Step 1: Then signature_origin_str should be concatenated as:
host: iat-api.xfyun.cn
date: Wed, 08 Jun 2022 09:00:06 UTC
POST /v2/iat HTTP/1.1
digest: SHA256=uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=
Step 2: The signature calculated using signature_origin_str:
rRU2FA174RdsqpdxGzrLmJ6C1CPk5GgfP7bUQToxQIw=
Step 3: Then the authorization header is:
api_key="5ccdf2b4d1b5cdf81846697bf8bcd05d", algorithm="hmac-sha256", headers="host date request-line", signature="rRU2FA174RdsqpdxGzrLmJ6C1CPk5GgfP7bUQToxQIw="
Step 4: The final request headers are:
Authorization: api_key="5ccdf2b4d1b5cdf81846697bf8bcd05d", algorithm="hmac-sha256", headers="host date request-line", signature="VhEap7PkvX7ujjx8DjBtkRZFwQDIEOc62EM+M9N+pf8="
Host: iat-api.xfyun.cn
Date: Wed, 08 Jun 2022 08:12:15 UTC
Digest: SHA256=uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=
Users can use this example to verify whether their signature algorithm implementation is correct.
# Key Function Description
| Function Name | Description |
|---|---|
| hmac-sha256 | A standard signature algorithm. Provide a key and data to be signed, and it can calculate the signature digest. The calculation result is raw bytes and should not be encoded. |
| base64 | A standard method to encode bytes into visible strings. Note to use standard base64, not base64_url. |
| sha256 | sha256 signature algorithm |
# Authentication Failure Response Example
HTTP/1.1 403 Forbidden
Date: Thu, 06 Dec 2018 07:55:16 GMT
Content-Length: 116
Content-Type: text/plain; charset=utf-8
{
"message": "HMAC signature does not match"
}
# Possible Causes of Authentication Failure Analysis
Users can determine where the authentication problem lies through the httpCode returned by the server and the message field in the response body.
| HTTP Code | Message | Cause |
|---|---|---|
| 401 | Unauthorized | User did not pass the Authorization header |
| 401 | HMAC signature cannot be verified, fail to retrieve credential | Server cannot query the api_key. Check if the apikey is correct. |
| 401 | HMAC signature cannot be verified, enforce header 'host' not used for HMAC Authentication | Server failed to parse 'Authorization' header. Check if the format of 'Authorization' header meets document requirements. |
| 403 | HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication | Date header format does not match document requirements or time deviation from server exceeds 300s. Check if the client machine timestamp is synchronized with the internet, or if the timezone is correct. |
| 403 | not found | Cannot find the request address. Check if the request address is correct. |
| 401 | HMAC signature does not match | Server calculated signature does not match client calculated signature value. There may be multiple causes. Try the following solutions: |
Troubleshooting Steps:
- Check if api_secret is correct
- Check if signature_origin_str format is concatenated correctly. The correct format is described in the document above. You can print parameters for comparison.
- Check if the hmac-sha256 calculated signature length is 44. If it is 88, the string used for base64 calculation has already been hex-encoded. You need to use raw unencoded byte stream for base64 encoding.
- Check if nginx proxy is used. Nginx defaults to using HTTP 1.0 for proxy requests, which causes the server to get HTTP version HTTP/1.0, resulting in signature calculation mismatch. Need to set nginx to use HTTP/1.1 protocol to request the server.
- If none of the above methods solve the issue, try using packet capture tools to analyze the packet. Some frameworks may send HTTP messages that differ slightly from the code, such as host, path, and HTTP version number may not match expectations. Focus on checking whether these parameters in the packet match the corresponding parameters used in code for concatenating signature_origin_str.
# Pseudocode
func assembleRequestHeader(requestUrl, method, body, apikey, apisecret) {
url = urlparse(requestUrl)
host = url.host
path = url.path
date = now().format('Tue, 26 Jun 2018 12:27:03 UTC')
request-line = "$method $path HTTP/1.1"
signature_headers = "host date request-line"
signature_strs = "host: $host\ndate: $date\n$request-line"
digest = ""
if body != nil:
signature_headers = "host date request-line digest"
digest = "SHA-256=" + base64(sha256(body))
signature_strs = "host: $host\ndate: $date\n$request-line\ndigest: $digest"
else:
signature_strs = "host: $host\ndate: $date\n$request-line"
signature = base64(hmac-sha256(signature_strs, apisecret))
authorization = 'api_key="$apikey", algorithm="hmac-sha256", headers="$signature_headers", signature="$signature"'
return {
"Host": host,
"Date": date,
"Digest": digest,
"Authorization": authorization
}
}
# Sample Code for Signature Generation
# Golang
package iflyauth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/url"
"time"
)
// Build authentication headers
// @requestUrl: like http://api.xfyun.cn
// @method: GET, POST, etc....
// @body: request body
func NewAuthHeaders(requestUrl, method string, apiKey, apiSecret string, body []byte) map[string]string {
bodySign := ""
if body == nil {
bodySign = sha256Base64([]byte(nil))
} else {
bodySign = sha256Base64(body)
}
bodySign = "SHA256=" + bodySign
u, err := url.Parse(requestUrl)
if err != nil {
panic("parse url error" + err.Error())
}
host := u.Host
date := time.Now().UTC().Format(time.RFC1123)
if u.Path == "" {
u.Path = "/"
}
requestLine := method + " " + u.Path + " HTTP/1.1"
signUrl := fmt.Sprintf("host: %s\ndate: %s\n%s\ndigest: %s", host, date, requestLine, bodySign)
signature := hmacSha256Base64([]byte(apiSecret), []byte(signUrl))
authorization := fmt.Sprintf(`api_key="%s", algorithm="hmac-sha256", headers="host date request-line digest", signature="%s"`, apiKey, signature)
return map[string]string{
"host": host,
"date": date,
"authorization": authorization,
"digest": bodySign,
}
}
func sha256Base64(b []byte) string {
h := sha256.New()
h.Write(b)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func hmacSha256Base64(secret []byte, data []byte) string {
h := hmac.New(sha256.New, secret)
h.Write(data)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
# Java
/**
* Calculate header parameters needed for signature (HTTP interface)
* @param requestUrl like 'http://rest-api.xfyun.cn/v2/iat'
* @param apiKey
* @param apiSecret
* @method request method POST/GET/PATCH/DELETE etc....
* @param body HTTP request body
* @return header map, contains all headers should be set when accessing api
*/
public static Map<String, String> assembleRequestHeader(String requestUrl, String apiKey, String apiSecret, String method, byte[] body) {
URL url = null;
try {
url = new URL(requestUrl);
// Get date
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = format.format(new Date());
// Calculate body digest (SHA256)
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.update(body);
String digest = "SHA256=" + Base64.getEncoder().encodeToString(instance.digest());
// date = "Thu, 19 Dec 2024 07:47:57 GMT";
String host = url.getHost();
int port = url.getPort(); // port > 0 means url contains port
if (port > 0) {
host = host + ":" + port;
}
String path = url.getPath();
if ("".equals(path) || path == null) {
path = "/";
}
// Build parameters needed for signature calculation
StringBuilder builder = new StringBuilder()
.append("host: ").append(host).append("\n")
.append("date: ").append(date).append("\n")
.append(method).append(" ").append(path).append(" HTTP/1.1").append("\n")
.append("digest: ").append(digest);
Charset charset = Charset.forName("UTF-8");
System.out.println(builder.toString());
// Use hmac-sha256 to calculate signature
Mac mac = Mac.getInstance("hmacsha256");
// System.out.println(builder.toString());
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
String sha = Base64.getEncoder().encodeToString(hexDigits);
// Build header
String authorization = String.format("hmac-auth api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line digest", sha);
Map<String, String> header = new HashMap<String, String>();
header.put("authorization", authorization);
header.put("host", host);
header.put("date", date);
header.put("digest", digest);
System.out.println("header " + header.toString());
return header;
} catch (Exception e) {
throw new RuntimeException("assemble requestHeader error:" + e.getMessage());
}
}
# JavaScript
function assembleRequestHeader(host, path, method, apiKey, apiSecret, body) {
var date = new Date().toGMTString()
var algorithm = 'hmac-sha256'
var headers = 'host date request-line digest'
var digest = "SHA256=" + CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(body))
var signatureOrigin = `host: ${host}\ndate: ${date}\n${method} ${path} HTTP/1.1\ndigest: ${digest}`
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
var signature = CryptoJS.enc.Base64.stringify(signatureSha)
var authorization = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
return {
// 'Host': host,
'X-Date': date, // In JS, may not be able to set Date header, use X-Date
'Authorization': authorization,
'Digest': digest,
}
}
var headers = assembleRequestHeader('rest-api-gz.xfyun.cn', '/v2/tts', 'POST', 'xxxxxxxxxxxxxxx', 'xxxxxxxxxxxxxxx', '')
# Python 3
from datetime import datetime
from wsgiref.handlers import format_date_time
from time import mktime
import hashlib
import base64
import hmac
from urllib.parse import urlparse
import sys
# Calculate sha256 and encode to base64
def sha256base64(data):
sha256 = hashlib.sha256()
sha256.update(data)
digest = base64.b64encode(sha256.digest()).decode(encoding='utf-8')
return digest
# Build auth request url
def assemble_auth_header(requset_url, method="GET", api_key="", api_secret="", body=""):
u = urlparse(requset_url)
host = u.hostname
path = u.path
now = datetime.now()
date = format_date_time(mktime(now.timetuple()))
digest = "SHA256=" + sha256base64(body.encode())
# date = "Thu, 12 Dec 2019 01:57:27 GMT"
signature_origin = "host: {}\ndate: {}\n{} {} HTTP/1.1\ndigest: {}".format(host, date, method, path, digest)
# print(signature_origin)
signature_sha = hmac.new(api_secret.encode('utf-8'), signature_origin.encode('utf-8'),
digestmod=hashlib.sha256).digest()
signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
authorization = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
api_key, "hmac-sha256", "host date request-line digest", signature_sha)
# print(authorization_origin)
headers = {
"host": host,
"date": date,
"authorization": authorization,
"digest": digest,
}
return headers
requrl = "http://rest-api.xfyun.cn/v2/itr"
import requests
import json
import time
body = {
"common": {},
"business": {},
"data": {}
}
now = time.time()
bds = json.dumps(body)
headers = assemble_auth_header(requrl, method="POST", api_key="xxxxxxxxx",
api_secret="xxxxxxxxxxxxx", body=bds)
resp = requests.post(requrl, headers=headers, data=bds)
print(resp.status_code, resp.text)
# PHP
class http_test {
function tocurl($url, $header, $content) {
$ch = curl_init();
if (substr($url, 0, 5) == 'https') {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Skip certificate check
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // Check if SSL encryption algorithm exists in certificate
curl_setopt($ch, CURLOPT_SSLVERSION, 1);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $url);
if (is_array($header)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($ch, CURLOPT_POST, true);
if (!empty($content)) {
if (is_array($content)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($content));
} else if (is_string($content)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
}
}
$response = curl_exec($ch);
$error = curl_error($ch);
// var_dump($error);
if ($error) {
die($error);
}
$header = curl_getinfo($ch);
curl_close($ch);
$data = array('header' => $header, 'body' => $response);
return $data;
}
function xfyun() {
$app_id = "XXXXX";
$api_sec = "XXXXXXXXX";
$api_key = "XXXXXXXXX";
$resource = "resource/xx";
$url = "xx";
$host = "xx.xxx.cn"; # Request host
$path = "/v2/xxx"; # Request path
// Body assembly
$body = json_encode($this->getBody($app_id, $resource));
// Assemble HTTP request headers
// $date = gmstrftime("%a, %d %b %Y %H:%M:%S %Z", time());
$date = gmdate('D, d M Y H:i:s') . ' GMT';
$digestBase64 = "SHA-256=" . base64_encode(hash("sha256", $body, true));
$builder = sprintf("host: %s\ndate: %s\nPOST %s HTTP/1.1\ndigest: %s", $host, $date, $path, $digestBase64);
$sha = base64_encode(hash_hmac("sha256", $builder, $api_sec, true));
$authorization = sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", $api_key, "hmac-sha256", "host date request-line digest", $sha);
$header = [
"Authorization: " . $authorization,
'Content-Type: application/json',
'Accept: application/json;version=1.0',
'Host: rest-api.xfyun.cn', // Replace with actual domain host
'Date: ' . $date,
'Digest: ' . $digestBase64
];
$response = $this->tocurl($url, $header, $body);
var_dump($response['body']);
}
}