最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Manually decrypting EnvelopeCms content - Stack Overflow

programmeradmin3浏览0评论

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
  • I have updated the question to include the code used to create the envelopCms msg and write it to a file. – Siddharth B Commented Mar 9 at 17:36
  • You're supposed to decrypt an EnvelopedCms using a proper X509 certificate, not a bare AES private key. – Charlieface Commented Mar 9 at 17:48
  • 2 The IV is not at the beginning of the encrypted data, it is part of the algorithm identifier for the encryption algorithm, so it's part of envelopedCMS.ContentEncryptionAlgorithm. – President James K. Polk Commented Mar 9 at 18:06
  • Answer to President James - that's what I read but after calling the Decode() method of the envelopedCms object, the envelopedCms.ContentEncryptionAlgorithm.Parameters is a zero byte array. So clearly the IV is not here. – Siddharth B Commented Mar 9 at 18:30
  • 1 @Topaco An issue has been opened on GitHub for this github/dotnet/runtime/issues/113668 please add your thoughts if any. – Charlieface Commented Mar 18 at 19:47
 |  Show 2 more comments

1 Answer 1

Reset to default 3

The 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;
}
发布评论

评论列表(0)

  1. 暂无评论