rtumaykin / ssis-build

A set of utilities that allow to autonomously build a Visual Studio SSIS project (dtproj) into a deployment package (ispac), and deploy the package to an SSIS catalog
Apache License 2.0
52 stars 39 forks source link

SSIS 2017 are not supported when EncryptSensitiveWithPassword #25

Open artur-tolstenco opened 6 years ago

artur-tolstenco commented 6 years ago

this is the error when I try to build a SSIS 2017 project with EncryptSensitiveWithPassword Protection Level:

PS C:\Users\Administrator> . $ssisbuild $prj -Configuration "Development" -Password "aaa" SSIS Build Engine Copyright (c) 2017 Roman Tumaykin

Executing SSIS Build with the following arguments: Project File: c:\Users\administrator\source\repos\SSIS\SSIS\SSIS.dtproj -Password: (hidden) -Configuration: Development

Project parameters: Starting build. Loading project files from c:\Users\administrator\source\repos\SSIS\SSIS\SSIS.dtproj. ERROR: Specified initialization vector (IV) does not match the block size for this algorithm.

artur-tolstenco commented 6 years ago

here some further investigation. on the left side the 2017 project version on the right side the 2016 project version: image

the ssis 2017 project version has a SSIS:Algorithm but the 2016 does not!

e2patrick commented 4 years ago

The problem is that Visual Studio changed the encryption algorithm from TripleDES to AES256-CBC with Version 2017. I changed the encryption and decryption methods to this and it worked for my VS 2017 projects:

protected virtual void EncryptElement(XmlElement element, string password) { if (password == null) throw new InvalidPaswordException(); var rgbSalt = new byte[8]; new RNGCryptoServiceProvider().GetBytes(rgbSalt); var document = element.GetDocument();

        using (MemoryStream ms = new MemoryStream())
        {
            using (RijndaelManaged AES = new RijndaelManaged())
            {
                AES.KeySize = 256;
                AES.BlockSize = 128;

                var key = new Rfc2898DeriveBytes(password, rgbSalt, 1000);
                AES.Key = key.GetBytes(AES.KeySize / 8);
                AES.IV = key.GetBytes(AES.BlockSize / 8);
                //AES.Padding = PaddingMode.None;
                AES.Mode = CipherMode.CBC;

                using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    using (StreamWriter sw = new StreamWriter(cs))
                        sw.Write(element.OuterXml);

                }
                var array = ms.ToArray();
                foreach (XmlNode childNode in element.ChildNodes)
                    element.RemoveChild(childNode);
                element.InnerText = Convert.ToBase64String(array);
                var saltAttribute = document.CreateAttribute("Salt", XmlHelpers.Schemas.SSIS);
                saltAttribute.Value = Convert.ToBase64String(rgbSalt);
                element.SetAttribute("Salt", XmlHelpers.Schemas.SSIS, Convert.ToBase64String(rgbSalt));
                element.SetAttribute("IV", XmlHelpers.Schemas.SSIS, Convert.ToBase64String(AES.IV));
            }
        }
    }

protected virtual void DecryptElement(XmlElement element, string password) { var algXmlAttributeNode = XmlHelpers.GetAttributeNode(element, "Algorithm"); if (!string.IsNullOrEmpty(algXmlAttributeNode?.Value)) {

            var saltXmlAttributeNode = XmlHelpers.GetAttributeNode(element, "Salt");
            if (string.IsNullOrEmpty(saltXmlAttributeNode?.Value))
            {
                throw new InvalidXmlException($"Encrypted element {element.Name} does not contain required Attribute \"Salt\", or its contents is empty", element);
            }
            byte[] rgbSalt;
            try
            {
                rgbSalt = Convert.FromBase64String(saltXmlAttributeNode.Value);
            }
            catch (FormatException)
            {
                throw new InvalidXmlException($"Invalid value of Attribute \"Salt\" ({saltXmlAttributeNode.Value}) in encrypted element {element.Name}", element);
            }
            var ivXmlAttributeNode = XmlHelpers.GetAttributeNode(element, "IV");
            if (string.IsNullOrEmpty(ivXmlAttributeNode?.Value))
            {
                throw new InvalidXmlException($"Encrypted element {element.Name} does not contain required Attribute \"IV\", or its contents is empty", element);
            }
            byte[] iv;
            try
            {
                iv = Convert.FromBase64String(ivXmlAttributeNode.Value);
            }
            catch (FormatException)
            {
                throw new InvalidXmlException($"Invalid value of Attribute \"IV\" ({ivXmlAttributeNode.Value}) in encrypted element {element.Name} ", element);
            }
            string xml;
            byte[] buffer;
            try
            {
                buffer = Convert.FromBase64String(element.InnerText);
            }
            catch (FormatException)
            {
                throw new InvalidXmlException($"Invalid value of encrypted element {element.Name}.", element);
            }
            try
            {
                using (MemoryStream ms = new MemoryStream(buffer))
                {
                    using (RijndaelManaged AES = new RijndaelManaged())
                    {
                        AES.KeySize = 256;
                        AES.BlockSize = 128;

                        var key = new Rfc2898DeriveBytes(password, rgbSalt, 1000);
                        AES.Key = key.GetBytes(AES.KeySize / 8);
                        AES.IV = iv;

                        AES.Mode = CipherMode.CBC;
                        using (CryptoStream csDecrypt = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Read))
                        {
                            using (StreamReader srDecrypt = new StreamReader(csDecrypt))

                                // Read the decrypted bytes from the decrypting stream
                                // and place them in a string.
                                xml = srDecrypt.ReadToEnd();
                        }

                    }
                }
            }

            catch (CryptographicException)
            {
                throw new InvalidPaswordException();
            }

            var xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xml);

            // The reason to not simply import the new node is because namespace declaration will also be imported with the node.
            element.Attributes.Remove(saltXmlAttributeNode);
            element.Attributes.Remove(ivXmlAttributeNode);

            foreach (XmlNode childNode in element.ChildNodes)
                element.RemoveChild(childNode);
            element.InnerXml = xmlDocument.DocumentElement?.InnerXml;
        }
    }