ECC P-256 problem (KSeF)

We are using TMSCryptographyPack in the C++ Builder 10.2 Tokyo environment to integrate with KSeF in Poland.

At the end of our development work, we encountered a certain problem.
We have the following code for generating a signature using the TECCEncSign object:

UnicodeString dir, filename, cipher;
dir = ExtractFilePath(Application->ExeName);

// This line makes error - Public key incorrect, must be 88
// ecc->FromCertificateFile("C:\Users\Andrzej\Desktop\Drugi link\certyfikat\certyfikat.crt");

Memo1->Lines->Clear();
Memo1->Lines->LoadFromFile(dir + "\test.txt", TEncoding::UTF8);
ecc->FromPrivateKeyFile("C:\Users\Andrzej\Desktop\Drugi link\certyfikat\dcertyfikat.key");
cipher = ecc->Sign(Memo1->Lines->Text.Trim());

Memo1->Lines->Clear();
Memo1->Lines->LoadFromFile(dir + "\test.txt");
Memo1->Lines->Text = Memo1->Lines->Text + "/" + cipher;

The test.txt file contains only the following text:
ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE

After executing the code, we obtain the full link with the signature:
ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/BCh4oVStUv91pPbKkI6Jv6j6A2qAfVQmnoUo00EqAlcNgSc5yXCPUoiVS0YZQdRCRQjD5GgGk9i1rOWjuP5o2Kk=
where the string
BCh4oVStUv91pPbKkI6Jv6j6A2qAfVQmnoUo00EqAlcNgSc5yXCPUoiVS0YZQdRCRQjD5GgGk9i1rOWjuP5o2Kk=
is the generated signature appended to the link. This string has 88 characters (87 without = char at the end). By the way, shouldn’t the = character be automatically removed for Base64URL by the ecc object? We tested the link both with and without this character and it still doesn’t work, so for now this is a secondary issue.

The ecc component on the form is configured as follows:

  • ECCType: p256
  • outputFormat: base64url
  • NaCl: naclno
  • Unicode: yesUni

The problem is that the generated link is invalid.

For comparison, we generate the same signature using the online tool:

We paste our key, select P‑256, SHA‑256, and PEM for the key. We paste exactly the same text as in test.txt and we obtain a Base64 string such as:
MEUCIQCnyGX8GuEk8Bt/mrLXvodpQJnDoh4/5fvIHC9l26+vqgIgAkIaWxIG1TdI0EMxje8tFuYEkCmOijTs3gQGDCV84Nk=
After converting it to Base64URL, we get:
MEUCIQCnyGX8GuEk8Bt_mrLXvodpQJnDoh4_5fvIHC9l26-vqgIgAkIaWxIG1TdI0EMxje8tFuYEkCmOijTs3gQGDCV84Nk
and then the link is valid :grinning_face: :
ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/MEUCIQCnyGX8GuEk8Bt_mrLXvodpQJnDoh4_5fvIHC9l26-vqgIgAkIaWxIG1TdI0EMxje8tFuYEkCmOijTs3gQGDCV84Nk

We would like to point out that the signature generated by TMS Crypto has 88 characters, while the one generated by the online tool has 96 characters (95 characters after Base64URL conversion, because the trailing = disappears).
We kindly ask for guidance on what might be wrong here. How can we achieve compatibility between TECCEncSign and the online tool?

Best regards,
Andrzej Gąsowski

From another Polish user, it looks like the leading ‘04’ at the beginning of the signature shall be removed prior to submitting the file to KSeF.

This user kindly provided the following code that you may have to adapt for your case:

ecc.outputFormat := TConvertType.base64;
derBase64 := ecc.Sign('my_text_to_sign');
sigBytes := DecodeBase64(derBase64);

if (Length(sigBytes) = 65) and (sigBytes[0] = $04) then
  RawSig := Copy(sigBytes, 1, 64)
else if (Length(sigBytes) = 64) then
  RawSig := sigBytes;

Base64UrlTxt := EncodeBase64(RawSig, Length(RawSig));
Base64UrlTxt := Base64UrlEncode(Base64UrlTxt);

Best regards,

bernard

Hi,

Thank you for the quick reply. I’ll try to check it today.

Code updated:

UnicodeString dir, filename, cipher;

TBytes sigBytes;
TBytes RawSig;

sigBytes.Length = 0;
RawSig.Length = 0;

dir = ExtractFilePath(Application->ExeName);

// This line makes error - Public key incorrect, must be 88
// ecc->FromCertificateFile("C:\\Users\\Andrzej\\Desktop\\KSeF\\Drugi link\\certyfikat\\certyfikat.crt");

// loading source text to sign
Memo1->Lines->Clear();
Memo1->Lines->LoadFromFile(dir + "\\test.txt", TEncoding::UTF8);

// setting outputFormat and PrivateKey
ecc->outputFormat = base64;
ecc->FromPrivateKeyFile
	("C:\\Users\\Andrzej\\Desktop\\KSeF\\Drugi link\\certyfikat\\dcertyfikat.key"
	);

// sign text
cipher = ecc->Sign(Memo1->Lines->Text.Trim());

// convert text to bytes
sigBytes = TNetEncoding::Base64->DecodeStringToBytes(cipher);

// your suggestion
if ((sigBytes.Length == 65) && (sigBytes[0] == 0x04)) {
	RawSig = sigBytes;
	RawSig.Length = 64;
}
else if (sigBytes.Length == 64) {
	RawSig = sigBytes;
};

// bytes to UnicodeString
UnicodeString Base64UrlTxt = TNetEncoding::Base64->EncodeBytesToString
	(&RawSig[0], RawSig.Length);

// Base64 to Base64Url
Base64UrlTxt = StringReplace(Base64UrlTxt, L"+", L"-",
	TReplaceFlags() << rfReplaceAll);
Base64UrlTxt = StringReplace(Base64UrlTxt, L"/", L"_",
	TReplaceFlags() << rfReplaceAll);
while (Base64UrlTxt.Length() > 0 && Base64UrlTxt[Base64UrlTxt.Length()]
	== L'=') {
	Base64UrlTxt.SetLength(Base64UrlTxt.Length() - 1);
};

Memo1->Lines->Clear();
Memo1->Lines->LoadFromFile(dir + "\\test.txt");
// Memo1->Lines->Text = Memo1->Lines->Text + "/" + cipher;
Memo1->Lines->Text = Memo1->Lines->Text + "/" + Base64UrlTxt;

Unfortunately, it still doesn’t work correctly.

Example link: ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/BDnLlaf0s-1NkHyZADPubY6icFTPdsvKvqlOp456kcpscYOE7GN8ey-GXYNU1CcR0W79wWeDRheznp3qjS4IVQA

It seems that the Sign method isn’t working correctly. The online tool that generates a valid signature produces a longer string.

Hi, the issue is I don’t have access to KSef services and can only verify signed files with EU tools such as https://signatures-conformance-checker.etsi.org/login or DSS Demonstration WebApp

Current signatures pass thoses tests (except for the Spanish ones with SHA1).

Does your signature pass the ETSI checker? If yes, then, KSeF requires an adaptation that I cannot check myself.

I can put you in in touch with users who have successfully done it, if you wish.

Regards,

bernard

Hi,

Testing the part we are having trouble with does not require access to KSeF.
In a moment I will send you our private key for the test environment by email.

  1. Using this key, generate a signature for the following string:
    ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE
  2. Append the generated result to the end of the above URL (after the slash):
    ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/{Your signature of the string from step 1 using our certificate – in Base64URL}
    for example (when we use online tool):
    ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/MEUCIQCnyGX8GuEk8Bt_mrLXvodpQJnDoh4_5fvIHC9l26-vqgIgAkIaWxIG1TdI0EMxje8tFuYEkCmOijTs3gQGDCV84Nk
  3. Paste the resulting link from step 2 into any browser. If everything is correct, a green confirmation message will appear.

That’s all! :grinning_face:

Below are the signature requirements from the KSeF documentation - they may be useful for you:

ECDSA (P‑256/SHA‑256)
The string to be signed is hashed using the SHA‑256 algorithm, and then a signature is generated using an ECDSA private key based on the NIST P‑256 (secp256r1) curve, which must be selected when generating the CSR.

The signature value is a pair of integers (r, s). It may be encoded in one of two formats:

  • IEEE P1363 Fixed Field Concatenation – the recommended method due to the shorter and fixed‑length output. The format is simpler and shorter than DER. The signature is the concatenation R S (32 bytes each, big‑endian).

  • ASN.1 DER SEQUENCE (RFC 3279) – the signature is encoded as ASN.1 DER. The signature size is variable. We recommend using this format only when IEEE P1363 is not possible due to technological limitations.

Regards,
Andrzej

A small correction.

I wrote:
{Your signature of the string from step 1 using our certificate – in Base64URL}


I meant:
{Your signature of the string from step 1 using our key – in Base64URL}

Hi Andrzej,

Here is a solution that addresses both formats accepted by KSeF.

procedure TMainForm.KsefBtnClick(Sender: TObject);
var
s, t, u, sig: string;
Conv: TConvert;
I: integer;

begin
s := 'ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE';
Conv := TConvert.Create(Base64url);
try
EC.ECCType := p256;
EC.outputFormat := raw;
EC.FromPrivateKeyFile('....\TestData\KSeFcertyfikat.key'); // Your KEY
sig := EC.Sign(s);
sig := sig.Substring(1, sig.Length - 1); // remove leading #4

// Display IEEE P1363 base64url encoded result
MainMemo.Lines.Add('P1363 signature: ' + Conv.CharToFormat(sig)); // the fomatted string without the trailing '=' (if present) can be added to the KSeF URL and verifies correctly

// Create ASN.1 DER SEQUENCE (RFC 3279)
for I := 1 to 32 do begin
  t := t + sig[I];
  u := u + sig[I + 32];
end;
if (byte(t[1]) and $80) = $80 then
  t := #0 + t;
if (byte(u[1]) and $80) = $80 then
  u := #0 + u;

t := #2 + char(t.Length) + t;
u := #2 + char(u.Length) + u;
sig := #$0030 + char(Length(t + u)) + t + u;
MainMemo.Lines.Add('RFC 3279 signature: ' + Conv.CharToFormat(sig)); // the formatted string can be added to the KSeF URL

finally
Conv.Free;
end;
end;

Examples (IEEE P1363 format then RFC 3279 format):

Let me know if it works for you.

Regards,

bernard

Thank you!

I will test this solution by tomorrow at the latest and let you know whether it works.

If it does, it will definitely help others. Generating a signed QR code is important for KSeF in Poland and it’s a hot topic right now. If this works, then the entire TMS Crypto library will be fully compliant with KSeF 2.0.

Well, it will work :slight_smile:

Note that you need to remove the tailing ‘=’ signs from the generated base64url text (if any).

I will add a specific function to the ECC class for this signature type.

Regarding signing QR codes, I did that in 2015, or so, in C at the time. I could certainly add that capability to TMS CP in full Delphi. If you have the detailed technical requirements, I will have a look at them.

Final code will look like this.

s := 'ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE';
try
EC.FromPrivateKeyFile('....\TestData\KSeFcertyfikat.key'); // Your KEY
sig := EC.SignWithKsefFormat(s, ieeeP1363);

MainMemo.Lines.Add(s + '/' + sig);
sig := EC.SignWithKsefFormat(s, rfc3279);

MainMemo.Lines.Add(s + '/' + sig);

finally

end;


s + '/' + sig can be submitted directly to KSeF

Example results (IEEE then RFC):

ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/AqWdiM1AHlQ9NuWlkx5v2bsnHkHA71hYPJtkz3RGvjF0C72dL3NiK63tJQEk2aEuNfg9rsKeK7pH4s5ixmYuig

ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/MEQCIA7eXm3qF8_6zM_raezv0yT-XTVJsYKbsmzYI1m_axozAiARVt1latnwAwm9OnLhq4BMBQjjFDrZ9cfSG93T4-eCIA

Thank you very much, Bernard!

Your code, after being translated to C++ Builder and slightly modified, works well.
It seems that the issue was indeed the leading #4.

My final C++ Builder code looks like this:

UnicodeString s, sig;

s = "ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE";

ecc->ECCType = p256;

// This line produces error - Private length incorrect, must be 32
// ecc->outputFormat = raw;

ecc->outputFormat = hexa;
ecc->FromPrivateKeyFile
	("C:\\Users\\Andrzej\\Desktop\\KSeF\\Drugi link\\certyfikat\\dcertyfikat.key"
	);

Memo1->Lines->Clear();
Memo1->Lines->Add(s);
Memo1->Lines->Add("");

sig = ecc->Sign(s);
Memo1->Lines->Add(sig);
Memo1->Lines->Add("");

sig = sig.SubString(3, sig.Length() - 2); // remove leading #4
Memo1->Lines->Add(sig);
Memo1->Lines->Add("");

conv->AType = hexa;
sig = conv->HexaToBase64url(sig);

Memo1->Lines->Add(sig);
Memo1->Lines->Add("");

// Remove '=' characters
sig = StringReplace(sig, L"=", L"", TReplaceFlags() << rfReplaceAll);

// Display IEEE P1363 base64url encoded result
Memo1->Lines->Add(s + "/" + sig);

In the Memo component it produces, for example, the following:

ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE

042B3B8F66928D9551D21B8C41259A32E6E019B09CBC7239BB423ACE44AED98A4D811E87F502D678A170905D267C31690B343FA1242F5A23065C742D66B45E4D6E

2B3B8F66928D9551D21B8C41259A32E6E019B09CBC7239BB423ACE44AED98A4D811E87F502D678A170905D267C31690B343FA1242F5A23065C742D66B45E4D6E

KzuPZpKNlVHSG4xBJZoy5uAZsJy8cjm7QjrORK7Zik2BHof1AtZ4oXCQXSZ8MWkLND-hJC9aIwZcdC1mtF5Nbg==

ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE/KzuPZpKNlVHSG4xBJZoy5uAZsJy8cjm7QjrORK7Zik2BHof1AtZ4oXCQXSZ8MWkLND-hJC9aIwZcdC1mtF5Nbg

The test link for verifying the KSeF certificate generated this way works :grinning_face: !

IEEE P1363 is the recommended method, so I didn’t check the DER SEQUENCE (RFC 3279) anymore.

The only thing that worries me is the line of code:
ecc->outputFormat = raw;
In C++ Builder it throws an error at the moment the key is loaded:
Private length incorrect, must be 32

I think it’s worth taking a look at this when updating TMS Crypto.

In any case, if the outputFormat is set to hex, everything else works.

Excellent news!

I guess the issue with ‘raw’ vs ‘hexa’ is that the key is loaded as a hex string because some conversion was not passed in the C++ conversion.

Hello Bernard and Andrzej,

I struggle with signing the url properly too and I appreciate your troubleshooting efforts you have made so far. It is very useful indeed.

I am trying to use this new method implemented by Bernard ECC.SignWithKsefFormat with ieeeP1363 signature format. But the generated(signed) url is not ok. The validation of the signature fails when such a url is invoked.

The Ministry provided some sample code snipets which demonstrate how this url can be signed.

I can see that they acquire a hash (SHA256) from the url to be signed and then they sign the output from hash function.

        // 1. SHA-256
        byte[] sha;
        sha = SHA256.HashData(Encoding.UTF8.GetBytes(pathToSign));

signature = ecdsa.SignHash(sha, dSASignatureFormat);

I do not see any explicit hashing in your examples, so I have a question if the ECC.Sign method performs a hash internally hence we do not have to hash the content passed to this method by ourselves?

Kind regards

Arek

Hi Arek,

ECC calls SHA256 (for P256) directly. You don’t need to hash anything, just pass the full URL as in this example:

ksef-test.mf.gov.pl/client-app/certificate/Nip/6571508893/6571508893/014AE117D068BBB5/UtQp9Gpc51y-u3xApZjIjgkpZ01js-J8KflSPW8WzIE';

Regards,

bernard

Hello Bernard,

it works like a charm now. Thank you!

I use ECC.FromPrivateKeyFile('some.key’) but I would be delighted if there was an alternative version of this function which can process the content of .key file provided in the string or stream (whatever is more convenient/sensible for you to add).

The procedure TECCEncSign.FromPrivateKey(KeyStr: string) does not seem to designated for that purpose.

Can you please recomend if there is a way to achieve this?

Many thanks in advance

Kind regards

Arek

Hi Arek,

ECC.FromPrivateKey will process a string containing the PKCS#8 file from which the header (—– BEGIN…) and footer (—– END…) have been removed, as well as in which all CRLF and extra spaces have been stripped off.

What would you need exactly?

Regards,

bernard

Hello Bernard,

The file with the private key is protected with password, so I can load it with this:
ECC := TECCEncSign.Create(nil);
ECC.outputFormat := hexa;
ECC.Password := pPrivKeyPass;
ECC.FromPrivateKeyFile('my.key');
and this is okay.

But I prefer to do it like this:

function TKSEF_Client.signWithPrivateKey(pStringToSign : string; pPrivKeyContent, pPrivKeyPass : string) : string;
var s : string;
ECC : TECCEncSign;
begin
Result := '';
s := pPrivKeyContent; //this pPrivKeyContent parameter contains the file content
s := Copy(s,Pos(#10,s)+1,length(s)); //removing first line
s := Copy(s,1,Pos('-----END ENCRYPTED PRIVATE KEY-----',s)-2); //removing last line
s := StringReplace(s,#10,'',[rfReplaceAll]); //removing EOL characters
s := StringReplace(s,#13,'',[rfReplaceAll]);

try

ECC := TECCEncSign.Create(nil);
ECC.outputFormat := hexa;
ECC.Password := pPrivKeyPass;
ECC.FromPrivateKey(s); //this call throws exception

And the call to ECC.FromPrivateKey(s) method throws an exception:

ECCExtractPrivateKey: incorrect type tag [03].

When I look into the ECC.FromPrivateKey in unit ECCObj I can see this:

procedure TECCEncSign.FromPrivateKey(KeyStr: string);*
var
X509: TX509Certificate;*

begin
if (self.FECCType = cc511187) or (self.FECCType = ex25519) or (self.FECCType = ex448) then
raise Exception.Create('Curve not supported in private key files');

X509 := TX509Certificate.Create();
try
X509.ECCExtractPrivateKey(KeyStr);
finally
X509.free;
end;
end;

And I am wondering how this X509 certificate relates to the ECC object? In other words where would be the output of X509.ECCExtractPrivateKey(KeyStr); stored (even if it was successful)?

and the procedure TECCEncSign.FromPrivateKeyFile(KeyFile: string); is completely different. It loads the file and parses its content. I need a similar thing but without loading the file (I would like to provide the content of the file in a parameter).

Is that feasible?

Kind regards

Arek

Arek,

All details of any extraction are in the X509 fields (in this case).

The issue is that ECCExtractPrivateKey doesn’t decode encrypted keys, so far. I guess that’s why you get an exception.

You need to use part of the code that is in FromPrivateKeyFile to create ECCExtractEncryptedPrivateKey, if you are in a hurry, or wait a little bit so I have time to do it.

Regards,

bernard

Actually, I added FromEncryptedPrivateKey in ECCObj.

It can be used like this:

// decode an encrypted key
procedure TMainForm.EncryptedKeyBtnClick(Sender: TObject);
var
ecc: TECCEncSign;
MyKeyBlob: string;
conv: TConvert;

begin
MyKeyBlob := '-----BEGIN ENCRYPTED PRIVATE KEY-----' +
'MIHzMF4GCSqGSIb3DQEFDTBRMDAGCSqGSIb3DQEFDDAjBBD8Gwr9JGd11iiLrGM/' +
'W5nVAgMBhqAwCgYIKoZIhvcNAgkwHQYJYIZIAWUDBAEqBBB9jOdYZXICXAUkMBao' +
'kuQNBIGQn/IQkRlThSM1Ye8/6GzhauSViJy3efhyuQ8eYeW2h9zxjWy+HXE8G+Xt' +
'qs6cax2GUxhNep+aUq3C3dP1QfcuKXvNzce/epcjUkbfDhT4TFv12E9waajezJoO' +
'JyZ0gYAu1h0jb/M7Eww9pQ/18fSkUTG2ttpwwEUZEqQ8KYIKph6Wt6sBJ78AKxXR' +
'RKVJgWDE' +
'-----END ENCRYPTED PRIVATE KEY-----';

conv := TConvert.Create(hexa);
ecc  := TECCEncSign.Create(P256);
try
ecc.Password := 'aPassForYourKey';  // use your password and key block
ecc.FromEncryptedPrivateKey(MyKeyBlob);
MainMemo.Lines.Add(conv.CharToFormat(ecc.PublicKey));
finally
ecc.Free;
conv.Free;
end;
end;

It will be in 5.1.1.4, tomorrow or Friday.