'Retrieving Signature R and S element from ASN.1 CMS Signature Bouncy Castle

I have an ASN.1 encoded detached CMS ECC signature - I used an online decoder to inspect it and can see the signature R and S values but I'm wondering how to actually get these 2 values.

SEQUENCE (1 elem)
Offset: 1250
Length: 2+9
(constructed)
Value:
(1 elem)
            OBJECT IDENTIFIER 1.2.840.10045.4.1 ecdsaWithSHA1 (ANSI X9.62 ECDSA algorithm with SHA1)
          OCTET STRING (71 byte) 30450220264E3B44C225A763A8C9134FC227398F8045F2269F43C849234E597230DCDF…
            SEQUENCE (2 elem)
              INTEGER (254 bit) 1732611137397374523048116562457205045056141747896936985930487632655856…
              INTEGER (256 bit) 9776787720086444189183880768608824233892259816463822789589418099639594…

So far I have been trying with:

var requestSignatureBase64 = "MIAGCSqG ...";
        
var sigBytes = Convert.FromBase64String(requestSignatureBase64);
// var asn1 = Asn1Object.FromByteArray(sigBytes);
// var derSeq = (DerSequence)asn1;

var input = new Asn1InputStream(sigBytes);

var contendInfo = ContentInfo.GetInstance(input.ReadObject());
var sData = SignedData.GetInstance(contendInfo.Content);
var sig = new CmsSignedData(contendInfo);

Am I close? Thanks!

I am able to inspect in Watch what seems to be close to what I need (but not quite).



Solution 1:[1]

r and s can be determined most easily with an ASN.1 parser, e.g. with BouncyCastle:

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Utilities.Encoders;
...
byte[] der = Hex.Decode("3044021f4043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e290221009caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629");// 3043021f1acda88726e28dbdfcea74e403e7424ba9cba588777e810e42caa5d274135502207283b90135ef09e5847516a9163f4f6a4d403abb9d7632c77397e37165632619");// 3045022100bb3435689a49cb00a04b64fbd7c886d5c67eb406781b43b4ca18bb778df6d35e02201e54c928da6c4867e2b8aafb7703b00784971ef47837433bdf8a6ecb4cdbebcd");
Asn1Sequence seq = Asn1Sequence.GetInstance(der);
BigInteger[] rsBI = new BigInteger[] {
    DerInteger.GetInstance(seq[0]).PositiveValue,
    DerInteger.GetInstance(seq[1]).PositiveValue
};
Console.WriteLine("r: " + rsBI[0]); // r: 113546697132886645577275908676387668608557658993333016730332161870441106985
Console.WriteLine("s: " + rsBI[1]); // s: 70870178508439211896524481754448149175401343396061029910903220215172053128745

Instead of BigInteger both parts can also be represented as unsigned byte arrays or hex encoded:

byte[] rBytes = rsBI[0].ToByteArrayUnsigned();
byte[] sBytes = rsBI[1].ToByteArrayUnsigned();
Console.WriteLine("r (hex): " + Hex.ToHexString(rBytes)); // r (hex): 4043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e29
Console.WriteLine("s (hex): " + Hex.ToHexString(sBytes)); // s (hex): 9caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629

For the notation as r|s, r and s must be padded to the private key size (32 bytes in the example) since in general both parts can also be smaller than the key size:

using System.Linq;
...
byte[] rBytes32 = new byte[32 - rBytes.Length].Concat(rBytes).ToArray(); 
byte[] sBytes32 = new byte[32 - sBytes.Length].Concat(sBytes).ToArray(); 
byte[] rsBytes64 = rBytes32.Concat(sBytes32).ToArray();
Console.WriteLine("r|s: " + Hex.ToHexString(rsBytes64)); // r|s: 004043e112eebe8c92c6aee9132f24af6f73c61353dbd1b1cdde2a5429ba4e299caf197c3cf9838e72068097409ee23b6648b1b14d44a38a5825495893dcd629

For simplicity, in the last code snippet Linq was applied. But the same can be achieved by copying, e.g. with BlockCopy().

As of .NET5 there is the AsnReader class (and hex encoding is supported natively), so BC is not required.


Edit:

The signature can be loaded as follows:

var signedDataDer = Convert.FromBase64String("MIAGCSqG...");
var signers = new CmsSignedData(signedDataDer).GetSignerInfos().GetSigners();
IEnumerator signersEnumerator = signers.GetEnumerator();
signersEnumerator.MoveNext();
SignerInformation signerInfo = (SignerInformation)signersEnumerator.Current;
byte[] signatureDer = signerInfo.GetSignature();

Now r and s can be determined as described above.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1