-
Notifications
You must be signed in to change notification settings - Fork 3
Dutch greenpass special format
jumpjack edited this page Nov 10, 2021
·
7 revisions
Dutch greenpasses from Holland are coded differently than any other EU greenpass, hence this repository is not able (yet) to decode them.
Main reason is the different BASE45 encoding of the QRcode data: if you read the QRcode using a standard QR reader,you get a string which starts by "NL2:" rather than "HC1:".
I found these algorithms to decode such string:
def base45decode_nl(s: str) -> bytes:
base45_nl_charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
s_len = len(s)
res = 0
for i, c in enumerate(s):
f = base45_nl_charset.index(c)
w = 45 ** (s_len - i - 1)
res += f * w
return res.to_bytes((res.bit_length() + 7) // 8, byteorder='big')
- Source: https://github.com/perfacilis/php-covid-qr/blob/ab462d798a143ff2ae9bc142b7bb1ccf322dc4be/tools/base45nl.py
- Discussion: https://www.bartwolff.com/Blog/2021/08/21/decoding-the-dutch-domestic-coronacheck-qr-code
public static class DutchCoronaCheckBase45Utils
{
const int BaseSize = 45;
static readonly char[] Base45Digits =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%',
'*', '+', '-', '.', '/', ':'
};
public static byte[] Decode(string inputString)
{
var stringLength = inputString.Length;
BigInteger result = 0;
foreach (var item in inputString.Select((ch, index) => new { index, ch }))
{
var location = Array.FindIndex(Base45Digits, d => d == item.ch);
var value = BigInteger.Pow(BaseSize, stringLength - item.index - 1);
result += location * value;
}
return result.ToBigEndianByteArray();
}
public static string Encode(byte[] srcBytes)
{
var source = srcBytes.ToBigEndianInteger();
int log = (int)BigInteger.Log(source, BaseSize);
var stringBuilder = new StringBuilder(log + 1);
while (log >= 0)
{
var location = BigInteger.DivRem(source, BigInteger.Pow(BaseSize, log), out BigInteger remainder);
stringBuilder.Append(Base45Digits[(int)location]);
source = remainder;
log--;
}
return stringBuilder.ToString();
}
}
}
function bctobytestring($dec) {
$last = bcmod($dec, 256);
$remain = bcdiv(bcsub($dec, $last), 256);
if($remain == 0) {
return chr($last);
} else {
$t = bctobytestring($remain).chr($last);
return $t;
}
}
function base45nl_decode($inp) {
$base45_nl_charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
$num = "0";
for ($i = 0; $i < strlen($inp); $i++) {
$position = strpos($base45_nl_charset, $inp[$i]);
$num = bcmul($num,45);
$num = bcadd($num,$position);
}
$res = bctobytestring($num);
return $res;
}
$b458_data = str_replace("NL2:","",$data);
$asn1_data = base45nl_decode($b458_data);
$offset = 0;
// var_dump(bin2hex($asn1_data));
$asn1_object = new FG\ASN1\UnknownConstructedObject($asn1_data,$offset);
// var_dump($asn1_object);
$signature = bctobytestring($asn1_object->getChildren()[1]->getContent());
$certdata = [];
$certdata["type"]="NL";
$certdata["header"]=[];
$certdata["payload"]=[];
$attrs = ['metaData',
'isSpecimen',
'isPaperProof',
'validFrom',
'validForHours',
'firstNameInitial',
'lastNameInitial',
'birthDay',
'birthMonth'];
$attr_data = $asn1_object->getChildren()[6]->getContent();
// var_dump(bctobytestring($attr_data[0]));
// $signerdata = $asn1_header_data->getChildren();
// $certdata["header"]["version"]=$signerdata[1]->getContent();
// $certdata["header"]["kid"]=$signerdata[0]->getContent();
for ($a=1; $a<count($attr_data);$a++) {
$rawval = $attr_data[$a]->getContent();
if ((intval($rawval) & 0x01) == 0x01) {
$bytes = pack("L", intval($rawval) >> 1);
$bitsize = (strlen(dechex(intval($rawval) >> 1))*4);
$bytes = substr($bytes,0,floor(($bitsize + 7)/8));
// var_dump(floor(($bitsize + 7)/8));
// var_dump($bytes);
$certdata["payload"][$attrs[$a]]=implode(array_map("chr", $bytes));
} else {
$certdata["payload"][$attrs[$a]]=null;
}
$certdata["payload"][$attrs[$a]]=intval($rawval)>>1;
}
$certdata["signature"]=[];
$certdata["signature"]["value"]=bin2hex($signature);
$certdata["signature"]["blocked"]=getNLCertBlocked($signature);
$res = $certdata;
Source: https://github.com/breenstorm/certdata/blob/5614c0e6b3911807f61591cdcf8f4472621baccd/index.php
References for dutch greenpass:
- https://www.bartwolff.com/Blog/2021/08/21/decoding-the-dutch-domestic-coronacheck-qr-code
- https://www.perfacilis.com/blog/security/decode-corona-qr-codes-using-php.html
- https://github.com/StefH/Dutch-CoronaCheck-QR-Code-Decoder-and-Verifier#overview
- https://github.com/perfacilis/php-covid-qr/tree/ab462d798a143ff2ae9bc142b7bb1ccf322dc4be
Dutch/Holland greenpass structure: