How can I verify signature for open PGP using BouncyCastle - c#

How can I verify signature for open PGP using BouncyCastle?
I am using C#
I have pulic key http://itransact.com/support/toolkit/html-connection/pgp.php
I am using BouncyCastle as open pgp library
I have signature that I recieve in query string.
According to instruction (http://itransact.com/downloads/PCFullDocument-4.4.pdf p.145) algorithm is RSA.
I checked a lot of resource but no success. As I understood I need to pass public key and signature to some Verify method.
It is also not clear if I have to convert given public key in string format to some appropriate public key object. If I have to what is the type? I have tried to convert it to RsaKeyParameters but got error message about inappropriate block on public key.
At the moment I have the following code
private bool VerifyWithPublicKey(string data, byte[] sig)
{
RSACryptoServiceProvider rsa;
using (var keyreader = new StringReader(publicKey))
{
var pemReader = new PemReader(keyreader);
var y = (RsaKeyParameters)pemReader.ReadObject();
rsa = (RSACryptoServiceProvider)RSA.Create();
var rsaParameters = new RSAParameters();
rsaParameters.Modulus = y.Modulus.ToByteArray();
rsaParameters.Exponent = y.Exponent.ToByteArray();
rsa.ImportParameters(rsaParameters);
// compute sha1 hash of the data
var sha = new SHA1CryptoServiceProvider();
byte[] hash = sha.ComputeHash(Encoding.ASCII.GetBytes(data));
// This always returns false
return rsa.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), sig);
}

Using RSA is not your case. You need
Define public key
Convert public ket into PgPPublicKey
Get PgpSignature object
Verify signature
To verify signature you will need original data that was signed.
public class iTransactVerifier
{
private const string PublicKey = #"-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: 4.5
mQCNAjZu
-----END PGP PUBLIC KEY BLOCK-----";
public static bool Verify(string signature, string data)
{
var inputStream = ConvertStringToStream(signature);
PgpPublicKey publicKey = ReadPublicKeyFromString();
var stream = PgpUtilities.GetDecoderStream(inputStream);
PgpObjectFactory pgpFact = new PgpObjectFactory(stream);
PgpSignatureList sList = pgpFact.NextPgpObject() as PgpSignatureList;
if (sList == null)
{
throw new InvalidOperationException("PgpObjectFactory could not create signature list");
}
PgpSignature firstSig = sList[0];
firstSig.InitVerify(publicKey);
firstSig.Update(Encoding.UTF8.GetBytes(data));
var verified = firstSig.Verify();
return verified;
}
....
private static PgpPublicKey ReadPublicKeyFromString()
{
var varstream = ConvertStringToStream(PublicKey);
var stream = PgpUtilities.GetDecoderStream(varstream);
PgpObjectFactory pgpFact = new PgpObjectFactory(stream);
var keyRing = (PgpPublicKeyRing)pgpFact.NextPgpObject();
return keyRing.GetPublicKey();
}
}

Related

How to verify digital signature in C#

I am new here.
I am learning the digital signature in C#. The certificates are generated followed by this document. Other documents I read: RSACng,
X509Certificate2.
I am working on Windows 10 Pro 1809, .Net Core 2.1, VSCode.
class Program
{
static void Main(string[] args)
{
var passwd = "password";
// Get client certificate.
var clientCertPath = #"./Certificates/test.pfx";
var clientCert = new X509Certificate2(clientCertPath, passwd);
// Get server certificate.
var serverCertPath = #"./Certificates/test.cer";
var serverCert = new X509Certificate2(serverCertPath);
// Generate data.
var translateResultData = BuildData();
var content = String.Join('&', translateResultData.Select(p => String.Join('=', p.Key, p.Value)));
// Sign
var sign = SignatureUtil.Sign(data: content, clientCert: clientCert);
// translateResultData.TryAdd(key: "sign", value : sign);
// Copy content ONLY for test.
var checkSign = sign;
var checkContent = content;
// Verify
var valid = SignatureUtil.Verify(data: checkContent, signature: checkSign, serverCert: serverCert);
System.Console.WriteLine(valid);
}
}
public class SignatureUtil
{
public static string Sign(string data, X509Certificate2 clientCert)
{
using(var privateKey = clientCert.GetRSAPrivateKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = privateKey.SignData(
data: dataByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureByteArray);
}
}
public static bool Verify(string data, string signature, X509Certificate2 serverCert)
{
try
{
using(var publicKey = serverCert.GetRSAPublicKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = Convert.FromBase64String(signature);
return publicKey.VerifyData(
data: dataByteArray,
signature: signatureByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
}
}
catch (System.Exception)
{
return false;
}
}
}
Expected result: valid should be true because I am checking the original data.
Fact: The Verify method always returns false even the original data are passed.
Can you tell me what I did wrong?
I cannot tell you what is going wrong in your code since I cannot reproduce it. Here is a very detailed answer how you can sign using RSA and SHA256. Your approach and the one described in this answer are conceptually the same, but maybe there is a difference in your code compared to this answer.
And here is a example how in my company certificates associated with smart cards are used for signing and verifying signatures. One big difference is that we do not store the signature as a string but rather keep it as an array of bytes.
public byte[] SignData(byte[] data)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPrivateKey())
{
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPublicKey())
{
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}

How can I create a SHA1WithRSA signature

I have a signature generator in Java:
private static String getPvtKeyFromConfig = "merchantPvtKey";
private static String getPubKeyFromConfig = "merchantPubKey";
private static String getSaltFromConfig = "merchant_salt";
public static void main(String[] args) throws Exception {
// Generate Signature
String uniqueId="ab123";
byte[] data = Base64.decodeBase64(uniqueId);
java.security.Signature sig =java.security.Signature.getInstance("SHA1WithRSA");
sig.initSign(getPrivateFromSpec(getPvtKeyFromConfig));
sig.update(data);
byte[] signatureBytes = sig.sign();
System.out.println("Signature for uniqueId - "+uniqueId+": "+ Base64.encodeBase64String(signatureBytes));
}
How can I do it in C#?
I think this is what you are looking for:
static byte[] Sign(string text, string certSubject)
{
// Access a store
using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly);
// Find the certificate used to sign
RSACryptoServiceProvider provider = null;
foreach (X509Certificate2 cert in store.Certificates)
{
if (cert.Subject.Contains(certSubject))
{
// Get its associated CSP and private key
provider = (RSACryptoServiceProvider)cert.PrivateKey;
break;
}
}
if (provider == null)
throw new Exception("Certificate not found.");
// Hash the data
var hash = HashText(text);
// Sign the hash
var signature = provider.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
return signature;
}
}
static bool Verify(string text, byte[] signature, string certPath)
{
// Load the certificate used to verify the signature
X509Certificate2 certificate = new X509Certificate2(certPath);
// Get its associated provider and public key
RSACryptoServiceProvider provider = (RSACryptoServiceProvider)certificate.PublicKey.Key;
// Hash the data
var hash = HashText(text);
// Verify the signature with the hash
var result = provider.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
return result;
}
static byte[] HashText(string text)
{
SHA1Managed sha1Hasher = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1Hasher.ComputeHash(data);
return hash;
}
Sample usage:
var signature = Sign("To be or not to be, that is the question.", "CN=some_cert");
var result = Verify("To be or not to be, that is the question.", signature, "C:\\temp\\some_cert.cer");
Console.WriteLine("Verified: {0}", result);
Below C# code works for me for the exact java code mentioned in the question.
Few Notes :
.) Your project should have Target frameworks as 4.8
.) You should have existing private key
.) Using this Private key we can generate the .pfx certificate by using the OpenSSL commands.( we will have to generate the .crt first and then .pfx)
static string GenerateSignatureUsingCert(string dataToSign)
{
X509Certificate2 certificate = new X509Certificate2(#"C:\PFX\MyCertificate.pfx", "****", X509KeyStorageFlags.Exportable);
RSA privateKey1 = certificate.GetRSAPrivateKey();
Encoding unicode = Encoding.UTF8;
byte[] bytesInUni = unicode.GetBytes(dataToSign);
return Convert.ToBase64String(privateKey1.SignData(bytesInUni, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1));
}
Usage
string canonicalizeData = CanonicalizeHeaders(headers);
String data = null;
try
{
data = GenerateSignatureUsingCert(canonicalizeData);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("Signature: " + data);

How to verify the signature of a response using a public key with RSAwithSHA256 in C#?

I have a problem verifying a signature using the RSAwithSHA256 procedure in C#. The problem is that the VerifyHash function will always return false, although I believe I'm using the correct procedure. I know RSACryptoServiceProvider is using SHA1 by default for signing, but here I only want to verify the signature. What could have gone wrong?
#region // public //
public bool Verify(SMBillsAuthResponse response) {
RSACryptoServiceProvider csp = retrieveCryptoServiceProvider();
string verificationMessage = getVerificationMessage(response);
byte[] hash = getSha256Hash(verificationMessage);
byte[] signature = HexStringToByteArray(response.auth.signature);
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), signature);
}
#endregion
#region // auxiliary //
private RSACryptoServiceProvider retrieveCryptoServiceProvider() {
X509Certificate2 cert = new X509Certificate2(this.publicKeyFile);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
return csp;
}
private string getVerificationMessage(SMBillsAuthResponse response) {
return this.apiKey + response.auth.nonce + response.auth.timestamp + response.transactionId;
}
private byte[] getSha256Hash(string message) {
SHA256Managed sha256 = new SHA256Managed();
byte[] data = Encoding.Unicode.GetBytes(message);
byte[] hash = sha256.ComputeHash(data);
return hash;
}
#endregion
It seems that the only problem I had was wrong encoding of the data. Instead of Encoding.Unicode.GetBytes(message) I used Encoding.UTF8.GetBytes(message) and it worked.

How to verify a signature in .net generated in Android

The problem is the following:
I generate the key in Android (Xamarin.Droid):
public IPublicKey CreateKey(string keyID)
{
/*KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
"key1",
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
.build());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
Signature signature = Signature.getInstance("SHA256withRSA/PSS");
signature.initSign(keyPair.getPrivate());
// The key pair can also be obtained from the Android Keystore any time as follows:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey privateKey = (PrivateKey)keyStore.getKey("key1", null);
PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();*/
//App.Current.MainPage.DisplayAlert("Info", "Creating a new key pair", "Ok");
// UTILIZANDO RSA
KeyPairGenerator kpg =
KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, KEYSTORE_NAME);
kpg.Initialize(
new KeyGenParameterSpec.Builder(keyID,
KeyStorePurpose.Sign)
.SetSignaturePaddings(KeyProperties.SignaturePaddingRsaPss)
.SetDigests(KeyProperties.DigestSha1)
.Build()
);
KeyPair keyPair = kpg.GenerateKeyPair();
Log.Debug(TAG, "New key created for fingerprint authentication");
return keyPair.Public;
}
Then i generate a signature:
KeyStore.PrivateKeyEntry PKentry =
(KeyStore.PrivateKeyEntry)_keystore.GetEntry(keyID, null);
IPublicKey pk = (IPublicKey)PKentry.Certificate.PublicKey;
//this.pk = pk;
privKey = PKentry.PrivateKey;
//cipher.Init(Cipher.EncryptMode, privKey);
//byte[] output = cipher.DoFinal(Encoding.UTF8.GetBytes(input));
//String s = new string(cipher.DoFinal(input));
// signature
Signature sig = Signature.GetInstance("SHA1withRSA/PSS");
sig.InitSign(privKey);
byte[] inputDataToSign = Encoding.UTF8.GetBytes(input);
sig.Update(inputDataToSign);
byte[] signatureBytes = sig.Sign();
And i send the key and the signature to a ASP.net wep API 2 server.
Client side response generation:
RegistrationResponse registrationResponse = new RegistrationResponse();
string fcparams = Utils.Base64Encode(JsonConvert.SerializeObject(finalChallengeParams));
registrationResponse.fcParams = fcparams;
byte[] signedData = sign(fcparams, registrationRequest.username, facetID);
registrationResponse.signedData = signedData;
registrationResponse.Base64key = convertPublicKeyToString(publicKey);
...
...
private string convertPublicKeyToString(IPublicKey publicKey)
{
string publicKeyString = Base64.EncodeToString(publicKey.GetEncoded(), 0);
return publicKeyString;
}
I send it using Refit Nugget.
And this is the code i use when i receive the HTTPRequest on server side:
[Route("regResponse/")]
[HttpPost]
public IHttpActionResult ProcessClientRegistrationResponse([FromBody] RegistrationResponse registrationResponse)
{
//byte[] publicKeyBytes = Convert.FromBase64String(registrationResponse.Base64key);
byte[] publicKeyBytes = registrationResponse.Base64key;
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
/*****/
string alg = rsa.SignatureAlgorithm;
byte[] signedData = registrationResponse.signedData;
byte[] fcParamsBytes = Encoding.UTF8.GetBytes(registrationResponse.fcParams);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParameters);
SHA1Managed hash = new SHA1Managed();
byte[] hashedData;
hashedData = hash.ComputeHash(signedData);
/*********/
bool rsaCngDataOk1 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk2 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk3 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk4 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngHashOk1 = rsaCng.VerifyHash(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool dataOK1 = rsa.VerifyData(fcParamsBytes, new SHA1CryptoServiceProvider(), signedData);
bool dataOk2 = rsa.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
bool hashOk = rsa.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA1"), signedData);
return Ok(true);
}
EVERY bool is wrong. I think the problem is clearly on the public key.
The questions are,
does the method publickey.encode() do what i think? I think it converts my public key to a byte[] representation (source: Android developer Key Info)
do i convert the received byte[] representation of the key to a correct RSA key?
Is there any problem on algorithms? I don't think so but we never know...
I don't find the solution. I searched for ways to import public keys from strings in .net or c# and for ways to export Android Public key to string or byte[] but there's no much help for this concrete questions...
#James K Polk gave me the solution.
Apparently C# doesn't work well with PSS padding. I just had to change it to PKCS1. And i changed to digest algorithm too to SHA512.

What is the difference between signing by OpenSSL and Microsoft Cryptography libraries?

I wrote two methods for signing using RSA and SHA256, the first one with OpenSSL library and the second one with Microsoft Cryptography library.
OpenSSL implementation:
private string PasswordHandler(bool verify, object userdata)
{
return userdata.ToString();
}
private string Sign(string signParams)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pem");
string privateKey;
using (StreamReader sr = new StreamReader(privateCertPath))
{
privateKey = sr.ReadToEnd();
}
OpenSSL.Crypto.RSA rsa = OpenSSL.Crypto.RSA.FromPrivateKey(new BIO(privateKey), PasswordHandler, _password);
//hash method
MessageDigest md = MessageDigest.SHA1;
BIO b = new BIO(signParams);
CryptoKey ck = new CryptoKey(rsa);
byte[] res1 = MessageDigestContext.Sign(md, b, ck);
return Uri.EscapeDataString(System.Convert.ToBase64String(res1));
}
Cryptography implementation:
private string Sign(string data)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pfx");
X509Certificate2 privateCert = new X509Certificate2(privateCertPath, _password, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
// Get the bytes to be signed from the string
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
//const string sha256Oid = "2.16.840.1.101.3.4.2.1";
//HashAlgorithm algorithm = new SHA256CryptoServiceProvider();
//byte[] hashBytes = algorithm.ComputeHash(bytes);
//byte[] signature = privateKey1.SignHash(hashBytes, sha256Oid);
byte[] signature = privateKey1.SignData(bytes, "SHA256");
// Base 64 encode the sig so its 8-bit clean
return Convert.ToBase64String(signature);
}
Signing with OpenSSL works, generates valid digital signature but signing with Cryptography lib generates invalid signature so my question is what I implemented wrong?
I tried to use different encoding but it did not help. Certificates are generated correctly.
It might by also useful to tell basic info about the .pem certificate:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC

Categories