Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#
Article

Cryptography in .NET

Rate me:
Please Sign up or sign in to vote.
4.58/5 (20 votes)
9 Oct 20021 min read 529.9K   2.2K   65   47
Cryptography in .NET using the Windows PKI

Overview

Windows provides a Public Key Infrastructure (PKI) that allows us to store certificates for encryption purposes. To access the this store we had two possibilities in the unmanaged past: The CryptoAPI and the CAPICOM.

The Framework Class Library (= FCL) provies a lot of function for encryption, but the current release doesn't provide any classes for accessing the the certificate store. Fortunately a the Web Services Development Kit (WSDK) Technology Preview adds this functionality; later this will be included in the FCL itself. But in the mean time you have to download the WSDK.

Enumerating The Certificate Store

The article Using WS-Security with the Web Services Development Kit Technology Preview has this nice example:
C#
using Microsoft.WSDK.Security.Cryptography;
using Microsoft.WSDK.Security.Cryptography.X509Certificates;
  ...
private X509CertificateStore store;
  ...
private void Form1_Load( object sender, System.EventArgs e )
{
    store = X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
    store.OpenRead();
    foreach( X509Certificate cert in store.Certificates )
    {
        listBox1.Items.Add( cert.GetName() );
    }
}

Asymmetric Encryption/Signature Example

The following code is the funcionallity of my submitted example. It assumes that you have at least two certificates (with private key!) in your Personal Certificate Store.

C#
try
{

   // GENERAL CODE TO READ THE CERTIFICATES FROM THE WINDOWS PKI INFRASTRUCTURE
   //
   // BEGINNER-TIP: Start MMC (=Microsoft Management Console) and select "Add-in/remove Snapin"
   // from the "Console" menu. Now press "Add.." button. Select "Certificates" in the list and
   // press "Add" button. You have the choise to select "My user account" or "Computer account".
   // Then press "Finish" and "Close" and start exploring the installed certificates... 
   // Each store has a "Personal" section with is BTW represented by the letters "MY".
   // Also interessting is the "Trusted root" certificates, there you see all the Certificate
   // Issuer that you trust, there is quite a lot and sometimes it's a good idea to delete
   // all of them and only add the one you need or really trust, for security resasons.
   //

   // Open private certificate store of current user

   X509CertificateStore store = 
               X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
   store.OpenRead();

   // Read e.g. the first two certificate
   X509Certificate sender = (X509Certificate)store.Certificates[0];
   X509Certificate receiver = (X509Certificate)store.Certificates[1];

   // Let's see who we are dealing with... - ps: not nessesary for the following code
   string sender_serial = sender.GetName();
   string receiver_serial = receiver.GetName();


   //
   // SENDER-SIDE CODE
   //

   // SENDER-SIDE: Extract own private keys and receiver's public key

   RSAParameters sender_private = sender.Key.ExportParameters( true );
   RSAParameters receiver_public = receiver.Key.ExportParameters( false );

   // SENDER-SIDE: Asymmetric encryption with receivers's public key
   RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
   rsa.ImportParameters( receiver_public ); 
   byte[] cleartext = ASCIIEncoding.ASCII.GetBytes("test");
   byte[] cipher = rsa.Encrypt( cleartext, false );
   
   // SENDER-SIDE: Sign the cipher with own private key
   rsa = new RSACryptoServiceProvider();
   rsa.ImportParameters( sender_private );
   byte[] signature = rsa.SignData( cipher, new SHA1CryptoServiceProvider() );

   //
   // TODO: TRANSFER DATA OVER UNSECURE CHANNEL...
   //

   // RECEIVER-SIDE: Get own private key and sender's public key

   RSAParameters receiver_private = receiver.Key.ExportParameters( true );
   RSAParameters sender_public = sender.Key.ExportParameters( false );


   // RECEIVER-SIDE: Verify signature with sender's public key
   //
   // Note: You are ONLY verifying the signature and NOT verifying the Certificate!
   // It's corresponding to the CAPICOM call SignedData.Verify( CAPICOM_VERIFY_SIGNATURE_ONLY )
   // I did not yet find out how we can use the .NET library to verify the Certificate
   // against the issuer-chain. If someone knows how to do this, and not using interop
   // and the SignedData.Verify( CAPICOM_VERIFY_SIGNATURE_AND_CERTIFICATE ), I would be
   // very, very, very happy - because this is a requirement in the software I'm developing
   // currently and if we can't do that I have to do CAPI-interop :-(
   // At the moment I think that there is no simple function call for this and this s***s.
   // Maybe it's possible to walk throw the chain-of-issuers and validate the fingerprint
   // this the public key of the issuer. But I don't know enough about that....
   // ANY HELP TO THIS POINT IS MORE THAN WELCOME
   //

   rsa = new RSACryptoServiceProvider();
   rsa.ImportParameters( sender_public );
   if( rsa.VerifyData( cipher, new SHA1CryptoServiceProvider(), signature ) )
   {
      // RECEIVER-SIDE: Asmymetirc decryption with own private key
      rsa.ImportParameters( receiver_private );
      byte[] cleartext_after_decription = rsa.Decrypt( cipher, false );

      // Check result
      Debug.Assert( ASCIIEncoding.ASCII.GetString( cleartext ) ==
         ASCIIEncoding.ASCII.GetString( cleartext_after_decription ),
         "Ups, the cleartext input is not equal the cleartext output..." );
   }
   else
      Debug.Assert( false, "Ups, check signature failed!" );
}
catch( Exception e )
{

   // NOTE: the following exception, that may occure during 'ExportParameters( true )'
   //
   //   System.Security.Cryptography.CryptographicException
   //   "Key information could not be exported from the cryptographic service 
   //    provider (CSP) for this implementation." }"
   // 
   // Can have one of the following reasons:
   // + The certificate was NOT imported with the flag "Mark the private key as exportable"
   // + The type "SSL Server Authentication(40)" and is in a CurrentUser store and not in
   //   the LocalComputer store. See certificate details in the MMC under "NetscapeCertType".
   //   IMHO: This reason is very wicked and I don't understand it!!


   Debug.Assert( false, e.ToString() );
}

Note: The X509Certificate class provides the function ExportParameters and the boolean parameter defines if the private key has to be submitted as well.

A quick note

Favorite person names for encryption examples are Alice, Bob and mean Steve. If you like to see how they are doing in .NET, search your MSDN examples for the excellent example file called PublicKey.cs!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwhat is the difference between signing and encryption in rsa? Pin
adam syria28-Oct-09 2:45
adam syria28-Oct-09 2:45 
GeneralIt is giving "Key not valid in use for specified state" Error Pin
dhanashekar28-Oct-05 1:07
dhanashekar28-Oct-05 1:07 
Generalusing wse 2.0 or 1.0 Pin
FPARRA812-Sep-05 13:17
FPARRA812-Sep-05 13:17 
GeneralCode cannot work in WSE 2.0 Pin
Chris L. Mullins4-Jan-05 12:59
Chris L. Mullins4-Jan-05 12:59 
GeneralRe: Code cannot work in WSE 2.0 Pin
dcoolidge2-Aug-05 9:37
dcoolidge2-Aug-05 9:37 
QuestionCryptography into Windows Service ? Pin
Pattt9-Dec-04 7:40
Pattt9-Dec-04 7:40 
AnswerRe: Cryptography into Windows Service ? Pin
Jeroen Landheer18-Jun-05 15:28
Jeroen Landheer18-Jun-05 15:28 
AnswerRe: Cryptography into Windows Service ? Pin
Anouphab8-Dec-06 1:49
Anouphab8-Dec-06 1:49 
Questionhow to remove a private key using CAPICOM Pin
chenkaijiang@yahoo.com7-Dec-04 2:42
chenkaijiang@yahoo.com7-Dec-04 2:42 
GeneralUsing more secure hash algorithm Pin
George Ceaser14-Oct-04 4:16
George Ceaser14-Oct-04 4:16 
GeneralProblem signing a serilized object using CAPICOM.SignedData Pin
Escroto5-Aug-04 23:54
Escroto5-Aug-04 23:54 
GeneralRe: Problem signing a serilized object using CAPICOM.SignedData Pin
Manuel Permuy6-Aug-04 13:10
Manuel Permuy6-Aug-04 13:10 
GeneralRe: Problem signing a serilized object using CAPICOM.SignedData Pin
Escroto6-Aug-04 21:28
Escroto6-Aug-04 21:28 
GeneralRe: Problem signing a serilized object using CAPICOM.SignedData Pin
Manuel Permuy9-Aug-04 3:26
Manuel Permuy9-Aug-04 3:26 
Generalx509certficatestore Pin
Member 96985422-Apr-04 2:33
Member 96985422-Apr-04 2:33 
GeneralCAPICOM+CDO Pin
rayback_29-Dec-03 7:13
rayback_29-Dec-03 7:13 
GeneralRe: CAPICOM+CDO Pin
rayback_223-Dec-03 23:17
rayback_223-Dec-03 23:17 
GeneralRe: CAPICOM+CDO Pin
goayeh21-Apr-05 16:19
sussgoayeh21-Apr-05 16:19 
GeneralRe: CAPCIOM+CDO SendSignedMail Pin
rayback_221-Apr-05 21:19
rayback_221-Apr-05 21:19 
Hi friend .I faced the same problem ,I remember struggling with line endings for a long time (it seemed that I put one extra line ending Smile | :) ).
I send here the modified code of what I wrote before and its code (at least work here ) hope it helps.
Sincerely Ray

Const cdoSendUsingPickup = 1
Const cdoSendUsingPort = 2

Const cdoBasic = 1

const CAPICOM_STORE_OPEN_READ_ONLY = 0
const CAPICOM_CURRENT_USER_STORE = 2
const CAPICOM_CERTIFICATE_FIND_SHA1_HASH = 0
const CAPICOM_CERTIFICATE_FIND_EXTENDED_PROPERTY = 6
const CAPICOM_CERTIFICATE_FIND_TIME_VALID = 9
const CAPICOM_CERTIFICATE_FIND_KEY_USAGE = 12
const CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME = 0
const CAPICOM_INFO_SUBJECT_SIMPLE_NAME = 0
const CAPICOM_CERT_INFO_SUBJECT_EMAIL_NAME = 2
const CAPICOM_ENCODE_BASE64 = 0
const CAPICOM_ENCODE_BINARY = 1
const CAPICOM_E_CANCELLED = -2138568446
const CERT_KEY_SPEC_PROP_ID = 6

Const vbFromUnicode = 128
Const vbUnicode = 64

Const cdoIMessage = "IMessage"
'*****************************************************************************************
'*********** Send Signed email Message **************************
'***********Attachments if any are given as comma separated paths (physical paths of the file)**
'*****************************************************************************************
Public Function SendSignedMail(ByVal fromstr, ByVal tostr , ByVal subjectstr , ByVal bodystr , ByVal Attchmnt)

Set oSignedMsg = CreateObject("CDO.Message")
Set oSignedData = CreateObject("CAPICOM.SignedData")
Set oUtilities = CreateObject("CAPICOM.Utilities")
Set oAttribute = CreateObject("CAPICOM.Attribute")
set oStore = CreateObject("CAPICOM.Store")
set oSigner = CreateObject("CAPICOM.Signer")
set oReciepents = CreateObject("CAPICOM.Certificates")
Dim szNames


' select the signer certificate
oStore.Open CAPICOM_CURRENT_USER_STORE, "My", CAPICOM_STORE_OPEN_READ_ONLY
Set cSignerCertificates = oStore.Certificates

Select Case cSignerCertificates.Count
Case 0
MsgBox "No certificate found"
Exit Function
Case 1
oSigner.Certificate = cSignerCertificates(1)
Case Else
Set cSignerCertificates = cSignerCertificates.Select("Certificates", "Select certificate for signing")
If (cSignerCertificates.Count = 0) Then
msgbox "Error : Certificate selection canceled"
Exit Function
End If
oSigner.Certificate = cSignerCertificates(1)
End Select

'assign certificate chosen by user to object for signing
SelectCertificate = oSigner.Certificate.GetInfo(CAPICOM_CERT_INFO_SUBJECT_EMAIL_NAME)

'set the signing time in UTC time
oAttribute.Name = CAPICOM_AUTHENTICATED_ATTRIBUTE_SIGNING_TIME
oAttribute.Value = oUtilities.LocalTimeToUTCTime(Now)



oSigner.AuthenticatedAttributes.Add oAttribute

'REGION ATTACHMENTS

'if there are no attachments

If Len(Trim(Attchmnt)) = 0 Then
Set oBodyPart = oSignedMsg.BodyPart.AddBodyPart
Set cFields = oBodyPart.Fields
cFields.Item("urn:schemas:mailheader:content-type") = "text/html"
cFields.Update

Set oStream = oBodyPart.GetDecodedContentStream
oStream.WriteText bodystr
oStream.Flush
End If

'if there are attachments

If Len(Trim(Attchmnt)) <> 0 Then

'add text part
Set oBodyPart = oSignedMsg.BodyPart.AddBodyPart
Set cFields = oBodyPart.Fields
cFields.Item("urn:schemas:mailheader:content-type").Value = "multipart/mixed;boundary=MIXEDBOUNDARY"
cFields.Update

Set oBodyPart = oSignedMsg.BodyPart.BodyParts(1).AddBodyPart
Set cFields = oBodyPart.Fields
cFields.Item("urn:schemas:mailheader:content-type") = "text/html"
cFields.Update

Set oStream = oBodyPart.GetDecodedContentStream
oStream.WriteText bodystr
oStream.Flush

'add attachments , the paths to attached files are separated by comma
Dim ekler
Dim ekcikler
Dim i
ekler = Split(Attchmnt, ",")

'replace \ character with /
For i = 0 To UBound(ekler)
ekcikler = Split(ekler(i), "\")
ekler(i) = Join(ekcikler, "/")
Next

For i = 0 To UBound(ekler)
Set oBodyPart = oSignedMsg.BodyPart.BodyParts(1).AddBodyPart
Set cFields = oBodyPart.Fields
cFields.Item("urn:schemas:mailheader:content-type").Value = "application/octet-stream" & vbCrLf & "Name = """ & ekler(i) & """"
cFields.Item("urn:schemas:mailheader:content-transfer-encoding").Value = "base64"
cFields.Item("urn:schemas:mailheader:content-disposition").Value = "attachment;" & vbCrLf & "FileName=""" & ekler(i) & """"
cFields.Update

Set oStream = oBodyPart.GetDecodedContentStream
oStream.LoadFromFile ekler(i)
oStream.Flush
Next

End If


'ENDREGION ATTACHMENT

ss = oSignedMsg.BodyPart.BodyParts(1).GetStream.ReadText
ss = Replace(ss, "This is a multi-part message in MIME format." + vbCrLf, "")



oSignedData.Content = StrConv(ss, 128)


' sign the content
dim szSignature
szSignature = oSignedData.Sign(oSigner, True, CAPICOM_ENCODE_BINARY)
byteSignature = oUtilities.BinaryStringToByteArray(szSignature)

' Attach the signature and let CDO base64 encode it
Set oBodyPart = oSignedMsg.BodyPart.AddBodyPart
Set cFields = oBodyPart.Fields
oBodyPart.Fields.Item("urn:schemas:mailheader:content-type").Value = "application/x-pkcs7-signature" & vbCrLf & "Name = ""smime.p7s"""
oBodyPart.Fields.Item("urn:schemas:mailheader:content-transfer-encoding").Value = "base64"
oBodyPart.Fields.Item("urn:schemas:mailheader:content-disposition").Value = "attachment;" & vbCrLf & "FileName=""smime.p7s"""
cFields.Update

Set oStream = oBodyPart.GetDecodedContentStream
oStream.Type = 1
oStream.Write(byteSignature)
oStream.Flush

' Set the messages content type, this needs to be done last to ensure it is not changed when we add the BodyParts
oSignedMsg.Fields.Item("urn:schemas:mailheader:content-type").Value = "multipart/signed;" & vbCrLf & "protocol=""application/x-pkcs7-signature"";" & vbCrLf & "micalg=SHA1;boundary=SIGNEDBOUNDARY"
oSignedMsg.Fields.Update

' Signing Was sucessfull
SignMessage = True

' set the from field based off of the selected certificate
oSignedMsg.From = oSigner.Certificate.GetInfo(CAPICOM_CERT_INFO_SUBJECT_EMAIL_NAME)
oSignedMsg.To = tostr
oSignedMsg.Subject = subjectstr
oSignedMsg.Fields.Update

'set sending parameters
oSignedMsg.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing").Value = cdoSendUsingPort
oSignedMsg.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver").Value = "MAILSERVERADRESS"
oSignedMsg.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport").Value = 25
oSignedMsg.Configuration.Fields.Update

'send message
oSignedMsg.Send

msgbox "Message succesfully sent"

End Function


'*****************************************************************************************
'*********** UNICODE to ASCII and BACK **************
'****************************************************************************
Public function StrConv(ByRef stringData, ByRef conversion)
Dim Stream
Set Stream = CreateObject("ADODB.Stream")

Const UnicodeCharaset = "Windows-1252"
Const BinaryCharset = "X-ANSI"
Select Case conversion
Case vbFromUnicode
' UNICODDAN ASCII'ye CEVIRME
With Stream
.Charset = UnicodeCharaset
.Type = 2
.Open
.WriteText stringData
.Position = 0
.Charset = BinaryCharset
.Type = 1
StrConv = MidB(.Read, 1)
End With

Case vbUnicode
' ASCII DEN UNICODA CEVIRME
Dim Length
Dim Buffer

if TypeName(stringData) = "Null" Then
CStrU = ""
Exit function
End if

stringData = MidB(stringData, 1)

Length = LenB(stringData)
Dim Rs
Set Rs = CreateObject("ADODB.Recordset")
Call Rs.Fields.Append("BinaryData", adLongVarBinary, Length)
Rs.Open
Rs.AddNew
Rs.Fields("BinaryData").AppendChunk(stringData & ChrB(0))
Rs.Update
Buffer = Rs.Fields("BinaryData").GetChunk(Length)
Rs.Close
Set Rs = Nothing
With Stream
.Charset = BinaryCharset
.Type = 1
.Open
Call .Write(Buffer)
.Position = 0
.Type = 2
.Charset = UnicodeCharaset
End With

StrConv = Stream.ReadText(-1)
End Select
Stream.Close
Set Stream = Nothing
End function
GeneralRe: CAPCIOM+CDO SendSignedMail Pin
NoradYeh26-Apr-05 22:12
NoradYeh26-Apr-05 22:12 
GeneralRe: CAPCIOM+CDO SendSignedMail Pin
rayback_226-Apr-05 22:26
rayback_226-Apr-05 22:26 
GeneralRe: CAPCIOM+CDO SendSignedMail Pin
abygm2-Nov-07 2:15
abygm2-Nov-07 2:15 
Generaluse phone number Pin
smailbel19-Nov-03 1:51
smailbel19-Nov-03 1:51 
QuestionWhich page is main source? Pin
Stefan Pedersen3-Oct-03 11:07
Stefan Pedersen3-Oct-03 11:07 
AnswerRe: Which page is main source? Pin
Manuel Permuy5-Oct-03 7:03
Manuel Permuy5-Oct-03 7:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.