I'm using BouncyCastle to sign transactions for the EOS blockchain. Similar to Bitcoin signatures, the signature needs to have a "recovery ID" as the 1st bytes. My code looks as follows:
var curve = SecNamedCurves.GetByName("secp256r1");
var domain = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H);
var p = new Org.BouncyCastle.Math.BigInteger(privateKey);
var keyParameters = new ECPrivateKeyParameters(
new Org.BouncyCastle.Math.BigInteger(1,privateKey),
domain);
ISigner signer = SignerUtilities.GetSigner("SHA-256withPLAIN-ECDSA");
signer.Init(true, keyParameters);
signer.BlockUpdate(message, 0, message.Length);
var signature = signer.GenerateSignature();
This seems to work as expected but I'm unable to get the recovery ID.
Here's an example of how another library does this.
https://github.com/EOSIO/eosjs-ecc/blob/a806b93fbbccec8d38c0c02998d204ff2040a6ae/src/ecdsa.js#L199
Can someone explain how I can do that same using BouncyCastle please?
This was another post that seemed helpful, but wasn't specific to how to do this with BouncyCastle https://bitcoin.stackexchange.com/questions/83035/how-to-determine-first-byte-recovery-id-for-signatures-message-signing
Related
I want to digitally sign pdf documents using iText 7. The signature is created by an external service which returns a PKCS1 signature only. I then have to create and apply the PKCS7.
There is a good documentation for this scenario from iText: https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
Sample application
I have created a sample application which signs pdf documents via local certificate. This sample application can be cloned from https://github.com/suntsu42/PdfSignSamplePkcs1. In this sample application are two different ways of creating the PKCS7. Once manually and once via a IExternalSignature(PrivateKeySignature) implementation.
For both cases, the pdf digest which must be signed is created in the same way. The only difference is the way the PKCS7 is created.
The project on github (https://github.com/suntsu42/PdfSignSamplePkcs1) is complete and self contained. In the resources folder is a private key file (pfx) used for creating the signature as well as the root certificate. In order to run the example, it should be enough to just change the value of the resourcePath variable to accommodate your local system.
The signature creation can be toggled by changeing the value of createSignatureViaPlainPkcs1
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
namespace PdfSignSamplePkcs1
{
class Program
{
static void Main(string[] args)
{
// TODO >> Change this path based on your local system
var resourcePath = #"c:\project\github\PdfSignSamplePkcs1\Resources\";
var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
var privateKeyPassword = "test";
// ############
// Change value in order to create the PKCS7
// either manually or via Itext
// ############
bool createSignatureViaPlainPkcs1 = false;
//delete signed file if it exists
if (System.IO.File.Exists(signedPdfPath))
System.IO.File.Delete(signedPdfPath);
var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
byte[] pdfDigest = null;
//#1 Prepare pdf for signing
var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
byte[] preparedToSignPdf = null;
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, 32000);
//get digest to be signed
pdfDigest = external.PdfDigest;
preparedToSignPdf = baos.ToArray();
}
}
}
//#2 Create PKCS7
SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
byte[] signatureAsPkcs7 = null;
if (createSignatureViaPlainPkcs1)
signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
else
signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature
//#3 apply cms(PKCS7) to prepared pdf
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
}
}
}
}
}
}
}
Manual creation of the pkcs7 signature
In this sample, first create a PKCS1 signature using a local certificate. The created PKCS1 signature then is applied to the PdfPKCS7 container via SetExternalDigest
The pdf created in this way is invalid.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Create PKCS7 signature using PrivateKeySignature implementation
In this sample, the PKCS7 is created using iText PrivateKeySignature. The signature is created with the same digest and the same private key as in the other example.
The pdf created here is valid. But since this approach doesn't allow the use of an external service for creating the signature, i cannot use it.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Remark (edit)
I think the reason for the problem is that i don't use GetAuthenticatedAttributeBytes for getting the hash to be signed. But i cannot use this method. The timestamp, ocsp and CLR are returned as part of the service call. Since the parameters for GetAuthenticatedAttributeBytes must be the same as when the signature is applied, i guess i cannot use this functionality.
Question
What is the reason the signature created via RSA is invalid in the resulting pdf?
edit: To be more specific: How can i create a valid pkcs7 container when the signature service returns PKCS1, Timestamp, Ocsp and CRL. What exactly must be signed in this case?
One error is fairly obvious:
In CreatePKCS7 you sign the to-be-signed attributes of the signature container (GetAuthenticatedAttributeBytes) which contain the document digest (Digest):
var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
byte[] extSignature = signature.Sign(sh);
In CreatePKCS7ViaPkcs1 you sign the document digest (Digest) itself:
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
In both cases you continue by injecting the returned signature into a PdfPKCS7:
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
and
sgn.SetExternalDigest(signature, null, "RSA");
respectively.
That the first variant works, is an indicator telling you that SetExternalDigest expects as first parameter the signature (externally signed digest) of the to-be-signed attributes, not of the document digest directly.
Thus, in CreatePKCS7ViaPkcs1 you simply sign the wrong bytes!
You can fix CreatePKCS7ViaPkcs1 by (just like in CreatePKCS7) creating the PdfPKCS7 instance before and receiving the to-be-signed attributes from it (using GetAuthenticatedAttributeBytes). You may or may not have to hash the result hereof before signing - I'm not so well-versed in .NET crypto APIs.
I've encountered a problem when trying to implement digital signature with iText7. According to the documentation of iText and couple examples I need to implement IExternalSignature (here), like so: IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm); but this is where I get the exception:
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
which basically means "Invalid key to use in the current state"(?).
Most of the examples are from older version of iText library and Java (I'm using C#) and I cant quite figute it out.
I'll be very grateful for any tips. Thanks!
EDIT:
Here's some example code just to replicate the exception:
static void Main(string[] args)
{
string output = "D:/Development/TestApp/testOutputMoje.pdf";
string input = "D:/Development/TestApp/testInput.pdf";
PdfReader reader = new PdfReader(input);
string digestAlgorithm = DigestAlgorithms.SHA256;
List<X509Certificate2> oCertChain = new List<X509Certificate2>();
//getting certificates from store
X509Store store = new X509Store(StoreName.My);
store.Open(OpenFlags.ReadOnly);
foreach (var oCert in store.Certificates)
{
oCertChain.Add(oCert);
}
store.Close();
//siginig with first certificate - just example
var cert = oCertChain[0];
//exception here:
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert.PrivateKey).Private;
X509Certificate[] bouncyCert = { Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(cert) };
StampingProperties stampProp = new StampingProperties();
stampProp.PreserveEncryption();
IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm);
PdfSigner signer = new PdfSigner(reader, new FileStream(output, FileMode.Create), stampProp);
signer.SignDetached(signature, bouncyCert, null, null, null, 0, CryptoStandard.CADES);
reader.Close();
}
Couple more informations about the certificate (I cant show You any details unfortunately)
problem is in certificate. It has to be marked as "Exportable". I don't know how solve this case, but in my case I used own certificate this way:
var cert = new X509Certificate2();
cert.Import(File.ReadAllBytes(#"C:\temp\certificate.cer"), "password", X509KeyStorageFlags.Exportable);
This works.
You need to find the way to change your certificate flag. Maybe something with store.Open(OpenFlags.MaxAllowed), but this doesn't work.
I am trying to create a Apple wallet passes in my system, after reading Different S/MIME signature between OpenSSL and C# and Apple Passbook coupons from C#, the system can create .pkasss automatically now.
And my problem is signature cannot create successfully in actual. If I using iPhone and try to open the .pkpass file, it can't be open!! I find out that is the problem is coming form signature, if I using mac to create a signature in terminal, it create a 3326bytes size signature; my code only can create a 3002 bytes file, which means the signature must be miss something.
Does Mac OS X method have a big difference between Windows OS method?
Has anyone faced this problem before? Does anyone know why the signatures are different?
Does anyone know how t fix it?
This is my source code:
var cert = new X509Certificate2(assetsFolder + p12File, p12Password);
var buffer = File.ReadAllBytes(Path.Combine(assetsFolder, "manifest.json"));
var cont = new ContentInfo(buffer);
var cms = new SignedCms(cont, true);
var signer = new CmsSigner(cert)
{
IncludeOption = X509IncludeOption.ExcludeRoot,
SignerIdentifierType = SubjectIdentifierType.IssuerAndSerialNumber,
};
cms.ComputeSignature(signer, true);
var myCmsMessage = cms.Encode();
File.WriteAllBytes(Path.Combine(assetsFolder, "signature"), myCmsMessage);
Big Thanks!!!
----------------------------------UPDATE---------------------------------
I found the ans of signature!!!
The setting of OID and SignerIdentifierType will affert the signature
Here is my solution:
byte[] buffer = File.ReadAllBytes(Path.Combine(assetsFolder, "manifest.json"));
X509Certificate2 cert = new X509Certificate2(assetsFolder + p12File, p12Password);
var oid = new Oid("1.2.840.113549.1.7.2");
ContentInfo contentInfo = new ContentInfo(oid, buffer);
SignedCms signedCms = new SignedCms(contentInfo, true);
var cmsSigner = new CmsSigner(cert);
cmsSigner.IncludeOption = X509IncludeOption.ExcludeRoot;
cmsSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
cmsSigner.SignerIdentifierType = SubjectIdentifierType.SubjectKeyIdentifier;
signedCms.ComputeSignature(cmsSigner);
byte[] myCmsMessage = signedCms.Encode();
return myCmsMessage;
I am trying to Apply HMAC SHA-512 algorithm using a secret key in python but I am not finding the correct way to sign.
I was able to do using nodejs and c# but not for python
in nodejs
return crypto.createHmac('sha512', new Buffer(secretString, 'base64')).update(new Buffer(stringToSign)).digest('base64');
in c#
byte[] secretkeyBytes = Encoding.UTF8.GetBytes(apiSecret);
byte[] inputBytes = Encoding.UTF8.GetBytes(stringToSign);
using (var hmac = new HMACSHA512(secretkeyBytes))
{
byte[] hashValue = hmac.ComputeHash(inputBytes);
signature = System.Convert.ToBase64String(hashValue);
}
but in python I am not figuring a way to do that.
I already tried:
b_secret_string=base64.b64encode(secret_string.encode('utf-8'))
hash = hmac.new(base64.b64encode(b_secret_string),'',sha512)
hash.update(string_to_sign.encode('utf-8'))
signature = base64.b64encode(hash.digest())
and
hashed = hmac.new(str(secret_string.encode('utf-8')),'',sha512)
hashed.update(string_to_sign.encode('utf-8'))
signature = base64.b64encode(hashed.digest())
but it is not working. If possible can someone give me a light? I really appreciate it.
UPDATE:
Also tried with the following:
string_to_sign = string_to_sign.encode('utf-8')
secret_string = secret_string.encode('utf-8')
hash = hmac.new(secret_string, string_to_sign, hashlib.sha512)
signature = base64.b64encode(hash.digest())
tried using hexdigest() too:
signature = base64.b64encode(hash.hexdigest())
if someone wants to test it. Should return the same as this code in nodejs.
var crypto = require('crypto');
a = new Buffer('PRIVATE_KEY', 'base64');
hash = crypto.createHmac('sha512', a)
stringToSign = 'Stack OverFlow Funtime';
hash.update(new Buffer(stringToSign));
console.log(hash.digest('base64'));
$ node example.js
ugmH0VdttdAxGdpzNJnaNn1KlVS4wBzoK//dsPuvK65Zsl8FgT+3aLGnsEubThlv5/3chfyMmsUH//LdS1MXqg==
I found a way to do the same in python. Answering here if someone pass for the same issue in future.
hmac_key = base64.b64decode(secret_string)
signature = hmac.new(hmac_key, string_to_sign, hashlib.sha512)
signature_b64 = signature.digest().encode('base64')
signature_lines = signature_b64.splitlines()
signature_b64 = ''.join(signature_lines)
How about:
import hashlib
print hashlib.sha512('some string').hexdigest()
I'm looking to convert some C# code to the equivalent in Java.
The C# code takes some string content, and a signature (generated using the private key, on a seperate machine) and combined with the public key it verifies the signature matches, providing a level of assurance that the request has not been tampered with.
public bool VerifySignature(string content, byte[] signatureBytes, AsymmetricAlgorithm publicKey)
{
var hash = new MD5CryptoServiceProvider();
byte[] dataBuffer = Encoding.ASCII.GetBytes(content);
var cs = new CryptoStream(Stream.Null, hash, CryptoStreamMode.Write);
cs.Write(dataBuffer, 0, dataBuffer.Length);
cs.Close();
var deformatter = new RSAPKCS1SignatureDeformatter(publicKey);
deformatter.SetHashAlgorithm("MD5");
return deformatter.VerifySignature(hash, signatureBytes);
}
The public key itself is an X509 Certificate - constructed from a .cer file, stored as assembly resource i.e.
byte[] data; // data is read from a resource stream.
var publicKey = new X509Certificate2(data, "", X509KeyStorageFlags.MachineKeySet).PublicKey.Key
What I'm looking to do is emulate this functionality in Java, so I can verify the signature generated by some code in C#... I've started investigating the crypto functionality of Java, but I'm a bit of a java noob. Here's what I've come up with so far:
byte[] certContents=null;
byte[] signature=null;
String contents = "abc";
// load cert
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certContents));
// grab public key
RSAPublicKey publicKey = (RSAPublicKey)cert.getPublicKey();
// get sha1 hash for contents
Mac mac = Mac.getInstance("HmacSHA1");
mac.update(contents.getBytes());
byte[] hash = mac.doFinal();
// get cipher
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// verify signature of contents matches signature passed to method somehow (and this is where I'm stuck)
Can anyone provide any insight into how I can verify the signature - or provide links to some resources which might explain the java.crypto and java.security.cert usage better then the run of the mill java docs.
That C# code looks really confusing to me. It use SHA1CryptoServiceProvider but uses MD5 hash so I can't tell which hashing algorithm it's using. I assume it's MD5.
The signature verification process involves padding so your code wouldn't work. Following is some snippet from my code and you can use it to verify the signature. data is the bytes to sign and sigBytes holds the signature.
String algorithm = "MD5withRSA";
// Initialize JCE provider
Signature verifier = Signature.getInstance(algorithm);
// Do the verification
boolean result=false;
try {
verifier.initVerify(cert); // This one checks key usage in the cert
verifier.update(data);
result = verifier.verify(sigBytes);
}
catch (Exception e) {
throw new VerificationException("Verification error: "+e, e);
}