【问题标题】:Convert QR decoding solution from Python to C# (EU DGC)将 QR 解码解决方案从 Python 转换为 C# (EU DGC)
【发布时间】:2021-08-19 11:23:15
【问题描述】:

我正在尝试将此 Python 代码转换为 C#(理想情况下为 .NET Core)。 Source

我的目标是将 QR 输入字符串转换为另一个包含 json 数据的字符串。请参阅提供的链接。

#! /usr/bin/env python3
import json
import sys
import zlib
 
import base45
import cbor2
from cose.messages import CoseMessage
 
payload = sys.argv[1][4:]
print("decoding payload: "+ payload)
 
# decode Base45 (remove HC1: prefix)
decoded = base45.b45decode(payload)
 
# decompress using zlib
decompressed = zlib.decompress(decoded)
# decode COSE message (no signature verification done)
cose = CoseMessage.decode(decompressed)
# decode the CBOR encoded payload and print as json
print(json.dumps(cbor2.loads(cose.payload), indent=2))

我找不到任何可以正常工作的 Zlib 的 NuGet 包。所以我在base45解码后直接卡住了。感谢您的任何提示。

using System.Text; //Rystem.Text.Base45 NuGet
    
var removedHeader = testQrData.Substring(4);
var decoded = removedHeader.FromBase45();
byte[] rawBytes = Encoding.ASCII.GetBytes(decoded);

link 可能有助于进一步调查。

Decoding scheme

【问题讨论】:

  • DeflateStream 可能会满足您的需求。 docs.microsoft.com/en-us/dotnet/api/…
  • 您的目标是将那个确切的应用程序/lib 从 Python 移植到 C# 还是只需要 something 来解码 C# 中的 QR?在后一种情况下,我建议使用 C# 中已经存在的现有 QR 工具。
  • 这是一个特定的欧盟数字绿色证书二维码。它是由 base45 编码的,而不是像大多数 QR 那样的 base64。 ..由 ZLIB 压缩并由 COSE/CBOR 加密。我想编写一个应用程序的一部分,它将这个 QR 的字符串转换为另一个字符串,该字符串包含 json 中的编码数据。在 Python 中这似乎很容易,因为您可以使用提供的示例轻松进行测试。但是我无法通过 C# 上的 zlib inflate 部分,尽管我尝试了许多 NuGet 包和在 stackoverflow 上提到的 ZLIB 解压缩方法。感谢您的 cmets!

标签: python c# qr-code zlib cbor


【解决方案1】:
IBarcodeReader reader = new BarcodeReader();//using Zxing
var barcodeBitmap = (Bitmap)Bitmap.FromFile("qrcode.png");

var barcodeReader = new BarcodeReader();

var qrcontent = barcodeReader.Decode(barcodeBitmap).Text;

var qrmessage = qrcontent.Substring(4);//remove first 4 chars

byte[] decodedBase45 = Base45Encoding.Decode(qrmessage);//using base45 lib
var cose = ZlibStream.UncompressBuffer(decodedBase45);//using zlib or similar

var decrypted = Message.DecodeFromBytes(cose).GetContent(); //using COSE
CBORObject cbor = CBORObject.DecodeFromBytes(decrypted);    //using Peter.O.. CBOR

var jsonDecoded = cbor.ToJSONString(); //or deserialize it to custom class

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

我从 NuGet 添加了 "Zlib.Portable" 并使用了 "ZlibStream.UncompressString"

byte[] decoded = Base45Encoding.Decode(input);
var stringResult = ZlibStream.UncompressString(decoded);`

我被困在下一步“CoseMessage.decode”:/

【讨论】:

  • 谢谢!这个 Zlib NuGet 的正确 ,,using" 是什么?同样的问题,你在 Base45Encoding 行中使用了什么 using 或 package?我相信我们会到达那里:)
  • 不客气 :) 第一个问题:在 References 中您右键单击,我将访问 NuGet 包,然后搜索 Zlib.Portable 并安装它。第二个问题:在这种情况下,我也尝试使用 NuGet 包,但它对我不起作用,您可以尝试 Rystem.Text.Base45(在描述中,您将获得项目 Base45 的链接)。我所做的是将此 CS 文件添加到我的解决方案 link
  • 查看我刚刚添加的解决方案。
【解决方案3】:

我过去使用ZXing库来解码和创建二维码:
https://github.com/micjahn/ZXing.Net

你也可以试试:https://github.com/codebude/QRCoder

来自他们 github 页面的快速 ZXing 示例:

// create a barcode reader instance
IBarcodeReader reader = new BarcodeReader();
// load a bitmap
var barcodeBitmap = (Bitmap)Image.LoadFrom("C:\\sample-barcode-image.png");
// detect and decode the barcode inside the bitmap
var result = reader.Decode(barcodeBitmap);
// do something with the result
if (result != null)
{
   txtDecoderType.Text = result.BarcodeFormat.ToString();
   txtDecoderContent.Text = result.Text;
}

此示例读取 QR 码图像。
我不确定您的输入是什么,但我会假设它也是二进制格式的图像,因此您可能需要尝试使其正常工作。

【讨论】:

  • 谢谢,但这与常规 base64 QR 无关。我希望解码欧盟数字绿色证书的 QR 输出/字符串 - covid pass。输出应该是另一个 json 格式的字符串。
【解决方案4】:

这个解决方案对我有用。

using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.IO;
using Com.AugustCellars.COSE;
using PeterO.Cbor;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Collections.Generic;

namespace DGCVerification.Services {
  public class DGCVerificationService: IDGCVerificationService {
    private readonly string _certificatePrefix = "HC1:";

private readonly ILogger < DGCVerificationService > _logger;

public DGCVerificationService(ILogger < DGCVerificationService > logger) {
  _logger = logger;
}

private bool TryTrimPrefix(string certificateString, out string result) {
  if (!certificateString.StartsWith(_certificatePrefix)) {
    result = null;
    return false;
  }

  result = certificateString.Substring(_certificatePrefix.Length);
  return true;
}

private byte[] DecompressFromZlib(byte[] input) {
  using(var memoryStream = new MemoryStream(input)) {
    using(var decompressionStream = new InflaterInputStream(memoryStream)) {
      using(var resultStream = new MemoryStream()) {
        decompressionStream.CopyTo(resultStream);
        var result = resultStream.ToArray();
        return result;
      }
    }
  }
}

private bool VerifyCoseSignature(Sign1Message coseMessage, string signature) {
  var signatureAsBytes = Convert.FromBase64String(signature);

  var publicKey = OneKey.FromX509(signatureAsBytes);
  var result = coseMessage.Validate(publicKey);

  _logger.LogInformation($"cose message signature {signature} verified as {result}");
  return result;
}

private DGCPayload DecodeAndDeserialize(string certificateString, out Sign1Message coseMessage) {
  coseMessage = null;
  if (!TryTrimPrefix(certificateString, out
      var trimmedPrefixString)) {
    _logger.LogInformation($"certificate {certificateString} didn't have proper prefix {_certificatePrefix}");
    return null;
  }

  var bytesBase256 = Base45Encoding.Decode(trimmedPrefixString);
  _logger.LogInformation($"certificate {certificateString} base45 decoded to  {Convert.ToBase64String(bytesBase256)}");

  var decompressedFromZlib = DecompressFromZlib(bytesBase256);
  _logger.LogDebug($"certificate {certificateString} zlib decompressed to {Convert.ToBase64String(decompressedFromZlib)}");

  coseMessage = Message.DecodeFromBytes(decompressedFromZlib) as Sign1Message;
  var coseMessagePayload = coseMessage.GetContent();
  var cborResult = CBORObject.DecodeFromBytes(coseMessagePayload);
  var jsonResult = cborResult.ToJSONString();
  var result = JsonConvert.DeserializeObject < DGCPayloadCzechVersionRoot > (jsonResult);
  return result.DGCPayloadWrap.DGCPayload;
}

private bool IsNotExpiredDGC(DGCPayload dGCPayload, UzisData uzisData) {
  var vaccine = dGCPayload.Vaccination?.FirstOrDefault();

  if (vaccine != null) {
    var vaccinationValidityRules = uzisData.DGCValidityCzechRules.Rules
      .FirstOrDefault()
      .PlatnostiVakcinace;

    var rule = vaccinationValidityRules.FirstOrDefault(x => x.VaccineMedicinalProduct == vaccine.Mp) ??
      vaccinationValidityRules.FirstOrDefault(x => x.VaccineMedicinalProduct == null);

    if (!DateTime.TryParse(vaccine.Dt, out
        var vaccinatedOnDate)) {
      _logger.LogError($"couldn't parse date of vaccination for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
      return false;
    }

    var result = DateTime.Now.Date <= vaccinatedOnDate.AddMonths(rule.OdolnostMesicDo);
    return result;
  }

  var test = dGCPayload.Test?.FirstOrDefault();

  if (test != null) {
    var testValidityRule = uzisData.DGCValidityCzechRules.Rules
      .FirstOrDefault()
      .PlatnostiTestu
      .FirstOrDefault(x => x.TypeOfTest == test.Tt);

    if (!DateTime.TryParse(test.Tr, out
        var testedOnDate)) {
      _logger.LogError($"couldn't parse date of test for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
      return false;
    }

    var result = DateTime.Now.Date <= testedOnDate.AddHours(testValidityRule.PlatnostHod);

    return result;
  }

  var recovery = dGCPayload.Recovery?.FirstOrDefault();

  if (recovery != null) {
    if (!DateTime.TryParse(recovery.Du, out
        var recoveryValidUntil)) {
      _logger.LogError($"couldn't parse recovert valid until for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
      return false;
    }

    var result = DateTime.Now.Date <= recoveryValidUntil;
    return result;
  }

  return false;
}

private string GetCountryFromDGC(DGCPayload dGCPayload) {
  var result = dGCPayload.Vaccination?.FirstOrDefault()?.Co ??
    dGCPayload.Test?.FirstOrDefault()?.Co ??
    dGCPayload.Recovery?.FirstOrDefault()?.Co;

  if (result == null) {
    throw new ArgumentException($"couldn't retrieve country from DGC. dgc : {JsonConvert.SerializeObject(dGCPayload)}");
  }

  return result;
}

private List < SignatureCertificate > GetFittingSignatures(DGCPayload dGCPayload, UzisData uzisData) {
  try {
    var country = GetCountryFromDGC(dGCPayload);

    var result = uzisData.NationalCertificateSignatures.SignatureCertificate
      .Where(x => x.Active)
      .Where(x => x.Country == country)
      .Where(x => x.CertificateType == "DSC")
      .ToList();

    return result;
  } catch (Exception e) {
    _logger.LogError(e, $"Filtering signatures from UZIS failed with exception");
    return null;
  }
}

private bool IsProperlySignedDGC(string certificateString, DGCPayload dGCPayload, Sign1Message coseMessage, UzisData uzisData) {
  var fittingSignatures = GetFittingSignatures(dGCPayload, uzisData);
  var result = false;
  foreach(var signature in fittingSignatures) {
    try {
      var signatureVerificationResult = VerifyCoseSignature(coseMessage, signature.RawData);

      _logger.LogInformation($"certificate {certificateString} signature validation against signature {signature} resulted in {signatureVerificationResult}");

      result |= signatureVerificationResult;
    } catch (Exception e) {
      _logger.LogError(e, $"certificate {certificateString} signature validation against signature {signature} failed with exception");
    }
  }

  return result;
}

public bool IsValidDgc(string certificateString, UzisData uzisData, out DGCPayload decodedDGCPayload, out VerificationResult verificationResult) {
  decodedDGCPayload = null;
  Sign1Message coseMessage = null;

  try {
    decodedDGCPayload = DecodeAndDeserialize(certificateString, out coseMessage);

    if (coseMessage == null) {
      _logger.LogInformation($"certificate {certificateString} decoded to null COSE");
      verificationResult = VerificationResult.UnableToDecode;
      return false;
    }
  } catch (Exception e) {
    _logger.LogError(e, $"certificate {certificateString} decoding failed with exception");
    verificationResult = VerificationResult.UnableToDecode;
    return false;
  }

  var isProperlySigned = IsProperlySignedDGC(certificateString, decodedDGCPayload, coseMessage, uzisData);

  if (!isProperlySigned) {
    verificationResult = VerificationResult.InvalidSignature;
    return false;
  }

  var isNotExpired = IsNotExpiredDGC(decodedDGCPayload, uzisData);

  if (!isNotExpired) {
    verificationResult = VerificationResult.Expired;
    return false;
  }

  verificationResult = VerificationResult.Valid;
  return true;
    }
  }
}

还有棘手的base45

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DGCVerification
{
public class Base45Encoding
{
    private static readonly Dictionary<byte, char> _encodingTable = new Dictionary<byte, char>
    {
            {0x0,'0'},
            {0x1,'1'},
            {0x2,'2'},
            {0x3,'3'},
            {0x4,'4'},
            {0x5,'5'},
            {0x6,'6'},
            {0x7,'7'},
            {0x8,'8'},
            {0x9,'9'},
            {0xA,'A'},
            {0xB,'B'},
            {0xC,'C'},
            {0xD,'D'},
            {0xE,'E'},
            {0xF,'F'},
            {0x10,'G'},
            {0x11,'H'},
            {0x12,'I'},
            {0x13,'J'},
            {0x14,'K'},
            {0x15,'L'},
            {0x16,'M'},
            {0x17,'N'},
            {0x18,'O'},
            {0x19,'P'},
            {0x1A,'Q'},
            {0x1B,'R'},
            {0x1C,'S'},
            {0x1D,'T'},
            {0x1E,'U'},
            {0x1F,'V'},
            {0x20,'W'},
            {0x21,'X'},
            {0x22,'Y'},
            {0x23,'Z'},
            {0x24,' '},
            {0x25,'$'},
            {0x26,'%'},
            {0x27,'*'},
            {0x28,'+'},
            {0x29,'-'},
            {0x2A,'.'},
            {0x2B,'/'},
            {0x2C,':'},
    };

    private static readonly Dictionary<char, byte> _decodingTable;

    static Base45Encoding()
    {
        _decodingTable = _encodingTable.ToDictionary(x => x.Value, x => x.Key);
    }

    private static List<byte> ToBytesBase45(string charsBase45)
    {
        var result = new List<byte>(charsBase45.Length);
        foreach (var character in charsBase45)
        {
            if (!_decodingTable.TryGetValue(character, out byte asByte))
            {
                throw new FormatException($"input string contains {character} with numeric value {char.GetNumericValue(character)} on index {charsBase45.IndexOf(character)} which is not valid base45 character");
            }
            result.Add(asByte);
        }

        return result;
    }

    private static List<ushort> ToShortsBase10(List<byte> bytesBase45)
    {
        var result = new List<ushort>(bytesBase45.Count);
        ushort num = 0;
        double pow = 0;

        for (int i = 0; i != bytesBase45.Count; ++i, ++pow)
        {
            num += (ushort)(bytesBase45[i] * Math.Pow(45, pow));

            if (pow == 2 || i == (bytesBase45.Count -1))
            {
                result.Add(num);
                num = 0;
                pow = -1;
            }
        }

        return result;
    }

    private static List<byte> ToBytesBase256(List<ushort> shortsBase10, int charBase45Length)
    {
        var result = new List<byte>(shortsBase10.Count);

        for (int i = 0; i != shortsBase10.Count; ++i)
        {
            var num = (byte)(shortsBase10[i] / 256);

            if(!(i == (shortsBase10.Count - 1) 
                && charBase45Length % 3 != 0
                && num == 0))
            {
                result.Add(num);
            }
            result.Add((byte)(shortsBase10[i] % 256));
        }

        return result;
    }

    public static byte[] Decode(string charsBase45)
    {
        if (charsBase45.Length % 3 == 1)
        {
            throw new FormatException("input string does not have correct length. mod 3 == 1. it isnt base45");
        }

        var bytesBase45 = ToBytesBase45(charsBase45);
        var shortsBase10 = ToShortsBase10(bytesBase45);
        var bytesBase256 = ToBytesBase256(shortsBase10, charsBase45.Length);

        return bytesBase256.ToArray();
    }
}
}

【讨论】:

    【解决方案5】:

    解码/编码base45:

    /// <summary>
    /// https://tools.ietf.org/html/draft-faltstrom-baseBaseSize-01
    /// TL/DR:
    /// This encoding takes a byte array, splits it into 2 byte chunks and encodes each chunk as 3 characters.
    /// Any remaining byte is encoded as 2 characters, padded with a '0' when the remaining byte has value &lt; 45.
    /// </summary>
    public static class Base45Encoding
    {
        private const int BaseSize = 45;
        private const int BaseSizeSquared = 2025;
        private const int ChunkSize = 2;
        private const int EncodedChunkSize = 3;
        private const int SmallEncodedChunkSize = 2;
        private const int ByteSize = 256;
    
        private static readonly char[] _Encoding = {'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', ' ', '$', '%', '*', 
                                                    '+', '-', '.', '/', ':' };
    
        private static readonly Dictionary<char, byte> _Decoding = new(BaseSize);
    
        static Base45Encoding()
        {
            for(byte i = 0; i < _Encoding.Length; ++i)
                _Decoding.Add(_Encoding[i], i);
        }
    
        public static string Encode(byte[] buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));
    
            var wholeChunkCount = buffer.Length / ChunkSize;
            var result = new char[wholeChunkCount * EncodedChunkSize + (buffer.Length % ChunkSize == 1 ? SmallEncodedChunkSize : 0)];
    
            if (result.Length == 0)
                return string.Empty;
    
            var resultIndex = 0;
            var wholeChunkLength = wholeChunkCount * ChunkSize;
            for (var i = 0; i < wholeChunkLength;)
            {
                var value = buffer[i++] * ByteSize + buffer[i++];
                result[resultIndex++] = _Encoding[value % BaseSize];
                result[resultIndex++] = _Encoding[value / BaseSize % BaseSize];
                result[resultIndex++] = _Encoding[value / BaseSizeSquared % BaseSize];
            }
    
            if (buffer.Length % ChunkSize == 0)
                return new string(result);
    
            result[^2] = _Encoding[buffer[^1] % BaseSize];
            result[^1] = buffer[^1] < BaseSize ? _Encoding[0] : _Encoding[buffer[^1] / BaseSize % BaseSize]; 
    
            return new string(result);
        }
    
        public static byte[] Decode(string value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
    
            if (value.Length == 0)
                return Array.Empty<byte>();
    
            var remainderSize = value.Length % EncodedChunkSize;
            if (remainderSize == 1)
                throw new FormatException("Incorrect length.");
    
            var buffer = new byte[value.Length];
            for (var i = 0; i < value.Length; ++i)
            {
                if (_Decoding.TryGetValue(value[i], out var decoded))
                {
                    buffer[i] = decoded;
                    continue; //Earliest return on expected path.
                }
    
                throw new FormatException($"Invalid character at position {i}.");
            }
    
            var wholeChunkCount = buffer.Length / EncodedChunkSize;
            var result = new byte[wholeChunkCount * ChunkSize + (remainderSize == ChunkSize ? 1 : 0)];
            var resultIndex = 0;
            var wholeChunkLength = wholeChunkCount * EncodedChunkSize;
            for (var i = 0;  i < wholeChunkLength; )
            {
                var val = buffer[i++] + BaseSize * buffer[i++] + BaseSizeSquared * buffer[i++];
                result[resultIndex++] = (byte)(val / ByteSize); //result is always in the range 0-255 - % ByteSize omitted.
                result[resultIndex++] = (byte)(val % ByteSize); 
            }
    
            if (remainderSize == 0) 
                return result;
            
            result[^1] = (byte)(buffer[^2] + BaseSize * buffer[^1]); //result is always in the range 0-255 - % ByteSize omitted.
            return result;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-29
      • 1970-01-01
      • 1970-01-01
      • 2012-08-22
      相关资源
      最近更新 更多