Keywords: C# | RSA encryption | key transmission
Abstract: This article provides an in-depth exploration of implementing RSA asymmetric encryption and decryption in C# using the System.Security.Cryptography.RSACryptoServiceProvider. It covers the complete workflow from key pair generation and public key serialization for transmission to data encryption and decryption with the private key. By refactoring example code, it analyzes the use of XML serialization for key exchange, byte array and string conversion mechanisms, and the selection between PKCS#1.5 and OAEP padding modes, offering technical insights for developing secure communication systems.
Fundamentals of RSA Encryption and Decryption
RSA (Rivest-Shamir-Adleman) is a widely used asymmetric encryption algorithm based on the mathematical difficulty of factoring large numbers, ensuring data security during transmission. In C#, the System.Security.Cryptography.RSACryptoServiceProvider class provides core functionality for RSA operations. Asymmetric encryption uses a key pair: a public key for encryption and a private key for decryption, allowing the public key to be shared safely while the private key remains confidential.
Generating an RSA Key Pair
First, generate an RSA key pair by instantiating RSACryptoServiceProvider with a specified key length, such as 2048 bits. For example:
var csp = new RSACryptoServiceProvider(2048);
var privateKey = csp.ExportParameters(true);
var publicKey = csp.ExportParameters(false);
The ExportParameters method exports key parameters, with true for the private key and false for the public key. Keys are stored as RSAParameters structures containing components like modulus and exponent.
Serializing and Transmitting the Public Key
To transmit the public key, convert it to a string format. XML serialization is common for preserving structure and enabling cross-platform exchange. The following code serializes the public key:
string publicKeyString;
using (var sw = new System.IO.StringWriter())
{
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
xs.Serialize(sw, publicKey);
publicKeyString = sw.ToString();
}
After serialization, the public key can be stored or transmitted. The recipient deserializes it back to RSAParameters:
using (var sr = new System.IO.StringReader(publicKeyString))
{
var xs = new System.Xml.Serialization.XmlSerializer(typeof(RSAParameters));
publicKey = (RSAParameters)xs.Deserialize(sr);
}
Private key serialization is similar but must be handled securely to prevent exposure.
Encrypting Data with the Public Key
Before encryption, convert plaintext to a byte array and apply padding. In the example, we use Unicode encoding and PKCS#1.5 padding:
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(publicKey);
var plainTextData = "example text";
var bytesPlainTextData = System.Text.Encoding.Unicode.GetBytes(plainTextData);
var bytesCipherText = csp.Encrypt(bytesPlainTextData, false);
var cipherText = Convert.ToBase64String(bytesCipherText);
The second parameter of Encrypt controls padding: false for PKCS#1.5, true for OAEP (Optimal Asymmetric Encryption Padding). OAEP offers enhanced security and is recommended when available.
Decrypting Data with the Private Key
For decryption, load the private key and process the encrypted byte array. First, restore bytes from the Base64 string:
var bytesCipherText = Convert.FromBase64String(cipherText);
var csp = new RSACryptoServiceProvider();
csp.ImportParameters(privateKey);
var bytesPlainTextData = csp.Decrypt(bytesCipherText, false);
var plainTextData = System.Text.Encoding.Unicode.GetString(bytesPlainTextData);
Ensure the padding mode matches encryption to avoid failure. This process validates asymmetric encryption integrity, as only the private key holder can decrypt the data.
Security Practices and Considerations
In real-world applications, key management is critical. Public keys can be distributed openly, but private keys must be stored securely, such as in hardware security modules (HSMs) or encrypted storage. When transmitting encrypted data, use secure protocols like TLS to prevent man-in-the-middle attacks. Additionally, regularly update key pairs and monitor encryption library updates to address potential vulnerabilities.
The code examples illustrate basic workflows, but production systems should incorporate error handling, logging, and performance optimizations. For instance, use using statements to ensure resource disposal and prevent memory leaks.