2015-04-02 8 views
3

Я прошел через большое количество примеров того, как люди используют Bouncy Castle для динамического создания RSA Key Pairs, а затем подписывают и проверяют все в одном блоке кода. Эти ответы были отличными и действительно помогли мне быстро подняться!Bouncy Castle Sign and Verify Сертификат SHA256 с C#

Это значит, что мне нужно создать клиент, который может проходить через вызовы WCF JSON, открытый ключ, который будет использоваться позже для проверки подписи, и я не могу заставить это работать. Во-первых здесь код используется для создания сертификата:

public System.Security.Cryptography.X509Certificates.X509Certificate2 LoadCertificate() 
    { 
     System.Security.Cryptography.X509Certificates.X509Certificate2 returnX509 = null; 
     //X509Certificate returnCert = null; 

     try 
     { 
      returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword); 
      //returnCert = DotNetUtilities.FromX509Certificate(returnX509); 
     } 
     catch 
     { 
      Console.WriteLine("Failed to obtain cert - trying to make one now."); 

      try 
      { 
       Guid nameGuid = Guid.NewGuid(); 
       AsymmetricCipherKeyPair kp; 
       var x509 = GenerateCertificate("CN=Server-CertA-" + nameGuid.ToString(), out kp); 
       SaveCertificateToFile(x509, kp, CertificateName, CertificateAlias, CertificatePassword); 
       returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword); 
       //returnCert = DotNetUtilities.FromX509Certificate(returnX509); 
       Console.WriteLine("Successfully wrote and retrieved cert!"); 
      } 
      catch (Exception exc) 
      { 
       Console.WriteLine("Failed to create cert - exception was: " + exc.ToString()); 
      } 
     } 

     return returnX509; 
    } 

С этими словами завершена, то я использую следующий код, чтобы подписать сообщение:

public string SignData(string Message, string PrivateKey) 
    { 
     try 
     { 
      byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message); 
      byte[] privateKey = UnicodeEncoding.ASCII.GetBytes(PrivateKey); 

      string curveName = "P-521"; 
      X9ECParameters ecP = NistNamedCurves.GetByName(curveName); 
      ECDomainParameters ecSpec = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed()); 

      ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); 
      BigInteger biPrivateKey = new BigInteger(privateKey); 
      ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters(biPrivateKey, ecSpec); 

      signer.Init(true, keyParameters); 
      signer.BlockUpdate(orgBytes, 0, orgBytes.Length); 

      byte[] data = signer.GenerateSignature(); 
      //Base64 Encode 
      byte[] encodedBytes; 
      using (MemoryStream encStream = new MemoryStream()) 
      { 
       base64.Encode(orgBytes, 0, orgBytes.Length, encStream); 
       encodedBytes = encStream.ToArray(); 
      } 

      if (encodedBytes.Length > 0) 
       return UnicodeEncoding.ASCII.GetString(encodedBytes); 
      else 
       return ""; 
     } 
     catch (Exception exc) 
     { 
      Console.WriteLine("Signing Failed: " + exc.ToString()); 
      return ""; 
     } 
    } 

И, наконец, моя попытка проверить:

public bool VerifySignature(string PublicKey, string Signature, string Message) 
    { 
     try 
     { 
      AsymmetricKeyParameter pubKey = new AsymmetricKeyParameter(false); 

      var publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(PublicKey)); 
      ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); 
      byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message); 

      signer.Init(false, publicKey); 
      signer.BlockUpdate(orgBytes, 0, orgBytes.Length); 

      //Base64 Decode 
      byte[] encodeBytes = UnicodeEncoding.ASCII.GetBytes(Signature); 
      byte[] decodeBytes; 
      using (MemoryStream decStream = new MemoryStream()) 
      { 
       base64.Decode(encodeBytes, 0, encodeBytes.Length, decStream); 
       decodeBytes = decStream.ToArray(); 
      } 

      return signer.VerifySignature(decodeBytes); 
     } 
     catch (Exception exc) 
     { 
      Console.WriteLine("Verification failed with the error: " + exc.ToString()); 
      return false; 
     } 
    } 

Вот мой тест приложение:

 Console.WriteLine("Attempting to load cert..."); 
     System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate(); 
     if (thisCert != null) 
     { 
      Console.WriteLine(thisCert.IssuerName.Name); 
      Console.WriteLine("Signing the text - Mary had a nuclear bomb"); 
      string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true)); 

      Console.WriteLine("Signature: " + signature); 

      Console.WriteLine("Verifying Signature"); 

      if (VerifySignature(thisCert.PublicKey.Key.ToXmlString(false), signature, "Mary had a nuclear bomb.")) 
       Console.WriteLine("Valid Signature!"); 
      else 
       Console.WriteLine("Signature NOT valid!"); 

     } 

Когда я пытаюсь запустить тестовое приложение, я получаю сообщение об ошибке «Ключ не действителен для использования в указанном состоянии». на линии:

string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true)); 

Я попытался заменить «PublicKey.Key» с «PrivateKey», но это не делает разницы. Я также попытался использовать BounceyCastle X509Certificate, но я не мог понять, как извлечь ключи. Есть идеи?

Спасибо!

ОБНОВЛЕНИЕ: Я выяснил, как подписать и проверить только с помощью .NET, но для меня это не очень полезно, поскольку мне нужно быть кросс-платформой, и на самом деле наше основное клиентское приложение написано на Java. Знает ли кто-нибудь Баунси-замок эквивалент следующего кода?

public string SignDataAsXml(string Message, X509Certificate2 ThisCert) 
    { 
     XmlDocument doc = new XmlDocument(); 
     doc.PreserveWhitespace = false; 
     doc.LoadXml("<core>" + Message + "</core>"); 

     // Create a SignedXml object. 
     SignedXml signedXml = new SignedXml(doc); 

     // Add the key to the SignedXml document. 
     signedXml.SigningKey = ThisCert.PrivateKey; 

     // Create a reference to be signed. 
     Reference reference = new Reference(); 
     reference.Uri = ""; 

     // Add an enveloped transformation to the reference. 
     XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); 
     reference.AddTransform(env); 

     // Add the reference to the SignedXml object. 
     signedXml.AddReference(reference); 

     // Create a new KeyInfo object. 
     KeyInfo keyInfo = new KeyInfo(); 

     // Load the certificate into a KeyInfoX509Data object 
     // and add it to the KeyInfo object. 
     keyInfo.AddClause(new KeyInfoX509Data(ThisCert)); 

     // Add the KeyInfo object to the SignedXml object. 
     signedXml.KeyInfo = keyInfo; 

     // Compute the signature. 
     signedXml.ComputeSignature(); 

     // Get the XML representation of the signature and save 
     // it to an XmlElement object. 
     XmlElement xmlDigitalSignature = signedXml.GetXml(); 

     // Append the element to the XML document. 
     doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true)); 


     if (doc.FirstChild is XmlDeclaration) 
     { 
      doc.RemoveChild(doc.FirstChild); 
     } 

     using (var stringWriter = new StringWriter()) 
     { 
      using (var xmlTextWriter = XmlWriter.Create(stringWriter)) 
      { 
       doc.WriteTo(xmlTextWriter); 
       xmlTextWriter.Flush(); 
       return stringWriter.GetStringBuilder().ToString(); 
      } 
     } 
    } 

    public bool VerifyXmlSignature(string XmlMessage, X509Certificate2 ThisCert) 
    { 
     // Create a new XML document. 
     XmlDocument xmlDocument = new XmlDocument(); 

     // Load the passed XML file into the document. 
     xmlDocument.LoadXml(XmlMessage); 

     // Create a new SignedXml object and pass it the XML document class. 
     SignedXml signedXml = new SignedXml(xmlDocument); 

     // Find the "Signature" node and create a new XmlNodeList object. 
     XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature"); 

     // Load the signature node. 
     signedXml.LoadXml((XmlElement)nodeList[0]); 

     // Check the signature and return the result. 
     return signedXml.CheckSignature(ThisCert, true); 
    } 
+0

Я думал, что ECDSA был всего лишь вариантом обычного алгоритма подписи DSA и не отражал используемые базовые ключи.У меня создалось впечатление, что подписание и проверка будут работать до тех пор, пока они используют один и тот же алгоритм подписывания. Разве это не так? – SEMDeveloper

+0

Я сделал - метод LoadCertificates() содержит часть генерации ключа. Я использую RSA356 для генерации ключей, а затем RSA356-ECDSA для подписи данных. – SEMDeveloper

+0

Извините, я случайно смешал две вещи вместе. GenerateKeys для моей обработки шифрования и не имеет ничего общего с этой проблемой. Я исправил вопрос и добавил тестовый код. – SEMDeveloper

ответ

6

Bouncy Castle не поддерживает форматы XML вообще. Если ваш прецедент строго требует этого, вам будет гораздо проще использовать кодировки Base64, с сертификатами (X.509) и частными ключами (PKCS # 8), хранящимися в формате PEM. Все это строковые форматы, поэтому их следует использовать непосредственно с JSON.

В примерах кода есть другие проблемы: подписи должны использовать закрытый ключ, подписи не должны рассматриваться как строки ASCII, возможно, ваши сообщения на самом деле являются UTF8. Я бы ожидать, что внутренний знак/проверить процедуры, чтобы, возможно, выглядеть следующим образом:

public string SignData(string msg, ECPrivateKeyParameters privKey) 
    { 
     try 
     { 
      byte[] msgBytes = Encoding.UTF8.GetBytes(msg); 

      ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); 
      signer.Init(true, privKey); 
      signer.BlockUpdate(msgBytes, 0, msgBytes.Length); 
      byte[] sigBytes = signer.GenerateSignature(); 

      return Convert.ToBase64String(sigBytes); 
     } 
     catch (Exception exc) 
     { 
      Console.WriteLine("Signing Failed: " + exc.ToString()); 
      return null; 
     } 
    } 

    public bool VerifySignature(ECPublicKeyParameters pubKey, string signature, string msg) 
    { 
     try 
     { 
      byte[] msgBytes = Encoding.UTF8.GetBytes(msg); 
      byte[] sigBytes = Convert.FromBase64String(signature); 

      ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); 
      signer.Init(false, pubKey); 
      signer.BlockUpdate(msgBytes, 0, msgBytes.Length); 
      return signer.VerifySignature(sigBytes); 
     } 
     catch (Exception exc) 
     { 
      Console.WriteLine("Verification failed with the error: " + exc.ToString()); 
      return false; 
     } 
    } 

Еще одна проблема в том, что я думаю, что .NET не получил поддержки ECDSA до .NET 3.5, в любом случае, нет класса ECDSA в .NET 1.1 (который является целью BC для предстоящего выпуска 1.8, после этого мы будем «модернизировать»), поэтому DotNetUtilities не поддерживает ECDSA. Однако мы можем экспортировать в PKCS # 12 и импортировать в BC. Пример программы:

public void Program() 
    { 
     Console.WriteLine("Attempting to load cert..."); 
     System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate(); 

     Console.WriteLine(thisCert.IssuerName.Name); 
     Console.WriteLine("Signing the text - Mary had a nuclear bomb"); 

     byte[] pkcs12Bytes = thisCert.Export(X509ContentType.Pkcs12, "dummy"); 
     Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build(); 
     pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "dummy".ToCharArray()); 

     ECPrivateKeyParameters privKey = null; 
     foreach (string alias in pkcs12.Aliases) 
     { 
      if (pkcs12.IsKeyEntry(alias)) 
      { 
       privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key; 
       break; 
      } 
     } 

     string signature = SignData("Mary had a nuclear bomb", privKey); 

     Console.WriteLine("Signature: " + signature); 

     Console.WriteLine("Verifying Signature"); 

     var bcCert = DotNetUtilities.FromX509Certificate(thisCert); 
     if (VerifySignature((ECPublicKeyParameters)bcCert.GetPublicKey(), signature, "Mary had a nuclear bomb.")) 
      Console.WriteLine("Valid Signature!"); 
     else 
      Console.WriteLine("Signature NOT valid!"); 
    } 

Я действительно не тестировал ни один из приведенных выше кодов, но он должен дать вам что-то, чтобы продолжить. Обратите внимание, что у BC есть также генераторы ключей и сертификатов, поэтому вы можете использовать BC для всего (кроме XML!) И экспортировать/импортировать в/из .NET-земли только там, где это необходимо.

+0

Большое спасибо за этот код - сейчас я тестирую вещи. Вы знаете, как я могу получить секретный ключ непосредственно из сертификата Org.BouncyCastle.X509.X509? Мой код фактически создает этот сертификат, экспортирует его в Windows, а затем обратно, и я хотел бы вообще удалить .NET из уравнения, если это возможно. – SEMDeveloper

+0

Я попытался с помощью следующего кода (с каждой производной я мог бы найти, чтобы экспортировать сертификат в массив байтов Pkcs12Store PKCS12 = новый Pkcs12StoreBuilder() Сборка();. pkcs12.Load (новый MemoryStream (CurrentCert.CertificateStructure.GetEncoded (), ложные), CertificatePassword.ToCharArray()); Но я продолжал получать ошибку: Невозможно привести объект типа 'Org.BouncyCastle.Asn1.DerSequence' к типу 'Org.BouncyCastle.Asn1.DerInteger' – SEMDeveloper

+0

Если я использую эту строку, все работает - но как это сделать в памяти? pkcs12.Load (File.OpenRead («myCert.p12»), CertificatePassword.ToCharArray()); – SEMDeveloper

Смежные вопросы