Wallet Pass Barcodes
Áskell can sign wallet-pass barcode values so scanning systems can verify that the value was generated by Áskell and has not been modified.
Format
The signed barcode value is sent in this format:
<value>:<signature>
Rules
valueis the original barcode value.signatureis theHMAC-SHA256ofvalue.Both
valueand the shared secret are treated asUTF-8bytes.signatureis sent as a lowercase hex string.The colon
:is reserved as the delimiter, sobarcode_message_templatemust not contain:.
Shared Secret
Each wallet pass template gets an automatically generated shared secret:
the secret is generated automatically when the template is created
the secret cannot be changed afterward
the secret is visible on the template details page in the dashboard and can be copied from there
Working Example
You can use this example to verify your implementation:
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
value = sub_SUB123
signature = fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105
barcode = sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105
Python
import hashlib
import hmac
def sign_wallet_barcode(value: str, secret: str) -> str:
signature = hmac.new(
secret.encode("utf-8"),
value.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return f"{value}:{signature}"
def verify_wallet_barcode(barcode: str, secret: str) -> bool:
value, signature = barcode.rsplit(":", 1)
expected = hmac.new(
secret.encode("utf-8"),
value.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature, expected)
secret = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
barcode = "sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105"
assert sign_wallet_barcode("sub_SUB123", secret) == barcode
assert verify_wallet_barcode(barcode, secret) is True
JavaScript / Node.js
const crypto = require('crypto');
function signWalletBarcode(value, secret) {
const signature = crypto
.createHmac('sha256', Buffer.from(secret, 'utf8'))
.update(Buffer.from(value, 'utf8'))
.digest('hex');
return `${value}:${signature}`;
}
function verifyWalletBarcode(barcode, secret) {
const index = barcode.lastIndexOf(':');
const value = barcode.slice(0, index);
const signature = barcode.slice(index + 1);
const expected = crypto
.createHmac('sha256', Buffer.from(secret, 'utf8'))
.update(Buffer.from(value, 'utf8'))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'utf8'),
Buffer.from(expected, 'utf8'),
);
}
const secret =
'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
const barcode =
'sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105';
console.assert(signWalletBarcode('sub_SUB123', secret) === barcode);
console.assert(verifyWalletBarcode(barcode, secret) === true);
PHP
<?php
function sign_wallet_barcode(string $value, string $secret): string {
$signature = hash_hmac('sha256', $value, $secret);
return $value . ':' . $signature;
}
function verify_wallet_barcode(string $barcode, string $secret): bool {
[$value, $signature] = explode(':', $barcode, 2);
$expected = hash_hmac('sha256', $value, $secret);
return hash_equals($expected, $signature);
}
$secret = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
$barcode = 'sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105';
assert(sign_wallet_barcode('sub_SUB123', $secret) === $barcode);
assert(verify_wallet_barcode($barcode, $secret) === true);
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
)
func signWalletBarcode(value, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(value))
signature := hex.EncodeToString(mac.Sum(nil))
return value + ":" + signature
}
func verifyWalletBarcode(barcode, secret string) bool {
index := strings.LastIndex(barcode, ":")
value := barcode[:index]
signature := barcode[index+1:]
expected := signWalletBarcode(value, secret)
expectedSignature := expected[strings.LastIndex(expected, ":")+1:]
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func main() {
secret := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
barcode := "sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105"
fmt.Println(signWalletBarcode("sub_SUB123", secret) == barcode)
fmt.Println(verifyWalletBarcode(barcode, secret))
}
C#
using System;
using System.Security.Cryptography;
using System.Text;
static string SignWalletBarcode(string value, string secret)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(value));
var signature = Convert.ToHexString(signatureBytes).ToLowerInvariant();
return $"{value}:{signature}";
}
static bool VerifyWalletBarcode(string barcode, string secret)
{
var index = barcode.LastIndexOf(':');
var value = barcode[..index];
var signature = barcode[(index + 1)..];
var expected = SignWalletBarcode(value, secret);
var expectedSignature = expected[(expected.LastIndexOf(':') + 1)..];
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signature),
Encoding.UTF8.GetBytes(expectedSignature)
);
}
var secret = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
var barcode = "sub_SUB123:fa7e0e69738cb28e457aad7e38a2aad2c66c7976b96f22d72f5d387ee6824105";
Console.WriteLine(SignWalletBarcode("sub_SUB123", secret) == barcode);
Console.WriteLine(VerifyWalletBarcode(barcode, secret));
Notes
If you want to display human-readable text below the barcode, use
barcode_alt_text_template. The signature is appended to the machine-readable barcode value, not necessarily the visible text.During verification, always use a constant-time comparison helper where the language provides one, such as
compare_digest,timingSafeEqual, orhash_equals.