1

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

我的目标是将 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);

链接可能有助于进一步调查。

解码方案

4

5 回答 5

3
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
于 2021-09-22T07:08:33.800 回答
1

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

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

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

于 2021-08-21T17:14:15.490 回答
0

解码/编码 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;
    }
}
于 2021-11-22T00:34:36.350 回答
0

这个解决方案对我有用。

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();
    }
}
}
于 2021-11-08T08:54:15.457 回答
0

我过去使用 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 码图像。
我不确定您的输入是什么,但我认为它也是二进制格式的图像,因此您可能必须四处玩耍才能使其正常工作。

于 2021-08-19T11:31:29.817 回答