This post shows how digital signatures can be implemented to check the integrity of cipher texts in ASP.NET Core Razor Pages. The cipher texts are encrypted with AES symmetric encryption and the key and the IV required to decrypt the texts are encrypted with asymmetric encryption using RSA certificates with a key size of 3072.
It is possible that the encrypted text was changed and the decryption steps will only be processed, if the digital signature of the sender is verified. With verification, the sender of the encrypted text can be proved and the text.
Code: https://github.com/damienbod/SendingEncryptedData
Posts in this Series
- Symmetric and Asymmetric Encryption in .NET Core
- Encrypting texts for an Identity in ASP.NET Core Razor Pages using AES and RSA
- Using Digital Signatures to check integrity of cipher texts in ASP.NET Core Razor Pages
Signing the Encrypted Text
Digital signatures can be implemented in .NET Core using a RSA instance. Any text can be signed. The RSA private key is required to sign the byte array created from the text and then to create the signature. SHA256 is used in this example.
// Sign with RSA using private key public string Sign(string text, RSA rsa) { byte[] data = Encoding.UTF8.GetBytes(text); byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return Convert.ToBase64String(signature); }
In the ASP.NET Core application with the identities (created in the previous blog), each user has its own PEM private key and a public key. The public key can be shared. It is important that the private key remains safe. The signature is created from the X509Certificate2 with the private key.
var certLoggedInUser = GetCertificateWithPrivateKeyForIdentity(); var signature = _digitalSignatures.Sign(encryptedText, Utils.CreateRsaPrivateKey(certLoggedInUser));
The GetCertificateWithPrivateKeyForIdentity method gets the 2 PEM files from the database and creates the X509Certificate2 certificate. The PEM private key is saved to the database using an application secret and cn only be used if the secret is known.
private X509Certificate2 GetCertificateWithPrivateKeyForIdentity() { var user = _applicationDbContext.Users.First(user => user.Email == User.Identity.Name); var cert = _importExportCertificate.PemImportCertificate(user.PemPrivateKey, _configuration["PemPasswordExportImport"]); return cert; }
Verifying the Encrypted Text
The signature can be verified using the public key from the private key which created the signature. If the byte array can be verified we know the text is good and the private key which created it.
// Verify with RSA using public key public bool Verify(string text, string signatureBase64, RSA rsa) { byte[] data = Encoding.UTF8.GetBytes(text); byte[] signature = Convert.FromBase64String(signatureBase64); bool isValid = rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); return isValid; }
The Verify method can be called using the X509Certificate2 containing the public key.
var senderCert = GetCertificateWithPublicKeyForIdentity(sender); var verified = _digitalSignatures.Verify(encryptedDto.EncryptedText, encryptedDto.DigitalSignature, Utils.CreateRsaPublicKey(senderCert));
The GetCertificateWithPublicKeyForIdentity gets the PEM public key from the database and returns a X509Certificate2 for this.
private X509Certificate2 GetCertificateWithPublicKeyForIdentity(string email) { var user = _applicationDbContext.Users.First( user => user.Email == email); var cert = _importExportCertificate.PemImportCertificate( user.PemPublicKey); return cert; }
Using digital signatures together with asymmetric encyption and symmetric encryption
Now we can use the symmetric encryption together with the asymmetric encryption and the digital signature. By using all three together the four goals of cryptography can be achieved; Confidentiality, Data integrity, Authentication, Non-repudiation. First the text is encrypted using AES with the target identities public key. The AES IV, the AES key, and the sender email are encrypted using asymmetric encryption with RSA. The AES encrypted text (cipher text) is signed using the senders private key using RSA.
Each identity has an RSA certificate with a key size of 3072 saved in the database as two strings in the PEM format. The private key is used for digital signatures, or to decrypt messages sent to that person and the public key is used to encrypt messages to send to someone else, or to verify the identity of a message received.
public class EncryptTextModel : PageModel { private readonly SymmetricEncryptDecrypt _symmetricEncryptDecrypt; private readonly AsymmetricEncryptDecrypt _asymmetricEncryptDecrypt; private readonly ApplicationDbContext _applicationDbContext; private readonly ImportExportCertificate _importExportCertificate; private readonly DigitalSignatures _digitalSignatures; private readonly IConfiguration _configuration; [BindProperty] [Required] public string TargetUserEmail { get; set; } [BindProperty] [Required] public string Message { get; set; } [BindProperty] public string EncryptedMessage { get; set; } public List<SelectListItem> Users { get; set; } public EncryptTextModel(SymmetricEncryptDecrypt symmetricEncryptDecrypt, AsymmetricEncryptDecrypt asymmetricEncryptDecrypt, ApplicationDbContext applicationDbContext, ImportExportCertificate importExportCertificate, DigitalSignatures digitalSignatures, IConfiguration configuration) { _symmetricEncryptDecrypt = symmetricEncryptDecrypt; _asymmetricEncryptDecrypt = asymmetricEncryptDecrypt; _applicationDbContext = applicationDbContext; _importExportCertificate = importExportCertificate; _digitalSignatures = digitalSignatures; _configuration = configuration; } public IActionResult OnGet() { // not good if you have a lot of users Users = _applicationDbContext.Users.Select(a => new SelectListItem { Value = a.Email.ToString(), Text = a.Email }).ToList(); return Page(); } public IActionResult OnPost() { if (!ModelState.IsValid) { // Something failed. Redisplay the form. return OnGet(); } var (Key, IVBase64) = _symmetricEncryptDecrypt.InitSymmetricEncryptionKeyIV(); var encryptedText = _symmetricEncryptDecrypt.Encrypt(Message, IVBase64, Key); var targetUserPublicCertificate = GetCertificateWithPublicKeyForIdentity(TargetUserEmail); var encryptedKey = _asymmetricEncryptDecrypt.Encrypt(Key, Utils.CreateRsaPublicKey(targetUserPublicCertificate)); var encryptedIV = _asymmetricEncryptDecrypt.Encrypt(IVBase64, Utils.CreateRsaPublicKey(targetUserPublicCertificate)); var encryptedSender = _asymmetricEncryptDecrypt.Encrypt(User.Identity.Name, Utils.CreateRsaPublicKey(targetUserPublicCertificate)); var certLoggedInUser = GetCertificateWithPrivateKeyForIdentity(); var signature = _digitalSignatures.Sign(encryptedText, Utils.CreateRsaPrivateKey(certLoggedInUser)); var encryptedDto = new EncryptedDto { EncryptedText = encryptedText, Key = encryptedKey, IV = encryptedIV, DigitalSignature = signature, Sender = encryptedSender }; string jsonString = JsonSerializer.Serialize(encryptedDto); EncryptedMessage = $"{jsonString}"; // Redisplay the form. return OnGet(); } private X509Certificate2 GetCertificateWithPublicKeyForIdentity(string email) { var user = _applicationDbContext.Users.First(user => user.Email == email); var cert = _importExportCertificate.PemImportCertificate(user.PemPublicKey); return cert; } private X509Certificate2 GetCertificateWithPrivateKeyForIdentity() { var user = _applicationDbContext.Users.First(user => user.Email == User.Identity.Name); var cert = _importExportCertificate.PemImportCertificate(user.PemPrivateKey, _configuration["PemPasswordExportImport"]); return cert; } }
The Decrypt Razor page that the payload which was created by the encrypt Razor page. The certificate for the identity logged in is used to decrypt the RSA asymmetric properties, ie the IV, the key and the sender. Then the sender is used to get the public key certificate and with this, the encrypted text is verified. If this is not ok and bad request is returned. If verified, the messge is decrypted using AES like it was created, and the text is displayed in the UI.
public class DecryptTextModel : PageModel { private readonly SymmetricEncryptDecrypt _symmetricEncryptDecrypt; private readonly AsymmetricEncryptDecrypt _asymmetricEncryptDecrypt; private readonly ApplicationDbContext _applicationDbContext; private readonly ImportExportCertificate _importExportCertificate; private readonly DigitalSignatures _digitalSignatures; private readonly IConfiguration _configuration; [BindProperty] public string Message { get; set; } [BindProperty] [Required] public string EncryptedMessage { get; set; } public DecryptTextModel(SymmetricEncryptDecrypt symmetricEncryptDecrypt, AsymmetricEncryptDecrypt asymmetricEncryptDecrypt, ApplicationDbContext applicationDbContext, ImportExportCertificate importExportCertificate, DigitalSignatures digitalSignatures, IConfiguration configuration) { _symmetricEncryptDecrypt = symmetricEncryptDecrypt; _asymmetricEncryptDecrypt = asymmetricEncryptDecrypt; _applicationDbContext = applicationDbContext; _importExportCertificate = importExportCertificate; _digitalSignatures = digitalSignatures; _configuration = configuration; } public IActionResult OnGet() { return Page(); } public IActionResult OnPost() { if (!ModelState.IsValid) { // Something failed. Redisplay the form. return OnGet(); } var cert = GetCertificateWithPrivateKeyForIdentity(); var encryptedDto = JsonSerializer.Deserialize<EncryptedDto>(EncryptedMessage); var sender = _asymmetricEncryptDecrypt.Decrypt(encryptedDto.Sender, Utils.CreateRsaPrivateKey(cert)); var senderCert = GetCertificateWithPublicKeyForIdentity(sender); var verified = _digitalSignatures.Verify(encryptedDto.EncryptedText, encryptedDto.DigitalSignature, Utils.CreateRsaPublicKey(senderCert)); if(!verified) return BadRequest("NOT verified"); var key = _asymmetricEncryptDecrypt.Decrypt(encryptedDto.Key, Utils.CreateRsaPrivateKey(cert)); var IV = _asymmetricEncryptDecrypt.Decrypt(encryptedDto.IV, Utils.CreateRsaPrivateKey(cert)); var text = _symmetricEncryptDecrypt.Decrypt(encryptedDto.EncryptedText, IV, key); Message = $"{text}"; // Redisplay the form. return OnGet(); } private X509Certificate2 GetCertificateWithPrivateKeyForIdentity() { var user = _applicationDbContext.Users.First(user => user.Email == User.Identity.Name); var cert = _importExportCertificate.PemImportCertificate(user.PemPrivateKey, _configuration["PemPasswordExportImport"]); return cert; } private X509Certificate2 GetCertificateWithPublicKeyForIdentity(string email) { var user = _applicationDbContext.Users.First(user => user.Email == email); var cert = _importExportCertificate.PemImportCertificate(user.PemPublicKey); return cert; } }
The texts can not be encrypted, or decrypted in the UI.
Now encryption and decryption is working better. The next step is to improve the user search and use a text search, add exception handling and add support to use streams or files for the AES encryption. The private key PEM files are protected with a secret from the application.
Links:
https://docs.microsoft.com/en-us/dotnet/standard/security/encrypting-data
https://docs.microsoft.com/en-us/dotnet/standard/security/decrypting-data
https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview
https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata.unprotect
https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection
https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=netcore-3.1
https://docs.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography
https://docs.microsoft.com/en-us/dotnet/standard/security/vulnerabilities-cbc-mode
https://edi.wang/post/2019/1/15/caveats-in-aspnet-core-data-protection
https://dev.to/stratiteq/cryptography-with-practical-examples-in-net-core-1mc4
https://www.tpeczek.com/2020/08/supporting-encrypted-content-encoding.html
[…] Using Digital Signatures to check integrity of cipher texts in ASP.NET Core Razor Pages (Damien Bowden) […]
[…] Using Digital Signatures to check integrity of cipher texts in ASP.NET Core Razor Pages – Damien Bowden […]