22 May 2026. Complete rewrite of this page and associated code samples to
include the latest CFDi version 4 specification and SHA-256 message digest algorithm,
and latest coding techniques.
The CryptoSys PKI Toolkit includes support for the RSA private key files published by the Servicio de Administración Tributaria in Mexico. You can use the toolkit to create a digital signature "Sello" element suitable for use in your CFDi XML files, verify such a signature, generate the required message digest, convert your X.509 certificate file to a base64 format "Certificado" string, and derive the special SAT 20-digit format for your certificate serial number "NoCertificado".
Outline of Procedure | Functions included in the code | The Code | Notes | Download the full code | Composing the Piped-string | See also | Contact us
The code snippets on this page can be displayed or hidden if Javascript is turned on: Show all code Hide all code
The procedure to sign a CFDi document is as follows.
Leave the NoCertificado,
Sello and Certificado nodes empty.
<cfdi:Comprobante ...
NoCertificado="" Certificado="" Sello="">
NoCertificado value of your signing certificate and add this to your XML document.
This is the 20-decimal digit decoded SAT format.
See Mex_SAT_SerialNumber below.
<cfdi:Comprobante ...
NoCertificado="30001000000300023708" Certificado="" Sello="">
Certificado node.
See Mex_CertToBase64String below. You can do this now or later.
||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||
Add the base64-encoded signature value to the Sello node.
Sello="Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w=="
Your CFDi document is now signed.
Note we used a dummy user certificate (emisor.cer) and its private key to sign our example. These test files are included in the demonstration download.
tfd:TimbreFiscalDigital node
in the cfdi:Complemento element.
Here is an example of a counter-signed document using the base document from above:
cfdv40-ejemplo-signed-tfd.xml.
(Note we used a dummy PAC certificate (pac.cer) and its private key to create our example here.)
Show/hide TimbreFiscalDigital element<tfd:TimbreFiscalDigital xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital"
xsi:schemaLocation="http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd"
Version="1.1" UUID="0e52ecfc-69a1-4c7f-88c5-3dea2a7e8863" FechaTimbrado="2026-04-18T15:19:00" RfcProvCertif="PUT211201AX4"
SelloCFD="Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w=="
NoCertificadoSAT="30001000000300023699"
SelloSAT="Uqe2OrznW6pdzpUqncfXr3hcGn6C1f3BumUvhmGnZJcJcsL0COgrZ3G5N1H0lKoDWx5+P8jDpjWEiqACxaxgxGqkSP/LY2bRKZ0prFG+NU6LdLQRAblxqkNx/VQcsGcNwqT3XHLqz0ZXtLXRxAdtncrwjM4VF0Q1et3vMm31rBlKsPzTz07v1VjXdrsBdzfc0wLGoVGNG41e/hObJNb5JkltBu9pj4C2xfufclpZHpu9Tr7ekJPwqerJWJPHXkiGmB+7SITA+LnW0KkMeVS0mn/IZtBMCVb9v9xzS6gEi4PqBZxpNkLBf8eHwoB79vmjN86zPA6xR5qgpto5ZgLAFw==" />
Mex_CreateSignature: Create a signature (Sello) value in base64 format given the piped-string input and the private key.
(more...)Mex_VerifySignature: Verify the signature (Sello) value using the public key in the Certificado node.
(more...)Mex_ValidateCert: Check that a given X.509 certificate really was issued by the issuer and has not expired.
(more...)Mex_CertToBase64String: Convert an X.509 certificate file into a base64 string suitable for Certificado field in XML.
(more...)Mex_SAT_SerialNumber: Extract the serial number from a SAT-issued X.509 certificate and outputs a 20-digit number suitable for the NoCertificado node.
(more...)Mex_CheckKeyAndCertMatch: Check that the keys in the private key and certificate match.
(more...)Mex_QueryCertString: Extract various details from a certificate string.
(more...)Mex_CreateDigestFromString: Create the message digest of the input string after converting to UTF-8 encoding.
(more...)Mex_ExtractDigestFromSignature: Extract the message digest from a signature (Sello) string using the X.509 certificate (Certificado) value.
(more...)Mex_SignUsingDigest: Create and verify a signature (Sello) using the digest of the piped-string.
(more...)/// <summary>
/// Creates a signature (sello) value in base64 format given the piped-string input and the private key
/// </summary>
public static void Mex_CreateSignature()
{
// Ref: cfdv40-ejemplo.xml
string strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||";
string strKeyFile = "emisor.key";
// Use a StringBuilder to store the password so we can wipe it
StringBuilder sbPassword = new StringBuilder("12345678a"); // CAUTION: DO NOT hardcode production passwords!
// 1. Convert string to UTF-8-encoded byte array
byte[] abData = Encoding.UTF8.GetBytes(strData);
// 2. Sign the input data using the private key
string strSig64 = Sig.SignData(abData, strKeyFile, sbPassword.ToString(), SigAlgorithm.Rsa_Sha256);
Console.WriteLine("SIGNATURE VALUE=" + strSig64);
// Expecting Gnm1yX...Y4RHNwe5w==
// 3. Clean up
Wipe.String(sbPassword);
}
'/**
' Create signature (sello) value in base64 format given the piped-string input and the private key.
'**/
Public Sub Mex_CreateSignature()
Dim strData As String
Dim abData() As Byte
Dim strKeyFile As String
Dim strPassword As String
Dim strSig64 As String
' INPUT: Our original piped-string data (NB includes non-ASCII character ó (LATIN SMALL LETTER O WITH ACUTE U+00F3)
' derived from file cfdv40-ejemplo.xml
strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||"
strKeyFile = "emisor.key"
' Test password - CAUTION: DO NOT hardcode production passwords!
strPassword = "12345678a"
' 1. Convert VBA piped-string to UTF-8-encoded byte array
abData = cnvUTF8BytesFromLatin1(strData)
' 2. Sign the input data using the private key, output direct to a base64 string,
' using sha256WithRSAEncryption algorithm (RSA-SHA-256)
strSig64 = sigSignData(abData, strKeyFile, strPassword, "sha256WithRSAEncryption")
Debug.Print "SIGNATURE VALUE=" & strSig64
' Expecting Gnm1yX...Y4RHNwe5w==
' 3. Clean up
strPassword = wipeString(strPassword)
End Sub
/// <summary>
/// Verify a signature (sello) using the public key in the X.509 certificate.
/// </summary>
public static void Mex_VerifySignature()
{
string strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||";
string strSig64 = "Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w==";
string strCertFile = "emisor.cer";
// Convert input string to UTF-8-encoded byte array
byte[] abData = Encoding.UTF8.GetBytes(strData);
// Verify signature over data
int r = Sig.VerifyData(strSig64, abData, strCertFile, SigAlgorithm.Rsa_Sha256);
Console.WriteLine("VerifyData returns " + r + " (expecting 0)");
}
'/**
' Verify a signature (sello) using the public key in the X.509 certificate.
'**/
Public Sub Mex_VerifySignature()
Dim strData As String
Dim abData() As Byte
Dim strCertFile As String
Dim strSig64 As String
Dim r As Long
' INPUT: Our original piped-string data (NB includes non-ASCII character ó (LATIN SMALL LETTER O WITH ACUTE U+00F3)
' derived from file cfdv40-ejemplo.xml
strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||"
' The signature (sello) value in base64
strSig64 = "Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w=="
' The certificate used to sign
strCertFile = "emisor.cer"
' Convert input string to UTF-8-encoded byte array
abData = cnvUTF8BytesFromLatin1(strData)
' Verify signature over data
r = sigVerifyData(strSig64, abData, strCertFile, "sha256WithRSAEncryption")
Debug.Print "sigVerifyData returns " & r & " (expecting 0)"
End Sub
/// <summary>
/// Check that a given X.509 certificate was issued by the issuer and has not expired.
/// </summary>
public static void Mex_ValidateCert()
{
string strCert = "emisor.cer";
string strIssuerCert = "satac.cer";
// 1. Was this cert signed by the purported issuer?
int n = X509.VerifyCert(strCert, strIssuerCert);
Console.WriteLine("X509_VerifyCert returns " + n);
if (n < 0) {
Console.WriteLine("ERROR: Validation failed");
} else if (n > 0) {
Console.WriteLine("ERROR: " + General.ErrorLookup(n));
} else {
Console.WriteLine("OK, cert was signed by issuer.");
}
// 2. Is this cert still valid now?
bool isValidNow = X509.CertIsValidNow(strCert);
Console.WriteLine("X509_CertIsValidNow returns " + isValidNow);
if (!isValidNow) {
Console.WriteLine("ERROR: cert has expired");
} else {
Console.WriteLine("OK, cert is still valid now.");
}
}
'/**
' Check that a given X.509 certificate really was issued by the issuer and has not expired.
'**/
Public Sub Mex_ValidateCert()
Dim strCert As String
Dim strIssuerCert As String
Dim nRet As Long
' INPUT: Filenames of certificate to be checked and issuer's certificate
strCert = "emisor.cer"
strIssuerCert = "satac.cer"
' 1. Was this cert signed by the purported issuer?
nRet = X509_VerifyCert(strCert, strIssuerCert, 0)
Debug.Print "X509_VerifyCert returns " & nRet
If nRet < 0 Then
Debug.Print "ERROR: Validation failed"
ElseIf nRet > 0 Then
Debug.Print "ERROR: " & pkiErrorLookup(nRet)
Else
Debug.Print "OK, cert was signed by issuer."
End If
' 2. Is this cert still valid now?
nRet = X509_CertIsValidNow(strCert, 0)
Debug.Print "X509_CertIsValidNow returns " & nRet
If nRet < 0 Then
Debug.Print "ERROR: cert has expired"
ElseIf nRet > 0 Then
Debug.Print "ERROR: " & pkiErrorLookup(nRet)
Else
Debug.Print "OK, cert is still valid now."
End If
End Sub
/// <summary>
/// Convert an X.509 certificate file into a base64 string suitable for XML Certificado element
/// </summary>
public static void Mex_CertToBase64String()
{
string strCertFile = "emisor.cer";
// Read in certificate file's data to a string
string strCertString = X509.ReadStringFromFile(strCertFile);
Console.WriteLine("For certificate '" + strCertFile + "':");
Console.WriteLine(strCertString);
// Check that the two versions are identical by computing SHA-1 thumbprints
string strThumb1 = X509.CertThumb(strCertFile, HashAlgorithm.Sha1);
string strThumb2 = X509.CertThumb(strCertString, HashAlgorithm.Sha1);
Console.WriteLine("SHA-1(file) =" + strThumb1);
Console.WriteLine("SHA-1(string)=" + strThumb2);
if (strThumb1 == strThumb2) {
Console.WriteLine("Certificates are identical");
} else {
Console.WriteLine("ERROR: certificates do not match");
}
}
'/**
' Convert an X.509 certificate file into a base64 string suitable for Certificado field in XML,
' and show how this string form can be treated just like the .cer file.
'**/
Public Sub Mex_CertToBase64String()
Dim strCertString As String
Dim strCertFile As String
Dim strThumb1 As String
Dim strThumb2 As String
strCertFile = "emisor.cer"
' Read in certificate file's data to a string
strCertString = x509ReadStringFromFile(strCertFile)
Debug.Print "For certificate '" & strCertFile & "':"
Debug.Print strCertString
' Check that the two versions of the certificate are identical by computing their SHA-1 thumbprints
strThumb1 = x509CertThumb(strCertFile)
strThumb2 = x509CertThumb(strCertString)
Debug.Print "SHA-1(file) =" & strThumb1
Debug.Print "SHA-1(string)=" & strThumb2
If strThumb1 = strThumb2 Then
Debug.Print "Certificates are identical"
Else
Debug.Print "ERROR: certificates do not match"
End If
End Sub
/// <summary>
/// Extract the serial number from a SAT-issued X.509 certificate
/// </summary>
public static void Mex_SATSerialNumber()
{
string strCertFile = "emisor.cer";
// Extract the certificate's serial number
string strSerialNumber = X509.QueryCert(strCertFile, "serialNumber");
Console.WriteLine("X.509 Serial Number=0x" + strSerialNumber);
// Decode from hex-encoded integer to string of ASCII digits
byte[] serialBytes = Cnv.FromHex(strSerialNumber);
string strSerialSAT = Encoding.ASCII.GetString(serialBytes);
Console.WriteLine("Decoded SAT Format ='" + strSerialSAT + "'");
}
'/**
' Extract the serial number from a SAT-issued X.509 certificate and display in SAT integer format
'**/
Public Sub Mex_SAT_SerialNumber()
Dim nLen As Long
Dim strCertFile As String
Dim strSerialNumber As String
Dim strSerialSAT As String
' Extract the certificate's serial number
strCertFile = "emisor.cer"
strSerialNumber = x509QueryCert(strCertFile, "serialNumber")
Debug.Print "X.509 Serial Number=0x" &strSerialNumber
' Decode from hex-encoded integer to string of ASCII digits
strSerialSAT = StrConv(cnvBytesFromHexStr(strSerialNumber), vbUnicode)
Debug.Print "Decoded SAT Format ='" &strSerialSAT &"'"
End Sub
/// <summary>
/// Check that the keys in the private key and certificate match.
/// </summary>
public static void Mex_CheckKeyAndCertMatch()
{
string strCertFile = "emisor.cer";
string strKeyFile = "emisor.key";
// Use StringBuilder types for password and private key so we can wipe them
StringBuilder sbPassword = new StringBuilder("12345678a"); // CAUTION: DO NOT hardcode production passwords!
// 1. Read in private key from encrypted .key file
StringBuilder sbPrivateKey = Rsa.ReadPrivateKey(strKeyFile, sbPassword.ToString());
if (sbPrivateKey.Length > 0) {
Console.WriteLine("Private key is " + Rsa.KeyBits(sbPrivateKey.ToString()) + " bits");
} else {
Console.WriteLine("ERROR: Cannot read private key file.");
return;
}
// 2. Clean up password
Wipe.String(sbPassword);
// 3. Read in public key from certificate
string strPublicKey = Rsa.ReadPublicKey(strCertFile).ToString();
if (strPublicKey.Length > 0) {
Console.WriteLine("Public key is " + Rsa.KeyBits(strPublicKey) + " bits");
} else {
Console.WriteLine("ERROR: Cannot read certificate file.");
return;
}
// 4. See if the two key strings match
int nRet = Rsa.KeyMatch(sbPrivateKey.ToString(), strPublicKey);
if (nRet == 0) {
Console.WriteLine("OK, key strings match.");
} else {
Console.WriteLine("FAILED: key strings do not match.");
}
// 5. Clean up private key string
Wipe.String(sbPrivateKey);
}
'/**
' Check that the keys in the private key and certificate match.
'**/
Public Sub Mex_CheckKeyAndCertMatch()
Dim strCertFile As String
Dim strKeyFile As String
Dim strPassword As String
Dim strPublicKey As String
Dim strPrivateKey As String
Dim nRet As Long
' INPUT: filenames for certificate and private key files
strCertFile = "emisor.cer"
strKeyFile = "emisor.key"
' Test password - CAUTION: DO NOT hardcode production passwords!
strPassword = "12345678a"
' 1. Read in private key from encrypted .key file
strPrivateKey = rsaReadPrivateKey(strKeyFile, strPassword, 0)
If Len(strPrivateKey) > 0 Then
Debug.Print "Private key is " &rsaKeyBits(strPrivateKey) & " bits"
Else
Debug.Print "ERROR: Cannot read private key file."
Exit Sub
End If
' 2. Clean up password as we are done with it
strPassword = wipeString(strPassword)
' 3. Read in public key from certificate
strPublicKey = rsaReadPublicKey(strCertFile)
If Len(strPublicKey) > 0 Then
Debug.Print "Public key is " &rsaKeyBits(strPublicKey) & " bits"
Else
Debug.Print "ERROR: Cannot read certificate file."
Exit Sub
End I
' 4. See if the two key strings match
nRet = RSA_KeyMatch(strPrivateKey, strPublicKey)
If nRet = 0 Then
Debug.Print "OK, key strings match."
Else
Debug.Print "FAILED: key strings do not match."
End If
' 5. Clean up private key string
strPrivateKey = wipeString(strPrivateKey)
End Sub
/// <summary>
/// Extract various details from a certificate string.
/// </summary>
public static void Mex_QueryCertString()
{
string strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJsZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAjBgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxVPWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1frvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04ypn0fGhTg8GV1rfvqY7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN";
// 1. Get the Issuer's distinguished name
string strOutput = X509.QueryCert(strCertificado, "issuerName");
Console.WriteLine("ISSUER= [" + strOutput + "]");
// 2. Get the Subject's distinguished name
strOutput = X509.QueryCert(strCertificado, "subjectName");
Console.WriteLine("SUBJECT=[" + strOutput + "]");
// 3. Get the Serial Number
strOutput = X509.QueryCert(strCertificado, "serialNumber");
Console.WriteLine("X.509 Serial Number=0x" + strOutput);
// 4. Get the expiry date
strOutput = X509.QueryCert(strCertificado, "notAfter");
Console.WriteLine("Expires on: " + strOutput);
// 5. Get the signature algorithm
string strQuery = "signatureAlgorithm";
strOutput = X509.QueryCert(strCertificado, strQuery);
Console.WriteLine(strQuery + "=" + strOutput);
}
'/**
' Extract various details from a certificate string.
'**/
Public Sub Mex_QueryCertString()
Dim strCertificado As String
Dim strOutput As String
Dim strQuery As String
' INPUT: Certificado string from XML file. This is the same as in the file emisor.cer expiring 2028-12-07.
strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJ" & _
"sZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTV" & _
"BSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAjBgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1" & _
"tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qa" & _
"uvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxV" & _
"PWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1f" & _
"rvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04yp" & _
"n0fGhTg8GV1rfvqY7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN"
' 1. Get the Issuer's distinguished name (converting any UTF-8 characters to Latin-1)
strOutput = x509QueryCert(strCertificado, "issuerName", PKI_X509_LATIN1)
Debug.Print "ISSUER= [" & strOutput & "]"
' 2. Get the Subject's distinguished name
strOutput = x509QueryCert(strCertificado, "subjectName", PKI_X509_LATIN1)
Debug.Print "SUBJECT=[" & strOutput & "]"
' 3. Get the Serial Number
strOutput = x509QueryCert(strCertificado, "serialNumber", PKI_X509_LATIN1)
Debug.Print "X.509 Serial Number=0x" & strOutput
' 4. Get the expiry date
strOutput = x509QueryCert(strCertificado, "notAfter", PKI_X509_LATIN1)
Debug.Print "Expires on: " & strOutput
' 5. Get the signature algorithm
strQuery = "signatureAlgorithm"
strOutput = x509QueryCert(strCertificado, strQuery, PKI_X509_LATIN1)
Debug.Print strQuery & "=" & strOutput
End Sub
Expected output:
ISSUER= [CN=AC de pruebas;O=SERVICIO DE ADMINISTRACION TRIBUTARIA;OU=SAT-IES Ficticia;C=MX;1.2.840.113549.1.9.2=Responsable: Esto no es un genuino SAT certificado] SUBJECT=[CN=ACCEM SERVICIOS EMPRESARIALES SC;2.5.4.41=ACCEM SERVICIOS EMPRESARIALES SC;O=ACCEM SERVICIOS EMPRESARIALES SC;C=MX;E=pruebas@accem.com.mx;2.5.4.45=AAA010101AAA / HEGT7610034S2;SERIALNUMBER= / HEGT761003MDFRNN09] X.509 Serial Number=0x3330303031303030303030333030303233373038 Expires on: 2028-12-07T00:00:01Z signatureAlgorithm=sha256WithRSAEncryption
/// <summary>
/// Create the SHA-256 digest of the input string after converting to UTF-8 encoding
/// </summary>
/// <returns>Digest value in hex</returns>
public static string Mex_CreateDigestFromString()
{
///
string strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||";
Console.WriteLine("INPUT=" + strData);
// 1. Convert string to UTF-8-encoded byte array
byte[] abData = Encoding.UTF8.GetBytes(strData);
// 2. Create the message digest hash
string strDigest = Hash.HexFromBytes(abData, HashAlgorithm.Sha256);
// OUTPUT: Display digest in hex format
Console.WriteLine("Digest=" + strDigest);
Debug.Assert(strDigest.Equals("c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e", StringComparison.OrdinalIgnoreCase));
return strDigest.ToLower();
}
'/**
' Create the SHA-256 digest of the input string after converting to UTF-8 encoding.
' @return Digest value in hex
'**/
Public Function Mex_CreateDigestFromString() As String
Dim strData As String
Dim abData() As Byte
Dim strDigest As String
' INPUT: Our original piped-string data (NB includes non-ASCII character ó (LATIN SMALL LETTER O WITH ACUTE U+00F3)
' from file cfdv40-ejemplo.xml
strData = "||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|99999|A1234|05|18|2021|09|ED1752FE-E865-4FF2-BFE1-0F552E770DC9|AAA010101AAA|Esta es una demostración|630|0123456789|BASJ600902KL9|Juanito Bananas De la Sierra|99999|MEX|0000000000000|630|S01|01010101|00001|1.5|C81|TONELADA|ACERO|1500000|2250000|01|2250000|002|Tasa|1.600000|360000|2250000|001|Tasa|0.300000|247500|51888|95141904|00002|1.6|WEE|TONELADA|ALUMINIO|1500|2400|02|2400|002|Tasa|1.600000|384|2400|001|Tasa|0.300000|264|AAA010101AAA|NombreACuentaTerceros|630|99999|15 48 4567 6001234|84101604|00003|1.7|G66|TONELADA|ZAMAC|10000|17000|0|03|17000|002|Tasa|1.600000|2720|17000|001|Tasa|0.300000|1870|25201513|055155|1.0|UNIDAD|PARTE EJEMPLO|1.00|1.00|15 48 4567 6001235|001|247000|003|500|247500|1.00|002|Tasa|1.600000|360000|360000||"
Debug.Print "INPUT=" & strData
' 1. Convert VBA string to UTF-8-encoded byte array
abData = cnvUTF8BytesFromLatin1(strData)
' 2. Create the message digest hash
strDigest = hashHexFromBytes(abData, PKI_HASH_SHA256)
' OUTPUT: Display digest in hex format
Debug.Print "Digest=" & strDigest
Mex_CreateDigestFromString = strDigest
' Correct c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e
Debug.Assert strDigest = "c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e"
Mex_CreateDigestFromString = strDigest
End Function
/// <summary>
/// Extract the message digest from a signature (sello) string using the X.509 certificate.
/// </summary>
/// <returns></returns>
public static string Mex_ExtractDigestFromSignature()
{
string strPublicKey;
string strSello = "Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w==";
string strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJsZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAjBgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxVPWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1frvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04ypn0fGhTg8GV1rfvqY7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN";
// 1. Read in Public key from X.509 certificate string
strPublicKey = Rsa.ReadPublicKey(strCertificado).ToString();
if (strPublicKey.Length == 0) {
Console.WriteLine("ERROR: failed to read Certificado string");
return "";
}
// Show we got something useful
int keyBits = Rsa.KeyBits(strPublicKey);
Console.WriteLine("Public key is " + keyBits + " bits long");
// 2. Convert base64 signature value to byte array
byte[] abData = Cnv.FromBase64(strSello);
int nSigLen = abData.Length;
int keyBytes = Rsa.KeyBytes(strPublicKey);
Console.WriteLine("Signature bytes=" + nSigLen);
Console.WriteLine("Key bytes=" + keyBytes);
if (nSigLen != keyBytes) {
Console.WriteLine("ERROR: key length does not match signature");
return "";
}
// 3. Decrypt using RSA public key
abData = Rsa.RawPublic(abData, strPublicKey);
if (abData.Length == 0) {
Console.WriteLine("ERROR: failed to decrypt RSA signature");
return "";
}
Console.WriteLine("Decrypted signature=" + Environment.NewLine + Cnv.ToHex(abData));
// 4. Decode to extract the original message digest
byte[] abMsg = Rsa.DecodeDigestForSignature(abData);
if (abMsg.Length == 0) {
Console.WriteLine("ERROR: failed to extract RSA message");
return "";
}
// 5. Convert to hex format
string strDigest = Cnv.ToHex(abMsg);
// OUTPUT: Digest in hex format
Console.WriteLine("Message digest: " + strDigest);
Console.WriteLine("Expected: c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e");
Debug.Assert(strDigest.Equals("c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e", StringComparison.OrdinalIgnoreCase), "Digest not expected");
return strDigest.ToLower();
}
'/**
' Extract the message digest from a signature (sello) string using the X.509 certificate (certificado) value.
' @return Digest value in hex
'**/
Public Function Mex_ExtractDigestFromSignature() As String
Dim strPublicKey As String
Dim strSello As String
Dim strCertificado As String
Dim abMsg() As Byte
Dim abData() As Byte
Dim nSigLen As Long
Dim strDigestHex As String
' INPUT: Base64 strings extracted from the XML file (Ref: cfdv40-ejemplo-signed.xml)
strSello = "Gnm1yXaIig5hr1dJ+88gjLY5usQxXP2s+zdmlLl4iokWENaUUlhpG/crkUFEzcJfdq1FbBxV/d/GN50MGuw2fP5f6MkRYz75UKaKfzubUak+SCkDzYot5jZRkO6hXKe4+KAfaulP7wa8Q7oSW5ccivppLnikme0CS3KtGBrQHU/q3pjNrw+jMvsnpUc1tx91REqMWrzMTZ2D6UkGdqn8i/0mLRU2vT8vPaNg/Hr2jpVVAgQmPtfvnfnORCWSm/5qZg9Tli7nTRRVwupF4o9ajH2/Is7LoCsIgZUg3wCHkZCjaKk2mJoK7FyMTsPXRRSGhoYgwi1kNHs1aY4RHNwe5w=="
strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJsZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAj" & _
"BgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66" & _
"g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxVPWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1frvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04ypn0fGhTg8GV1rfvq" & _
"Y7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN"
' 1. Read in Public key from X.509 certificate string directly
strPublicKey = rsaReadPublicKey(strCertificado)
If Len(strPublicKey) = 0 Then
Debug.Print "ERROR: failed to read Certificado string"
Exit Function
End If
' --Show we got something useful
Debug.Print "Public key is " & rsaKeyBits(strPublicKey) & " bits long"
' 2. Convert base64 signature value to byte array
abData = cnvFromBase64(strSello)
' 2a. Check lengths match
nSigLen = cnvBytesLen(abData)
Debug.Print "Signature bytes=" & nSigLen
Debug.Print "Key bytes =" & rsaKeyBytes(strPublicKey)
If nSigLen <> rsaKeyBytes(strPublicKey) Then
Debug.Print "ERROR: key length does not match signature"
Exit Function
End If
' 3.Decrypt using RSA public key
abData = rsaRawPublic(abData, strPublicKey)
If cnvBytesLen(abData) = 0 Then
Debug.Print "ERROR: failed to decrypt RSA signature"
Exit Function
End If
' Display result in hex
Debug.Print "Decrypted signature=" & vbCrLf & cnvHexStrFromBytes(abData)
' 4. Decode to extract the original message digest
abMsg = rsaDecodeMsg(abData, PKI_EMSIG_PKCSV1_5)
If cnvBytesLen(abMsg) = 0 Then
Debug.Print "ERROR: failed to extract RSA message"
Exit Function
End If
' 5. Convert to hex format
strDigestHex = cnvToHex(abMsg)
' OUTPUT: Digest in hex format
Debug.Print "Message digest: " & strDigestHex
Debug.Print "Expected: " & "c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e"
Debug.Assert strDigestHex = "c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e"
Mex_ExtractDigestFromSignature = LCase(cnvToHex(abMsg))
End Function
/// <summary>
/// Create and verify a signature (Sello) using the digest of the piped-string.
/// </summary>
public static void Mex_SignUsingDigest()
{
// Digest value in hex of piped-string
string strDigest = "c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e";
string strKeyFile = "emisor.key";
// Use a StringBuilder to store the password so we can wipe it
StringBuilder sbPassword = new StringBuilder("12345678a"); // CAUTION: DO NOT hardcode production passwords!
// 1. Convert digest value to bytes
byte[] abDigest = Cnv.FromHex(strDigest);
// 2. Sign over the digest value using the private key
string strSig64 = Sig.SignDigest(abDigest, strKeyFile, sbPassword.ToString(), SigAlgorithm.Rsa_Sha256);
Console.WriteLine("SIGNATURE VALUE=" + strSig64);
// Expecting Gnm1yX...Y4RHNwe5w==
// 3. Clean up
Wipe.String(sbPassword);
// 4. Now verify using the digest and the Certificado value
string strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJsZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAjBgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxVPWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1frvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04ypn0fGhTg8GV1rfvqY7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN";
int r = Sig.VerifyDigest(strSig64, abDigest, strCertificado, SigAlgorithm.Rsa_Sha256);
Console.WriteLine("Sig.VerifyDigest returns " + r + " (0 => signature verified)");
}
'/**
' Create and verify a signature (Sello) using the digest of the piped-string.
'**/
Public Sub Mex_SignUsingDigest()
Dim strDigest As String
Dim abDigest() As Byte
Dim strKeyFile As String
Dim strPassword As String
Dim strSig64 As String
Dim strCertificado As String
Dim r As Long
' INPUT: Digest value in hex of piped-string
strDigest = "c1492662dbd98ddbb7892d027c10808236e296eded13deec87179c2a8fdc742e"
strKeyFile = "emisor.key"
' Test password - CAUTION: DO NOT hardcode production passwords!
strPassword = "12345678a"
' 1. Convert digest value to bytes
abDigest = cnvFromHex(strDigest)
' 2. Sign the digest using the private key, output direct to a base64 string,
' using sha256WithRSAEncryption algorithm (RSA-SHA-256). Note the USEDIGEST flag
strSig64 = sigSignData(abDigest, strKeyFile, strPassword, "sha256WithRSAEncryption", PKI_SIG_USEDIGEST)
Debug.Print "SIGNATURE VALUE=" & strSig64
' Expecting Gnm1yX...Y4RHNwe5w==
' 3. Clean up
strPassword = wipeString(strPassword)
' 4. Now verify using the digest and the Certificado value
strCertificado = "MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwgbMxFjAUBgNVBAMTDUFDIGRlIHBydWViYXMxLjAsBgNVBAoTJVNFUlZJQ0lPIERFIEFETUlOSVNUUkFDSU9OIFRSSUJVVEFSSUExGTAXBgNVBAsTEFNBVC1JRVMgRmljdGljaWExCzAJBgNVBAYTAk1YMUEwPwYJKoZIhvcNAQkCEzJSZXNwb25zYWJ" & _
"sZTogRXN0byBubyBlcyB1biBnZW51aW5vIFNBVCBjZXJ0aWZpY2FkbzAeFw0yMTEyMDcwMDAwMDFaFw0yODEyMDcwMDAwMDFaMIH6MSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTV" & _
"BSRVNBUklBTEVTIFNDMQswCQYDVQQGEwJNWDEjMCEGCSqGSIb3DQEJARYUcHJ1ZWJhc0BhY2NlbS5jb20ubXgxJTAjBgNVBC0THEFBQTAxMDEwMUFBQSAvIEhFR1Q3NjEwMDM0UzIxHjAcBgNVBAUTFSAvIEhFR1Q3NjEwMDNNREZSTk4wOTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgwivvAantGnYVIO3+7yTdD1" & _
"tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qa" & _
"uvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMgMB4wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBAA1Pck4R2/W/YiY2pmVtv7mJ+dxCHJYal9NKv8gy89DTyW66g/DrpD4c2omv9LO8iRrzJbfcLupg7WKIri8aAdRU9/CwusNW3SrdJc9jjFMNPbvcYtjaRuPdQxV" & _
"PWDQ+VUMD/iDYKt+4Db4npvht6UjJtUEzaqSIj9jHKSROG/KbMBZVBzyp8/A45q+krpuGEj9UOhBi3InTxv03REbb7jnBWAjSQFOhqloYHg6PtXY2nslnOhRAnvFNY1bZKvkEOVbcpxyNQdhBXvuESHRYoiOCUdlpRsPkkJRbg0BGHpCLS7d8mebdM1A6L1emuqCa8j55vVblDqAk5Re6wOvtRodZbx3QGltsDKYATd3HvR+PfdT5cwlI3sGp1f" & _
"rvVNvYhO29BX7UXqarlQA5HyEfaK48s9vd71/yFDa1XsUXytcvGZoYP8L68tbz1V8rLB0CAcRCOYYbMo4YPkdbP4BlYd8sAsljwsbpDl0DymcUEj/07KN/5diXAB9oqUxzEy5pjPLItF+WAB5XkrLZFtMEwYkpOLGIozwA1sp4EHA+ww/EZU3NQsmLiiQd00e4yIuHA4wkQ8HCcSkE+x4tpJ60j3gGxwHpcY2fb9uruxKaMrtluUIFJJ0AW04yp" & _
"n0fGhTg8GV1rfvqY7aGUdoDyHZJQHaJrakHipIUYTJ8fk3Jf2UN"
r = sigVerifyData(strSig64, abDigest, strCertificado, "sha256WithRSAEncryption", PKI_SIG_USEDIGEST)
Debug.Print "sigVerifyData returns " & r & " (0 => signature verified)"
End Sub
emisor.cer) or the Certificado base64 string
"MIIFXTCCA0WgAwIBAgIUMzAwMDEwMDAwMDAz..." interchangably when calling
CryptoSys PKI functions.
C# code: MexicoSAT-csharp-3.0.0.zip
VBA code: MexicoSAT-vba-3.0.0.zip
The test files are in the work subdirectory in the zip files. The password for emisor.key is 12345678a
||4.0|A|123ABC|2021-12-07T23:59:59|99|30001000000300023708|CONDICIONES|1000|0.00|MXN|1.0|1500|P|03|PPD|...|Tasa|1.600000|360000|360000||
You can do this in several ways.
||.
(The pipe symbol "|" is Unicode character U+007C vertical line)To contact us or comment on this page, please send us a message.
This page first published 16 December 2005. Last updated 23 May 2026