Using Asymmetric Algorithms

for RuBoard

The final portion of the core cryptographic object model to discuss is the set of classes that implement asymmetric algorithms and associated formatting/ deformatting functions. Asymmetric algorithms are represented in the .NET Framework by the AsymmetricAlgorithm class and its subclasses. Like their symmetric counterparts, individual asymmetric algorithms are represented in the object model as subclasses of the abstract AsymmetricAlgorithm class and specific implementations of an algorithm are subclasses of those subclasses. For example, the RSA algorithm is represented by the RSA class, which is a subclass of AsymmetricAlgorithm . An implementation of the RSA algorithm is provided by the RSACryptoServiceProvider class, which is a subclass of the RSA class.

The .NET Framework includes support for two asymmetric algorithms ”RSA and DSA. RSA (named after its inventors, Ron Rivest, Adi Shamir, and Len Adelman) is a public key algorithm that supports both encryption and digital signatures. DSA, the Digital Signature Algorithm specified by the U.S. government in Federal Information Processing Standard (FIPS) 186-2, supports only digital signatures. Table 30.5 shows the abstract algorithm and implementation classes included in the .NET Framework that support RSA and DSA, as well as default and allowed key sizes.

Table 30.5. Asymmetric Algorithms in the .NET Framework
Cipher Default Implementation Class Legal Key Sizes Default Key Size
RSA RSACryptoServiceProvider 384 “16384 bits (in 8 bit increments ) 1,024 bits
DSA DSACryptoServiceProvider 512 “1024 bits (in 64 bit increments) 1,024 bits

NOTE

As is the case with any algorithm in the .NET Framework implemented on top of CryptoAPI, you may need to explicitly install a "high encryption" update to your operating system to use some key lengths. Platforms that do not have the "high encryption" option installed will not be able to generate RSACryptoServiceProvider public encryption keys greater than 512 bits in length.


An instance object of a class descendant from AsymmetricAlgorithm represents a particular algorithm, choice of operating parameters, and a public/private key pair. Every AsymmetricAlgorithm object supports two key- related properties ” KeySize and LegalKeySizes . These properties are similar to those of the same name on SymmetricAlgorithm . KeySize returns the size (in bits) of the public key associated with the object. The LegalKeySizes property returns an array of KeySizes structures that declares what key sizes are valid for this algorithm.

AsymmetricAlgorithm objects are created using static Create() methods , just like other cryptographic objects in the .NET Framework. For example, to create a new instance of an RSA object, simply call RSA.Create() :

 RSA rsa = RSA.Create(); 

This will create a new instance of the default implementation of the RSA algorithm and generate a random key pair to use with RSA.

In the "Using Symmetric Algorithms" section, we were able to set the value of an encryption or decryption key on a SymetricAlgorithm object simply by assigning the key's byte[] value to the Key property on the object. The situation is a little more complicated for AsymmetricAlgorithm objects, because an asymmetric key pair typically consists of a set of related values and, furthermore, exactly what values need to be defined vary by algorithm. For these reasons, the .NET Framework has chosen to use stringified XML structures to import key values into an algorithm object and export key values from an object. (Importing a key or key pair into an AsymmetricAlgorithm is akin to setting the Key property on a SymmetricAlgorithm . Exporting is equivalent to retrieving the value of the Key property.) For public key values, the XML structures used by the .NET Framework are equivalent to KeyValue structures defined by the XML Digital Signature Standard. For private key values, the XML structures extend the KeyValue definitions by adding fields for the private key components .

The AsymmetricAlgorithm class defines two methods that deal with importing and exporting key values ” ToXmlString and FromXmlString . The ToXmlString method creates a stringified XML structure for the public key value stored in the object. ToXmlString takes one argument as input, a Boolean value that specifies whether to also include private key components in the XML string. The following code snippet shows how to create XML string representations of public keys and public/private key pairs from any AsymmetricAlgorithm . (The sample uses an RSA object, but any subclass of AsymmetricAlgorithm can be used.)

 RSA rsa = RSA.Create(); String publicKeyOnly = rsa.ToXmlString(false); String publicAndPrivateKeys = rsa.ToXmlString(true); 

The following are the XML string values for an actual random RSA public/private key pair. First is the string value stored in the publicKeyOnly variable as a result of calling rsa.ToXmlString(false) :

 <RSAKeyValue> <Modulus>yB8PydTa4ka4CRXeoGzHO7MS7DDiuIhjgKFM23i83IbvwraLae2XvRzyXFj7 rz/C7nwK9MYqwFksTtSOhV3M1J96cPMuDMIcNJp/NIeODl08972idvVZivh0e35NpsQg3 ohxXVekN4LIqMmE9bkVmtPwX36xWai7Ws/TgMtNQEU=</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> 

For the RSA algorithm, a public key consists of a modulus and exponent. A private key contains this information and more, as shown next in the value stored in the publicAndPrivateKeys variable:

 <RSAKeyValue> <Modulus>yB8PydTa4ka4CRXeoGzHO7MS7DDiuIhjgKFM23i83IbvwraLae2XvRzyXFj7 rz/C7nwK9MYqwFksTtSOhV3M1J96cPMuDMIcNJp/NIeODl08972idvVZivh0e35NpsQg3 ohxXVekN4LIqMmE9bkVmtPwX36xWai7Ws/TgMtNQEU=</Modulus> <Exponent>AQAB</Exponent> <P>5njbrh6xncLf/VF/Uyeux7zxlUNdYjHedQq0rh5kt57Y7SLxsMhLqB9MFHwRgBGTmb dj87+6kic2R54YwgdjhQ==</P> <Q>3kmX+dFUe69Llt+P1xOJ9/Sp/1oJLZIg0G3LEEQW+gMEcxkZvo5aDk81ss3QPaDmGI kDNkonNrIjzZxqecUlwQ==</Q> <DP>TqhK2WcyWVRsG8mXueqeNR8gGEAwe9XnRWzM82v+FckJ4gz+DcaeQ5fC4G7jjiDxj hHP9B2ocD8fwFuNuZLJ/Q==</DP><DQ>CHkBo/IIqFY8KVoIH4iNH7hhqmwCIYyKV6d3r /0IaysmRkTUqGDAqf726wPRRigV3SWLy8vzxq/vkWy+2jlbgQ==</DQ> <InverseQ>NQuTfOM7BHx9Uh2uPwOkvpMztbC1/2gs7Z9juJrg0B+6KA6+INe93hmQUm3 YdXpqpmAPke3nOGWqjA7ZlrNPdg==</InverseQ> <D>XIH3U25gzE6yjgidA/2kz5UE/0fN1k296V0m4SFb9Hkv5gtqQMpC5Xm3EzdTCPzpiE Kw8duVMZtHHx2k0EO/Bg1zFdStVHitOUS/5QfR3ctY+34+yAida5mOYB6kgCGpsgg7NaS 0DtyvI+8lDiSb+1nONXY0f/ZvS7T2OegXMQE=</D> </RSAKeyValue> 

To import a public key (or public/private key pair) from an XmlString generated by a prior call to ToXmlString , simply call the FromXmlString method on the object into which you want to import the key value. FromXmlString will correctly parse and handle XML representations of both public keys only as well as public/private key pairs.

 RSA newRsa = RSA.Create(); newRsa.FromXmlString(publicAndPrivateKeys); 

After importing the public and private keys into the newRsa variable, newRsa will be a clone of the rsa variable that originally generated the key.

NOTE

When using an asymmetric algorithm to encrypt data, you typically only have the public key of the intended recipient. AsymmetricAlgorithm objects are designed to accept as valid key imports either a functional public/private key pair or a lone public key value. If the object contains only a public key value, you can only use it to encrypt data or verify digital signatures (the operations that depend on the public component of the key pair). Attempting to export private key components (using ToXmlString(true) ) when the object only contains a public key value will throw an exception.


XML string representations of public and private keys work great in general, but sometimes it can be inefficient to have to convert key data into an out of XML if you simply want to move it around within your program. In particular, sometimes it is useful to be able to move key parameters around as a structured object instead of as a string. Thus, in addition to the ToXmlString and FromXmlString methods on AsymmetricAlgorithm , the .NET Framework has adopted a further design pattern in which each direct subclass of AsymmetricAlgorithm implements algorithm-specific ImportParameters and ExportParameters functions. These functions consume and return a structured object that is specific to the algorithm represented by the subclass. For example, the abstract RSA class exposes an ExportParameters method that returns an RSAParameters object:

 RSA rsa = RSA.Create(); RSAParameters publicKeyParameters = rsa.ExportParameters(false); RSAParameters publicAndPrivateKeyParameters = rsa.ExportParameters(true); 

By convention, for each abstract algorithm class such as RSA or DSA , there is a corresponding structure for holding key parameters whose name is the concatenation of the algorithm classname and Parameters . So the DSAParameters structure matches DSA and its subclasses, and any implementation subclass of DSA will be able to produce and consume DSAParameters objects. After you hold an instance of a *Parameters object, individual parameter values can be readily accessed. By convention, large integers (sometimes called "bignums") are stored as byte arrays in "big endian" format with the highest byte of the number stored in the first ( lowest index) element of the array. Consequently, if publicKeyParameters is an instance of RSAParameters , publicKeyParameters.Modulus is the public key modulus, and publicKeyParameters.Modulus[0] is the high-order byte of the entire modulus value.

NOTE

The .NET Framework does not provide a generic service for persisting private keys securely; that task is left up to the algorithm implementation classes or the application using cryptography. Later chapters discuss how you can access CryptoAPI's various persisted key stores when using the RSACryptoServiceProvider and DSACryptoServiceProvider classes.


Now that you have an instance of your favorite asymmetric algorithm object configured with a key pair, you probably want to sign or encrypt some other data with it. Public key encryption and digital signature computations are easy to do, but the particular methods you will need to use vary by algorithm. We first discuss RSA -related methods and then move on to those for DSA . If you have other asymmetric algorithms available on your system, you will need to check the documentation that came with them to see what methods are supported.

The abstract RSA class defines two abstract methods for encryption and decryption ” EncryptValue(byte[] data) and DecryptValue(byte[] data) . These classes do exactly what their respective names imply ”they perform raw RSA computations on the large integer value represented by the data byte array. EncryptValue and DecryptValue perform no padding on the data. Implementations of the RSA algorithm implement EncryptValue and DecryptValue so that various encryption and signature formatters and deformatters can access the underlying raw RSA operations.

CAUTION

It is very unlikely that you will actually want or need to use the EncryptValue and DecryptValue methods in your programs. In general, you need to combine the RSA algorithm with a good padding function to pad the to-be-encrypted data out to the size of the modulus. Weak or nonexistent padding can seriously reduce the security of your program's use of RSA. The various Asymmetric*Formatter and Asymmetric*Deformatter classes, described later on in this chapter, make available combinations of RSA and padding algorithms. Finally, note that if you are using the RSACryptoServiceProvider class, which is the default implementation of RSA in the .NET Framework, you cannot call EncryptValue and DecryptValue because these methods are not implemented by RSACryptoServiceProvider . The CryptoAPI implementation of RSA does not expose the raw RSA operations at the Win32 layer; the only exposed signature and encryption functions always add some type of padding.


The RSA implementation class RSACryptoServiceProvider exposes a number of methods for performing padded encryption, decryption, signature generation, and signature validation. Encryption and decryption services are provided by the Encrypt and Decrypt methods. These methods take as input two arguments ”the data to be encrypted or decrypted and a Boolean flag that indicates which padding algorithm should be used. The to-be-encrypted/decrypted data is passed as a byte[] array. If the Boolean flag is true , then OAEP padding will be used (a newer standard); PKCS#1 v1.5 padding will be used if the flag is false .

NOTE

At the time this book went to press, only Windows XP includes support for OAEP padding. Attempting to use OAEP padding on an earlier version of Windows will generate a CryptographicException .


Listing 30.13 contains sample code that shows how to encrypt the integer value 0x1f3f5f using RSA:

Listing 30.13 Encrypting a Data Value with an RSA Public Key and PKCS#1 v1.5 Padding
 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); byte[] data = new byte[3]{  0x1f, 0x3f, 0x5f } ; byte[] enc = rsa.Encrypt(data, false); 

This code sample used PKCS#1 v1.5 style padding (the fOAEP flag is false ). The size of the encrypted value enc will, in general, be the same as the size of the public key modulus, which defaults to 1024 bits on "high encryption" platforms. Decrypting enc is similarly straightforward:

 byte[] dec = rsa.Decrypt(enc, false); 

You must use the same padding algorithm for both encryption and decryption; padding algorithms are not interchangeable.

CAUTION

If you are debugging an application that uses RSA encryption, beware that both the PKCS#1 v1.5 and OAEP padding algorithms for RSA generate and use random numbers as part of the padding. This means that if you encrypt the same value twice, using the same RSA public key, you will get two different encrypted outputs. The randomness that is explicitly used in the RSA padding makes debugging more difficult, especially if you are not aware of it!


RSACryptoServiceProvider includes a number of methods and overloads for computing and verifying digital signatures. The core operations, signing a hash value and verifying a signed hash value, are provided by the SignHash and VerifyHash methods. SignHash requires two arguments ”the hash value to be signed (as a byte array) and a String value containing the ASN.1 Object Identifier (OID) for the hash algorithm used to generate the hash value. SignHash needs the OID value because it is a required input to the PKCS#1 signature padding function (which is always used). There are only two OID values you need to know, the values for the SHA-1 and MD5 hash algorithms, and Table 30.6 lists these values.

Table 30.6. Hash Algorithm ID Values
Algorithm OID Value as a String
SHA-1 "1.3.14.3.2.26"
MD5 "1.2.840.113549.2.5"

Listing 30.14 shows a code fragment that creates an RSA object (with random public key), a SHA1 hash object, and then computes the RSA signature over the SHA1 hash of our three-byte sample data:

Listing 30.14 Signing a Hash Value with an RSA Private Key
 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); SHA1 sha1 = SHA1.Create(); byte[] data = new byte[3]{  0x1f, 0x3f, 0x5f } ; byte[] signature = rsa.SignHash(sha1.ComputeHash(data), "1.3.14.3.2.26"); 

Verifying the sample signature is similarly easy; simply call VerifyHash with the hash of the data to compare to the signature, the OID of the hash function, and the signature. The Boolean return value will be true if the signed hash value corresponds to the input hash value:

 bool signatureIsGood = rsa.VerifyHash(sha1.ComputeHash(data), "1.3.14.3.2.26", signature) 

The RSACryptoServiceProvider class also includes methods that will compute a hash of some input data and sign that hash value in a single operation. The three SignData methods accept as input respectively a Stream , a buffer, or a portion of a buffer (indicated by a starting offset and count); this input is the to-be- hashed data. SignData also requires an Object argument that specifies the type of hash value to generate. SignData is more forgiving than SignValue in that you need not pass in the OID string to identify a hash algorithm. You can call SignData with any of the following types of Objects :

  • A String ”If you pass a String to SignData to identify the hash function, SignData will use the cryptography configuration system to map that String value to an OID. Built-in definitions exist for the friendly names MD5 , SHA1 , SHA256 , SHA384 , and SHA512 .

  • A HashAlgorithm ”You can pass SignData a subclass of HashAlgorithm , and it will attempt to automatically figure out the proper OID string to use based on the specific type of HashAlgorithm passed in.

  • A Type ”You can also pass SignData a System.Type object corresponding to a subclass of HashAlgorithm . Again, SignData will attempt to automatically figure out the proper OID that corresponds to this Type .

The same signature that was computed in Listing 30.14 using an explicit hash computation and a call to the SignHash method can equivalently be computed by the following call to SignData :

 byte[] signature = rsa.SignData(data, "SHA1"); 

There is a corresponding VerifyData method that hashes the contents of an input buffer and compares the computed hash with a signed hash value. However, there are no corresponding VerifyData methods for Stream or partial buffer inputs; you will need to hash these types of inputs yourself and call VerifyHash to check signatures.

Using the DSA signature algorithm is very similar to performing signing operations with the RSA-related classes. Because DSA does not support encryption or decryption functions, the DSA interfaces do not expose EncryptValue , DecryptValue , Encrypt , or Decrypt methods. Rather, the abstract DSA class defines two methods common to all DSA implementations for creating and verifying DSA digital signatures ” CreateSignature and VerifySignature . They are essentially equivalent to the RSA SignHash and VerifyHash functions, except that they do not require an OID string as input because by definition, DSA always uses the SHA-1 hash functions to compute to-be-signed hash values. The following is a code snippet for computing a DSA signature over the same sample three-byte data value used in Listing 30.13 and the other RSA samples:

 SHA1 sha1 = SHA1.Create(); DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(); byte[] data = new byte[3]{  0x1f, 0x3f, 0x5f } ; byte[] signature = dsa.CreateSignature(sha1.ComputeHash(data)); 

The signature can be verified by calling the VerifySignature method:

 bool sigVerifies = dsa.VerifySignature(sha1.ComputeHash(data),signature); 

CAUTION

If you are debugging an application that uses DSA signatures, beware that part of the DSA algorithm generates and uses a random number. This means that if you compute two DSA signatures over the same data/hash values with the same DSA keys, you will get two different signature values.


In addition to the CreateSignature and VerifySignature methods defined on the abstract DSA class, the DSACryptoServiceProvider class defines the same SignHash , VerifyHash , SignData , and VerifyData methods that RSACryptoServiceProvider does. The only difference between the DSACryptoServiceProvider and RSACryptoServiceProvider versions is that the DSACryptoServiceProvider methods never require information about the type of hash algorithm to use, because the hash algorithm is always by definition SHA-1.

Although the RSACryptoServiceProvider and DSACryptoServiceProvider classes automatically pad data to be signed or encrypted using an appropriate padding algorithm, other RSA and DSA implementations may not provide those functions. The .NET Framework assumes only that an RSA or DSA implementation class will implement the raw encryption and signature functions ” RSA.EncryptValue , RSA.DecryptValue , DSA.CreateSignature , and DSA.VerifySignature ”and provides separate "helper" functions to perform padding, formatting, deformatting, and depadding. The padding- and formatting-related classes that are included in the .NET Framework are as follows :

  • AsymmetricKeyExchangeFormatter ” An abstract class representing padding-and-formatting algorithms associated with encrypting a data value with a public encryption key

    • RSAPKCS1KeyExchangeFormatter ” Subclass of AsymmetricKeyExchangeFormatter that implements PKCS#1 v1.5 padding for RSA encryption

    • RSAOAEPKeyExchangeFormatter ” Subclass of AsymmetricKeyExchangeFormatter that implements OAEP formatting for RSA encryption

  • AsymmetricKeyExchangeDeformatter ” An abstract class representing deformatting and depadding algorithms associated with decrypting a public key encrypted data value

    • RSAPKCS1KeyExchangeDeformatter ” Subclass of AsymmetricKeyExchangeDeformatter that implements PKCS#1 v1.5 depadding for RSA decryption

    • RSAOAEPKeyExchangeDeformatter ” Subclass of AsymmetricKeyExchangeDeformatter that implements OAEP depadding for RSA decryption

  • AsymmetricSignatureFormatter ” An abstract class representing a signature algorithm's padding and formatting function

    • RSAPKCS1SignatureFormatter ” Subclass of AsymmetricSignatureFormatter that implements PKCS#1 v1.5 padding for RSA signatures

    • DSASignatureFormatter ” Subclass of AsymmetricSignatureFormatter that implements DSA signature padding (defined in [DSS])

  • AsymmetricSignatureDeformatter ” An abstract class representing a signature verification algorithm's deformatting and depadding function

    • RSAPKCS1SignatureDeformatter ” Subclass of AsymmetricSignatureDeformatter that implements PKCS#1 v1.5 depadding for RSA signatures

    • DSASignatureDeformatter ” Subclass of AsymmetricSignatureDeformatter that implements DSA signature depadding (as defined in [DSS])

Using these various formatters is straightforward. First, create an instance of the formatter you want to use. All of the implemented formatter classes included in the .NET Framework support a constructor that takes an AsymmetricAlgorithm object as its lone argument. The AsymmetricAlgorithm instance you pass to the constructor should hold the public key or key pair you want to use. (You can also create an instance of the formatter object using its null-argument constructor and associate an AsymmetricAlgorithm object with the new formatter instance using its SetKey method.) If you are using an RSAPKCS1SignatureFormatter or an RSAPKCS1SignatureDeformatter , you will also need to set or change the associated hash algorithm using the SetHashAlgorithm methods. ( SetHashAlgorithm takes as input the a String name for a hash algorithm, such as MD5 or SHA1 .) Finally, each formatter object has one method to actually " turn the crank" and perform the operation (for example, RSAPKCS1SignatureFormatter.CreateSignature ). Call that method with the value to be signed or encrypted and the return value will be the desired signature or encrypted value.

To illustrate the use of formatter objects, we will now show how to perform the encryption operation in Listing 30.13 using a formatter object instead of a direct call to RSA.Encrypt . In Listing 30.13, we encrypted the hexadecimal values 0x1f3f5f using an RSA key and PKCS#1 v1.5 padding. Listing 30.15 shows how to perform the same computation using an RSAPKCS1KeyExchangeFormatter and any RSA key (not necessarily an RSACryptoServiceProvider ).

Listing 30.15 Encrypting a Data Value Using an RSAPKCS1KeyExchangeFormatter
 RSA rsa = RSA.Create(); byte[] data = new byte[3]{  0x1f, 0x3f, 0x5f } ; RSAPKCS1KeyExchangeFormatter keyExchangeFormatter = new RSAPKCS1KeyExchangeFormatter(rsa); byte[] enc = keyExchangeFormatter.CreateKeyExchange(data); 

To decrypt the encrypted value enc , use a corresponding RSAPKCS1KeyExchangeDeformatter object and its DecryptKeyExchange method. Continuing the code in Listing 30.15, the decrypted value is computed as follows:

 RSAPKCS1KeyExchangeDeformatter keyExchangeDeformatter = new graphics/ccc.gif RSAPKCS1KeyExchangeDeformatter(rsa); byte[] dec = keyExchangeDeformatter.DecryptKeyExchange(enc); 

AsymmetricSignatureFormatter and AsymmetricSignatureDeformatter objects work similarly, using the methods CreateSignature and VerifySignature as they are named. When using the RSAPKCS1SignatureFormatter and matching Deformatter , you must explicitly set the hash algorithm so that the formatter will know what OID to insert into the padding and signature computation.

NOTE

When combined with an RSACryptoServiceProvider or DSACryptoServiceProvider object, the formatters simply use CryptoAPI's implementation of the padding algorithms to generate the padding. (This is necessitated by the fact that CryptoAPI does not expose the raw encryption and digital signature methods but only combination methods that also perform padding.) This means, for example, that you can only use the RSAOAEPSignatureFormatter with an RSACryptoServiceProvider on Windows XP, because earlier versions of Windows do not expose OAEP functionality.


for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net