Signature Example¶
Signature Example Source Code¶
Each example below, when run, will print the following:
No consumer: 0cv%2BYkrN9CMBMnYELldaPOt7JZQAksAcsXt9G8hoFbM%3D
Single consumer identifier: m57MtdATa3zuSEKcK4FOaP0UGOQ7TQjD3KSFI8GeoAI%3D
Multiple consumer identifiers: A4Kvwaw%2BZ4zNsMfKDi%2FD6ttuyL3UvUbN67P26EiaSQU%3D
These represent the results of the first three test cases in the CSV file that can be found in the Test Cases section. For an explanation of how the algorithm for calculating HMAC signatures works, see Authentication.
Python 3¶
import binascii
import hashlib
import hmac
import urllib.parse
def make_signature(key, secret, expires, consumer):
if consumer:
message = f"{key}\n{consumer}\n{expires}"
else:
message = f"{key}\n{expires}"
mac = hmac.new(secret, message.encode("utf-8"), digestmod=hashlib.sha256)
return urllib.parse.quote_plus(binascii.b2a_base64(mac.digest()).rstrip())
# Timestamp at which this signature expires.
expires = "1637763396"
# The key, provided by Yocuda.
key = "8IMF3WFX4Z11I8WTPL2P"
# The secret, provided by Yocuda, is 32 bytes of binary information, encoded
# using base64. Please ensure that you are working with the decoded value!
secret = binascii.a2b_base64("O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=")
print("No consumer: " + make_signature(key, secret, expires, None))
print("Single consumer identifier: " + make_signature(key, secret, expires, "1001"))
print(
"Multiple consumer identifiers: "
+ make_signature(key, secret, expires, "1001,1002")
)
Ruby¶
require 'base64'
require 'openssl'
require 'cgi'
def make_signature(key, secret, expires, consumer)
if consumer
message = "#{key}\n#{consumer}\n#{expires}"
else
message = "#{key}\n#{expires}"
end
hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), secret, message)
signature = CGI.escape(Base64.encode64(hmac).delete("\n"))
end
# Timestamp at which this signature expires.
expires = "1637763396"
# The key, provided by Yocuda.
key = "8IMF3WFX4Z11I8WTPL2P"
# The secret, provided by Yocuda, is 32 bytes of binary information, encoded
# using base64. Please ensure that you are working with the decoded value!
secret = Base64.decode64("O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=")
puts "No consumer: " +
make_signature(key, secret, expires, nil)
puts "Single consumer identifier: " +
make_signature(key, secret, expires, "1001")
puts "Multiple consumer identifiers: " +
make_signature(key, secret, expires, "1001,1002")
PHP¶
<?php
function makeSignature($key, $secret, $expires, $consumer) {
if ($consumer) {
$message = "$key\n$consumer\n$expires";
} else {
$message = "$key\n$expires";
}
$mac = base64_encode(hash_hmac('sha256', $message, $secret, true));
return urlencode($mac);
}
// Timestamp at which this signature expires.
$expires = '1637763396';
// The key, provided by Yocuda.
$key = '8IMF3WFX4Z11I8WTPL2P';
// The secret, provided by Yocuda, is 32 bytes of binary information, encoded
// using base64. Please ensure that you are working with the decoded value!
$secret = base64_decode('O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=');
echo 'No consumer: ',
makeSignature($key, $secret, $expires, NULL), "\n";
echo 'Single consumer identifier: ',
makeSignature($key, $secret, $expires, '1001'), "\n";
echo 'Multiple consumer identifiers: ',
makeSignature($key, $secret, $expires, '1001,1002'), "\n";
C#¶
using System;
using System.Security.Cryptography;
using System.Text;
using System.Web;
namespace Yocuda
{
internal class Program
{
public static string MakeSignature(string key, byte[] secret,
string expires, string consumer) {
string message;
if (consumer != null) {
message = $"{key}\n{consumer}\n{expires}";
} else {
message = $"{key}\n{expires}";
}
byte[] mac = new HMACSHA256(secret).
ComputeHash(Encoding.ASCII.GetBytes(message));
return Uri.EscapeDataString(Convert.ToBase64String(mac));
}
private static void Main(string[] args)
{
// Timestamp at which this signature expires.
var expires = "1637763396";
// The key, provided by Yocuda.
var key = "8IMF3WFX4Z11I8WTPL2P";
// The secret, provided by Yocuda, is 32 bytes of binary
// information, encoded using base64. Please ensure that you
// are working with the decoded value!
byte[] secret = Convert.FromBase64String(
"O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=");
Console.WriteLine(
"No consumer: "
+ MakeSignature(key, secret, expires, null));
Console.WriteLine(
"Single consumer identifier: "
+ MakeSignature(key, secret, expires, "1001"));
Console.WriteLine(
"Multiple consumer identifiers: "
+ MakeSignature(key, secret, expires, "1001,1002"));
}
}
}
Swift¶
import CryptoKit // iOS 13.0+ or macOS 10.15+
import Foundation
// Any + and = characters in the base64 encoded signature must
// be percent encoded. Encoding the / is optional
var cs = CharacterSet.urlQueryAllowed
cs.remove("+")
cs.remove("=")
cs.remove("/")
func makeSignature(key: String, secret: SymmetricKey, expires: String,
consumer: String?) -> String
{
var message: String
if consumer != nil {
message = key + "\n" + consumer! + "\n" + expires
} else {
message = key + "\n" + expires
}
let mac = HMAC<SHA256>.authenticationCode(
for: message.data(using: .utf8)!, using: secret
)
let signature = Data(mac)
.base64EncodedString()
.addingPercentEncoding(withAllowedCharacters: cs)!
return signature
}
// Timestamp at which this signature expires.
let expires = "1637763396"
// The key, provided by Yocuda.
let key = "8IMF3WFX4Z11I8WTPL2P"
// The secret, provided by Yocuda, is 32 bytes of binary information, encoded
// using base64. Please ensure that you are working with the decoded value!
let secret = SymmetricKey(
data: Data(base64Encoded: "O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=")!
)
print("No consumer: " +
makeSignature(key: key, secret: secret, expires: expires, consumer: nil))
print("Single consumer identifier: " + makeSignature(
key: key,
secret: secret,
expires: expires,
consumer: "1001"
))
print("Multiple consumer identifiers: " + makeSignature(
key: key,
secret: secret,
expires: expires,
consumer: "1001,1002"
))
Java¶
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Signature {
private static final String HMAC_SHA256 = "HmacSHA256";
public static String MakeSignature(String key, byte[] secret,
String expires, String consumer)
throws Exception {
final String message;
final Mac sha256Hmac;
if(consumer != null) {
message = String.format("%s\n%s\n%s", key, consumer, expires);
} else {
message = String.format("%s\n%s", key, expires);
}
sha256Hmac = Mac.getInstance(HMAC_SHA256);
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, HMAC_SHA256);
sha256Hmac.init(secretKeySpec);
byte[] mac = sha256Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return URLEncoder.encode(Base64.getEncoder().encodeToString(mac), "UTF-8");
}
public static void main(String[] args) throws Exception {
// Timestamp at which this signature expires.
String expires = "1637763396";
// The key, provided by Yocuda.
String key = "8IMF3WFX4Z11I8WTPL2P";
// The secret, provided by Yocuda, is 32 bytes of binary
// information, encoded using base64. Please ensure that you
// are working with the decoded value!
byte[] secret = Base64.getDecoder().decode(
"O4x13cuK5T+lbd72NKd4D4dWFJaDkdim6gvdjLziQFY=");
System.out.println(
"No consumer: "
+ MakeSignature(key, secret, expires, null));
System.out.println(
"Single consumer identifier: "
+ MakeSignature(key, secret, expires, "1001"));
System.out.println(
"Multiple consumer identifiers: "
+ MakeSignature(key, secret, expires, "1001,1002"));
}
}
Note
Certain secrets (in particular those that can retrieve customer data, such as receipts or identifiers) should not be stored on customer devices, including mobile phones, tables, and web-browsers. Where customer data needs to be accessed from a customer’s device, please refer to Generating Signatures on Behalf of Consumers for documentation on implementing an authentication server which can generate customer specific time limited signed URLs.
Signature request¶
The signature, the key, the consumer, and the expires values are embedded into the query string of a request along with any other query parameters required for that request. It is vitally important never to embed the shared secret into a request.
Example requests:
This example shows a request to the GET /receipts (v1) resource, in this case taking no extra parameters, using the values from the signature example above:
GET /request?expires=1363190856&consumer=990000000001&key=YHK2SX77A3212KUPXPJB&signature=6x8lyyNAhmy855UF%2F%2B6GDAa8m6kgtS7ARD4CuAKsjRQ%3D HTTP/1.1
Host: api.ereceipts.co.uk
Accept: application/json
The next example shows a GET
request to the /receipts
resource, getting
the next 10 receipts after the receipt with the id 513e2391ee5e525ff9000000
:
GET /request?expires=1363190856&consumer=990000000001&key=YHK2SX77A3212KUPXPJB&signature=6x8lyyNAhmy855UF%2F%2B6GDAa8m6kgtS7ARD4CuAKsjRQ%3D&older=513e2391ee5e525ff9000000&count=10 HTTP/1.1
Host: api.ereceipts.co.uk
Accept: application/json
Receipts from multiple consumers can be requested as follows:
GET /request?expires=1363190856&consumer=990000000001%2C990000000002&key=YHK2SX77A3212KUPXPJB&signature=0VJf7sHS8QNvfL0KWJgvvBiEAuaP2yobzdtt3Y6KH4M%3D HTTP/1.1
Host: api.ereceipts.co.uk
Accept: application/json
Test Cases¶
Download the signature_test_cases.csv
to check your implementation of the signature algorithm.
This UTF-8 encoded CSV file contains one test case in each row. Columns that contain commas are quoted using double quotes. The following columns are present in the file: key
, secret
, consumer
, expires
, signature
, and example_url
.
To use the test cases, provide key
, secret
, consumer
, and expires
to your signature generating function and check the resulting signature against the signature
column (containing the base64 encoded signature.) You can also check that your application generates correctly quoted signatures by checking against the signature query parameter in the example_url
column, which contains the base64 and percent-encoded signature, as generated by the above examples.