I have a situation where I have an envelopeCms object on disk. It contains:
- The generated AES-256 private-key encrypted using a public key
- The actual payload encrypted with the generated AES-256 private-key
Now, I have the AES-256 private-key with me. I want to use this to manually decrypt the encrypted data.
To decrypt the message, I need:
- The decrypted AES 256 key (which I have separately)
- IV (initialization vector)
I believe the IV is the first 16 bytes of the actual encrypted content. Based on the above understanding, I have this code (DotNet8), not using BouncyCastle or any 3rd party libraries, just the Microsoft System.Security.Cryptography.Pkcs nuget:
using System.Security.Cryptography;
using System.Text;
using System.Security.Cryptography.Pkcs;
static void Main(string[] args)
{
string inputFile = "D:\\temp\\test.txt.enc"; // my envelopedCmd file created by another utility
byte[] baEncryptedEnvelopedMessage = File.ReadAllBytes(inputFile);
DecodeDirect(baEncryptedEnvelopedMessage);
}
public static byte[] DecodeDirect(byte[] encryptedMessage)
{
string aesEncrytionKeyFile = "D:\\temp\\decryptionkey.bin";
EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedMessage);
byte[] aesKey;
byte[] baEncKey = envelopedCms.RecipientInfos[0].EncryptedKey;
aesKey = File.ReadAllBytes(aesEncrytionKeyFile);
byte[] iv;
byte[] baEncContentOnly;
byte[] baEncContentPlusIV = envelopedCms.ContentInfo.Content;
ExtractEncryptedContentAndIV(baEncContentPlusIV, aesKey, out iv, out baEncContentOnly);
byte[] content = DecryptAes(baEncContentOnly, aesKey, iv); // this is not matching the original unecrypted bytes. This one has length: 415 whereas original content has length: 431
string decryptedTextContent = Encoding.Default.GetString(content); // for debugging, this is not matching
return content;
}
private static void ExtractEncryptedContentAndIV(byte[] encryptedContent, byte[] aesKey, out byte[] iv, out byte[] actualCipherText)
{
int ivLength = 16;
iv = new byte[ivLength];
Buffer.BlockCopy(encryptedContent, 0, iv, 0, ivLength);
actualCipherText = new byte[encryptedContent.Length - ivLength];
Buffer.BlockCopy(encryptedContent, ivLength, actualCipherText, 0, actualCipherText.Length);
}
private static byte[] DecryptAes(byte[] cipherText, byte[] key, byte[] iv)
{
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.IV = iv;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor())
{
return decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);
}
}
}
The results: I can actually see "most" of the decrypted content (string) having the correct values.
The original unencrypted file content: "hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world"
The decrypted content: "o world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world"
I have tried with other strings like: Original: "ABCDEFGHIJK hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world LMNOPQRSTUV"
Decrypted:
"o world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world LMNOPQRSTUV"
After trying a couple of different content, it looks like the first 16 bytes are always missing.
Note: If I use DotNetCore's envelopeCmd's Decode() & Decrypt() method on the same input encrypted file D:\temp\test.txt.enc, all bytes are decrypted perfectly and the original unencrypted and the decrypted strings match perfectly.
I have uploaded the source code and the AES key file and the envelopeCmd file in github.
Help! I need somebody help!
Code used to create the encryptedCms in case anyone thinks this will help to solve the problem:
static void main()
{
byte[] content = File.ReadAllBytes(inputFile);
byte[] encodedContent = new PKCSUtil().Encode(content, certFile, certPass);
File.WriteAllBytes(outputFile, encodedContent);
Console.WriteLine("Encoded content written successfully.");
}
static void byte[] Encode(byte[] content, string publicKeyCertFile, string? password = null)
{
X509Certificate2 recipientCert = new X509Certificate2(publicKeyCertFile, password);
return Encode(recipientCert, content);
}
public byte[] Encode(X509Certificate2 certificate, byte[] content)
{
var envelopedCms = new EnvelopedCms(new ContentInfo(content));
var recipient = new CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, certificate, RSAEncryptionPadding.OaepSHA1);
envelopedCms.Encrypt(recipient);
return envelopedCms.Encode();
}
envelopdData from test.txt.enc (hex encoded):
3082039006092A864886F70D010703A08203813082037D020100318201943082019002010030783060310B300906035504061302494E310B300906035504080C024D48310D300B06035504070C0450554E453121301F060355040A0C18496E7465726E6574205769646769747320507479204C74643112301006035504030C096C6F63616C686F7374021451B1B6A5426ACE913F571FC5F11814BE46894087300D06092A864886F70D0101073000048201008045C3563B2862AFDAAFD1C709D147B11C56E653244DF01F7521DD630F86BCBBF2C0778E2FEF21352A2FC4BE4BEB885E821ADA71F24F9B684E640D2031703CF642009BB824B4EF08612A55CA0DE7454DC03B356D505A88EB7335868523AF975FB062D2542995C15A42A39C0A372F4251247FD4B2923EAB9E7E791DA542C42FB368487F0E49E0EFDC76B2863B4773490C98BA47244BF02ACEEFDD926188E481FE32BE085325F7A50CFCC96677B1695705E1D63A0BA06E6B90865B4858D328DA280C5B7D86FF62E284BA90BA4366F7AB83F0C8AFDE1503FB05A1D1DA2E043733371415BD754F263CF36A1F7D8E23C4EBF3D8352544E81C5C5C1494AE2EDB52FBFC308201DE06092A864886F70D010701301D060960864801650304012A0410A9AFB334A7074ABB58D5CFD3FFB4DE7E808201B046203B6855EB5CDA67EE41AA0688D7958312F447178108F71B55ECF612E227051D6AFA6C454375E9FAF6A9143E823A29E8C7F8912BD84224EE8A4D06D061A3F72DF681D2F19295DD7701EBE439BD849310AB7DB9B662512FCE71E22A23126A36EF062A8968220B3923226AF0BB3FA625AAFECD0F59F9D44893039EC4B8A34D39782EF8B89D90A4B25E24904872D4B751246157720FA4B38F0948D4D50F659E8B2B8B3ADA52D0E30E2B6331FCB57BE2183AB50BD290205372CE05FA9BF2DCF6547DD2806FF089F1338F4D13DFAEA1C0C19C238A7DA201B3DDE245B32CC85FCFD47C0EA222F11D7BA88455BE5CCB612197A02C16E3266A57A74441A94B18BE419480F6CE0F6AD9074EB69AFA8E7895853B538DF413113A9B2699A9D51F8C0BC01FD1F0CAB4595AE15FCE8045283DD1FD84F8E1025CD4017E63203A4C0C2D2512CA51F0511C0F6597CAD1F4CD019965BA6A2116C1C6CB5D8FA1598982D62A759BDF6FA92A9EC72A0DD91B54219ED6E2B216E94EAF570FE3135DD86464A75CAE5C04866A2D520DD2728BF98EBCEA0CA9EECB231AA60C95DDFF70C6ECDA698DC6511A712F9797F9B11C9D1D06DB0C3F0DBFC2
AES key from decryptionkey.bin (hex encoded):
57BC7E022874EFB52AE41525F3E7C46FC5B3F754F8955C9398A8884527BDF678
I have a situation where I have an envelopeCms object on disk. It contains:
- The generated AES-256 private-key encrypted using a public key
- The actual payload encrypted with the generated AES-256 private-key
Now, I have the AES-256 private-key with me. I want to use this to manually decrypt the encrypted data.
To decrypt the message, I need:
- The decrypted AES 256 key (which I have separately)
- IV (initialization vector)
I believe the IV is the first 16 bytes of the actual encrypted content. Based on the above understanding, I have this code (DotNet8), not using BouncyCastle or any 3rd party libraries, just the Microsoft System.Security.Cryptography.Pkcs nuget:
using System.Security.Cryptography;
using System.Text;
using System.Security.Cryptography.Pkcs;
static void Main(string[] args)
{
string inputFile = "D:\\temp\\test.txt.enc"; // my envelopedCmd file created by another utility
byte[] baEncryptedEnvelopedMessage = File.ReadAllBytes(inputFile);
DecodeDirect(baEncryptedEnvelopedMessage);
}
public static byte[] DecodeDirect(byte[] encryptedMessage)
{
string aesEncrytionKeyFile = "D:\\temp\\decryptionkey.bin";
EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedMessage);
byte[] aesKey;
byte[] baEncKey = envelopedCms.RecipientInfos[0].EncryptedKey;
aesKey = File.ReadAllBytes(aesEncrytionKeyFile);
byte[] iv;
byte[] baEncContentOnly;
byte[] baEncContentPlusIV = envelopedCms.ContentInfo.Content;
ExtractEncryptedContentAndIV(baEncContentPlusIV, aesKey, out iv, out baEncContentOnly);
byte[] content = DecryptAes(baEncContentOnly, aesKey, iv); // this is not matching the original unecrypted bytes. This one has length: 415 whereas original content has length: 431
string decryptedTextContent = Encoding.Default.GetString(content); // for debugging, this is not matching
return content;
}
private static void ExtractEncryptedContentAndIV(byte[] encryptedContent, byte[] aesKey, out byte[] iv, out byte[] actualCipherText)
{
int ivLength = 16;
iv = new byte[ivLength];
Buffer.BlockCopy(encryptedContent, 0, iv, 0, ivLength);
actualCipherText = new byte[encryptedContent.Length - ivLength];
Buffer.BlockCopy(encryptedContent, ivLength, actualCipherText, 0, actualCipherText.Length);
}
private static byte[] DecryptAes(byte[] cipherText, byte[] key, byte[] iv)
{
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.IV = iv;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor())
{
return decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length);
}
}
}
The results: I can actually see "most" of the decrypted content (string) having the correct values.
The original unencrypted file content: "hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world"
The decrypted content: "o world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world"
I have tried with other strings like: Original: "ABCDEFGHIJK hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world LMNOPQRSTUV"
Decrypted:
"o world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world LMNOPQRSTUV"
After trying a couple of different content, it looks like the first 16 bytes are always missing.
Note: If I use DotNetCore's envelopeCmd's Decode() & Decrypt() method on the same input encrypted file D:\temp\test.txt.enc, all bytes are decrypted perfectly and the original unencrypted and the decrypted strings match perfectly.
I have uploaded the source code and the AES key file and the envelopeCmd file in github.
Help! I need somebody help!
Code used to create the encryptedCms in case anyone thinks this will help to solve the problem:
static void main()
{
byte[] content = File.ReadAllBytes(inputFile);
byte[] encodedContent = new PKCSUtil().Encode(content, certFile, certPass);
File.WriteAllBytes(outputFile, encodedContent);
Console.WriteLine("Encoded content written successfully.");
}
static void byte[] Encode(byte[] content, string publicKeyCertFile, string? password = null)
{
X509Certificate2 recipientCert = new X509Certificate2(publicKeyCertFile, password);
return Encode(recipientCert, content);
}
public byte[] Encode(X509Certificate2 certificate, byte[] content)
{
var envelopedCms = new EnvelopedCms(new ContentInfo(content));
var recipient = new CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, certificate, RSAEncryptionPadding.OaepSHA1);
envelopedCms.Encrypt(recipient);
return envelopedCms.Encode();
}
envelopdData from test.txt.enc (hex encoded):
3082039006092A864886F70D010703A08203813082037D020100318201943082019002010030783060310B300906035504061302494E310B300906035504080C024D48310D300B06035504070C0450554E453121301F060355040A0C18496E7465726E6574205769646769747320507479204C74643112301006035504030C096C6F63616C686F7374021451B1B6A5426ACE913F571FC5F11814BE46894087300D06092A864886F70D0101073000048201008045C3563B2862AFDAAFD1C709D147B11C56E653244DF01F7521DD630F86BCBBF2C0778E2FEF21352A2FC4BE4BEB885E821ADA71F24F9B684E640D2031703CF642009BB824B4EF08612A55CA0DE7454DC03B356D505A88EB7335868523AF975FB062D2542995C15A42A39C0A372F4251247FD4B2923EAB9E7E791DA542C42FB368487F0E49E0EFDC76B2863B4773490C98BA47244BF02ACEEFDD926188E481FE32BE085325F7A50CFCC96677B1695705E1D63A0BA06E6B90865B4858D328DA280C5B7D86FF62E284BA90BA4366F7AB83F0C8AFDE1503FB05A1D1DA2E043733371415BD754F263CF36A1F7D8E23C4EBF3D8352544E81C5C5C1494AE2EDB52FBFC308201DE06092A864886F70D010701301D060960864801650304012A0410A9AFB334A7074ABB58D5CFD3FFB4DE7E808201B046203B6855EB5CDA67EE41AA0688D7958312F447178108F71B55ECF612E227051D6AFA6C454375E9FAF6A9143E823A29E8C7F8912BD84224EE8A4D06D061A3F72DF681D2F19295DD7701EBE439BD849310AB7DB9B662512FCE71E22A23126A36EF062A8968220B3923226AF0BB3FA625AAFECD0F59F9D44893039EC4B8A34D39782EF8B89D90A4B25E24904872D4B751246157720FA4B38F0948D4D50F659E8B2B8B3ADA52D0E30E2B6331FCB57BE2183AB50BD290205372CE05FA9BF2DCF6547DD2806FF089F1338F4D13DFAEA1C0C19C238A7DA201B3DDE245B32CC85FCFD47C0EA222F11D7BA88455BE5CCB612197A02C16E3266A57A74441A94B18BE419480F6CE0F6AD9074EB69AFA8E7895853B538DF413113A9B2699A9D51F8C0BC01FD1F0CAB4595AE15FCE8045283DD1FD84F8E1025CD4017E63203A4C0C2D2512CA51F0511C0F6597CAD1F4CD019965BA6A2116C1C6CB5D8FA1598982D62A759BDF6FA92A9EC72A0DD91B54219ED6E2B216E94EAF570FE3135DD86464A75CAE5C04866A2D520DD2728BF98EBCEA0CA9EECB231AA60C95DDFF70C6ECDA698DC6511A712F9797F9B11C9D1D06DB0C3F0DBFC2
AES key from decryptionkey.bin (hex encoded):
57BC7E022874EFB52AE41525F3E7C46FC5B3F754F8955C9398A8884527BDF678
Share
Improve this question
edited Mar 10 at 9:45
Topaco
49.8k4 gold badges45 silver badges80 bronze badges
asked Mar 9 at 16:31
Siddharth BSiddharth B
3663 silver badges13 bronze badges
7
|
Show 2 more comments
1 Answer
Reset to default 3The IV is contained in envelopedData, as you can check by loading the data into an ASN.1 parser, e.g. here:
With this IV, direct decryption is successful:
byte[] encryptedMessage = Convert.FromHexString("3082039006092A864886F70D010703A08203813082037D020100318201943082019002010030783060310B300906035504061302494E310B300906035504080C024D48310D300B06035504070C0450554E453121301F060355040A0C18496E7465726E6574205769646769747320507479204C74643112301006035504030C096C6F63616C686F7374021451B1B6A5426ACE913F571FC5F11814BE46894087300D06092A864886F70D0101073000048201008045C3563B2862AFDAAFD1C709D147B11C56E653244DF01F7521DD630F86BCBBF2C0778E2FEF21352A2FC4BE4BEB885E821ADA71F24F9B684E640D2031703CF642009BB824B4EF08612A55CA0DE7454DC03B356D505A88EB7335868523AF975FB062D2542995C15A42A39C0A372F4251247FD4B2923EAB9E7E791DA542C42FB368487F0E49E0EFDC76B2863B4773490C98BA47244BF02ACEEFDD926188E481FE32BE085325F7A50CFCC96677B1695705E1D63A0BA06E6B90865B4858D328DA280C5B7D86FF62E284BA90BA4366F7AB83F0C8AFDE1503FB05A1D1DA2E043733371415BD754F263CF36A1F7D8E23C4EBF3D8352544E81C5C5C1494AE2EDB52FBFC308201DE06092A864886F70D010701301D060960864801650304012A0410A9AFB334A7074ABB58D5CFD3FFB4DE7E808201B046203B6855EB5CDA67EE41AA0688D7958312F447178108F71B55ECF612E227051D6AFA6C454375E9FAF6A9143E823A29E8C7F8912BD84224EE8A4D06D061A3F72DF681D2F19295DD7701EBE439BD849310AB7DB9B662512FCE71E22A23126A36EF062A8968220B3923226AF0BB3FA625AAFECD0F59F9D44893039EC4B8A34D39782EF8B89D90A4B25E24904872D4B751246157720FA4B38F0948D4D50F659E8B2B8B3ADA52D0E30E2B6331FCB57BE2183AB50BD290205372CE05FA9BF2DCF6547DD2806FF089F1338F4D13DFAEA1C0C19C238A7DA201B3DDE245B32CC85FCFD47C0EA222F11D7BA88455BE5CCB612197A02C16E3266A57A74441A94B18BE419480F6CE0F6AD9074EB69AFA8E7895853B538DF413113A9B2699A9D51F8C0BC01FD1F0CAB4595AE15FCE8045283DD1FD84F8E1025CD4017E63203A4C0C2D2512CA51F0511C0F6597CAD1F4CD019965BA6A2116C1C6CB5D8FA1598982D62A759BDF6FA92A9EC72A0DD91B54219ED6E2B216E94EAF570FE3135DD86464A75CAE5C04866A2D520DD2728BF98EBCEA0CA9EECB231AA60C95DDFF70C6ECDA698DC6511A712F9797F9B11C9D1D06DB0C3F0DBFC2");
EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedMessage);
byte[] aesKey = Convert.FromHexString("57BC7E022874EFB52AE41525F3E7C46FC5B3F754F8955C9398A8884527BDF678");
byte[] iv = Convert.FromHexString("A9AFB334A7074ABB58D5CFD3FFB4DE7E");
byte[] encryptedContent = envelopedCms.ContentInfo.Content;
byte[] content = DecryptAes(encryptedContent, aesKey, iv);
string strContent = Encoding.UTF8.GetString(content);
Console.WriteLine(strContent); // ABCDEFGHIJK hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world hello world LMNOPQRSTUV
However, when envelopedCms.Decode(encryptedMessage)
is called, EnvelopedCms.ContentEncryptionAlgorithm
is not updated with the data from envelopedData, so that the IV cannot be determined using envelopedCms
. This may be an MS bug.
In contrast, the decryption with envelopedCms.Decrypt()
works correctly (as you have already found out yourself), which means that somehow the correct IV must be determined.
Presumably, when envelopedCms.Decode(encryptedMessage)
is called, a reference to the envelopedData is held internally and the IV is determined directly from it somewhere in EnvelopedCms.Decrypt()
.
The direct determination of the IV from the envelopedData is also a workaround for determining the IV for your direct decryption. A convenient way to do this is to use an ASN.1 parser, e.g. AsnReader
, a stateful, forward-only reader for DER encoded ASN.1 data:
private static byte[] GetIV(byte[] encryptedMessage)
{
var seq0 = new AsnReader(encryptedMessage, AsnEncodingRules.DER).ReadSequence();
seq0.ReadObjectIdentifier(); // 1.2.840.113549.1.7.3
var ctx0 = seq0.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
var seq1 = ctx0.ReadSequence();
seq1.ReadInteger();
seq1.ReadSetOf();
var seq2 = seq1.ReadSequence();
seq2.ReadObjectIdentifier(); // 1.2.840.113549.1.7.1
var seq3 = seq2.ReadSequence();
seq3.ReadObjectIdentifier(); // 2.16.840.1.101.3.4.1.42
var iv = seq3.ReadOctetString();
return iv;
}
EnvelopedCms
using a proper X509 certificate, not a bare AES private key. – Charlieface Commented Mar 9 at 17:48envelopedCMS.ContentEncryptionAlgorithm
. – President James K. Polk Commented Mar 9 at 18:06