欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NetCore分布式部署中的DataProtection密钥安全性

程序员文章站 2022-04-09 17:40:58
在.NetCore中默认使用DataProtection来保护数据,例如Cooike等。一般情况下DataProtection生成的密钥会被加密后存储,例如默认的文件存储 可以看到使用了Windows DPAPI加密。 但是如果更改默认设置例如使用的外部存储如redis则此时密钥默认是不加密的 微软 ......

在.netcore中默认使用dataprotection来保护数据,例如cooike等。一般情况下dataprotection生成的密钥会被加密后存储,例如默认的文件存储

.NetCore分布式部署中的DataProtection密钥安全性

可以看到使用了windows dpapi加密。

但是如果更改默认设置例如使用的外部存储如redis则此时密钥默认是不加密的

微软说明如下

.NetCore分布式部署中的DataProtection密钥安全性

.NetCore分布式部署中的DataProtection密钥安全性

警告密钥未加密,这个时候如果redis被破解,系统的密钥也就泄漏了。

微软提供了2个接口ixmlencryptor,ixmldecryptor来实现密钥的加密解密,下面使用aes来简单现实,也可以替换为任何加密方式

.NetCore分布式部署中的DataProtection密钥安全性
namespace microsoft.aspnetcore.dataprotection
{
    /// <summary>
    /// extensions for configuring data protection using an <see cref="idataprotectionbuilder"/>.
    /// </summary>
    public static class dataprotectionbuilderextensions
    {
        /// <summary>
        /// configures keys to be encrypted with aes before being persisted to
        /// storage.
        /// </summary>
        /// <param name="builder">the <see cref="idataprotectionbuilder"/>.</param>
        /// use on the local machine, 'false' if the key should only be decryptable by the current
        /// windows user account.</param>
        /// <returns>a reference to the <see cref="idataprotectionbuilder" /> after this operation has completed.</returns>
        public static idataprotectionbuilder protectkeyswithaes(this idataprotectionbuilder builder)
        {
            if (builder == null)
            {
                throw new argumentnullexception(nameof(builder));
            }

            builder.services.addsingleton<iconfigureoptions<keymanagementoptions>>(services =>
            {
                //var loggerfactory = services.getservice<iloggerfactory>() ?? nullloggerfactory.instance;
                return new configureoptions<keymanagementoptions>(options =>
                {
                    options.xmlencryptor = new aesxmlencryptor();
                });
            });

            return builder;
        }
    }
    /// <summary>
    /// an <see cref="ixmlencryptor"/> that encrypts xml elements with a aes encryptor.
    /// </summary>
    sealed class aesxmlencryptor : ixmlencryptor
    {
        /// <summary>
        /// encrypts the specified <see cref="xelement"/> with a null encryptor, i.e.,
        /// by returning the original value of <paramref name="plaintextelement"/> unencrypted.
        /// </summary>
        /// <param name="plaintextelement">the plaintext to echo back.</param>
        /// <returns>
        /// an <see cref="encryptedxmlinfo"/> that contains the null-encrypted value of
        /// <paramref name="plaintextelement"/> along with information about how to
        /// decrypt it.
        /// </returns>
        public encryptedxmlinfo encrypt(xelement plaintextelement)
        {
            if (plaintextelement == null)
            {
                throw new argumentnullexception(nameof(plaintextelement));
            }
            // <encryptedkey>
            //   <!-- this key is encrypted with {provider}. -->
            //   <value>{base64}</value>
            // </encryptedkey>

            var jsonxmlstr =jsonconvert.serializeobject(plaintextelement);
            var encrypteddata = encrypthelper.aesencrypt(jsonxmlstr, "b587be32-0420-4eb1-89c6-01bb999e18fe");
            var newelement = new xelement("encryptedkey",
                new xcomment(" this key is encrypted with aes."),
                new xelement("value",encrypteddata));

            return new encryptedxmlinfo(newelement, typeof(aesxmldecryptor));
        }
    }
    /// <summary>
    /// an <see cref="ixmldecryptor"/> that decrypts xml elements with a aes decryptor.
    /// </summary>
    sealed class aesxmldecryptor : ixmldecryptor
    {
        /// <summary>
        /// decrypts the specified xml element.
        /// </summary>
        /// <param name="encryptedelement">an encrypted xml element.</param>
        /// <returns>the decrypted form of <paramref name="encryptedelement"/>.</returns>
        public xelement decrypt(xelement encryptedelement)
        {
            if (encryptedelement == null)
            {
                throw new argumentnullexception(nameof(encryptedelement));
            }

            // <encryptedkey>
            //   <!-- this key is encrypted with {provider}. -->
            //   <value>{base64}</value>
            // </encryptedkey>
            var encrypteddata=(string)encryptedelement.element("value");
            var jsonxmlstr = encrypthelper.aesdecrypt(encrypteddata, "b587be32-0420-4eb1-89c6-01bb999e18fe");

            // return a clone of the single child node.
            return jsonconvert.deserializeobject<xelement>(jsonxmlstr);
        }
    }
    #region aes
    public class encrypthelper
    {
        static readonly byte[] aes_iv = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef };
        /// <summary>
        /// aes加密算法
        /// </summary>
        /// <param name="encryptstring">加密前字符串</param>
        /// <param name="keytype">秘钥</param>
        /// <returns></returns>
        public static string aesencrypt(string encryptstring, string encryptkey)
        {
            if (string.isnullorwhitespace(encryptstring)) return null;
            if (string.isnullorwhitespace(encryptkey)) return null;
            encryptkey = encryptkey.padright(32, ' ');
            byte[] keybytes = encoding.utf8.getbytes(encryptkey.substring(0, 32));
            using (aescryptoserviceprovider aesalg = new aescryptoserviceprovider())
            {
                aesalg.key = keybytes;
                aesalg.iv = aes_iv;
                icryptotransform encryptor = aesalg.createencryptor(aesalg.key, aesalg.iv);
                using (memorystream msencrypt = new memorystream())
                {
                    using (cryptostream csencrypt = new cryptostream(msencrypt, encryptor, cryptostreammode.write))
                    {
                        using (streamwriter swencrypt = new streamwriter(csencrypt))
                        {
                            swencrypt.write(encryptstring);
                        }
                        byte[] bytes = msencrypt.toarray();
                        return convert.tobase64string(bytes).replace('+', '-').replace('/', '_');
                    }
                }
            }
        }
        /// <summary>
        /// aes解密算法
        /// </summary>
        /// <param name="decryptstring">解密前的字符串</param>
        /// <param name="keytype">秘钥</param>
        /// <returns></returns>
        public static string aesdecrypt(string decryptstring, string decryptkey)
        {
            if (string.isnullorwhitespace(decryptstring)) return null;
            decryptstring = decryptstring.replace('-', '+').replace('_', '/');
            if (string.isnullorwhitespace(decryptkey)) return null;
            decryptkey = decryptkey.padright(32, ' ');
            byte[] keybytes = encoding.utf8.getbytes(decryptkey.substring(0, 32));
            byte[] inputbytes = convert.frombase64string(decryptstring);
            using (aescryptoserviceprovider aesalg = new aescryptoserviceprovider())
            {
                aesalg.key = keybytes;
                aesalg.iv = aes_iv;
                icryptotransform decryptor = aesalg.createdecryptor(aesalg.key, aesalg.iv);
                using (memorystream msencrypt = new memorystream(inputbytes))
                {
                    using (cryptostream csencrypt = new cryptostream(msencrypt, decryptor, cryptostreammode.read))
                    {
                        using (streamreader srencrypt = new streamreader(csencrypt))
                        {
                            return srencrypt.readtoend();
                        }
                    }
                }
            }
        }
    }
    #endregion
}
view code

 

调用也很简单.protectkeyswithaes()即可

  services.adddataprotection().setapplicationname("dataprotection").persistkeystostackexchangeredis(connectionmultiplexer.connect(redisconnection), "dataprotection-keys").protectkeyswithaes();

加密后的密钥如下

.NetCore分布式部署中的DataProtection密钥安全性

注:在生成密钥之前要删除之前的密钥,不然会使用旧密钥而不生成新的密钥直到密钥过期。

 

对于aes所使用密钥也要进行保护,可以使用第三方密钥存储库如azure 密钥保管库,或者也可以使用x509证书来来加密。

 github  https://github.com/saber-wang/dataprotection