.NetCore分布式部署中的DataProtection密钥安全性
程序员文章站
2022-04-09 17:40:58
在.NetCore中默认使用DataProtection来保护数据,例如Cooike等。一般情况下DataProtection生成的密钥会被加密后存储,例如默认的文件存储 可以看到使用了Windows DPAPI加密。 但是如果更改默认设置例如使用的外部存储如redis则此时密钥默认是不加密的 微软 ......
在.netcore中默认使用dataprotection来保护数据,例如cooike等。一般情况下dataprotection生成的密钥会被加密后存储,例如默认的文件存储
可以看到使用了windows dpapi加密。
但是如果更改默认设置例如使用的外部存储如redis则此时密钥默认是不加密的
微软说明如下
警告密钥未加密,这个时候如果redis被破解,系统的密钥也就泄漏了。
微软提供了2个接口ixmlencryptor,ixmldecryptor来实现密钥的加密解密,下面使用aes来简单现实,也可以替换为任何加密方式
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 }
调用也很简单.protectkeyswithaes()即可
services.adddataprotection().setapplicationname("dataprotection").persistkeystostackexchangeredis(connectionmultiplexer.connect(redisconnection), "dataprotection-keys").protectkeyswithaes();
加密后的密钥如下
注:在生成密钥之前要删除之前的密钥,不然会使用旧密钥而不生成新的密钥直到密钥过期。
对于aes所使用密钥也要进行保护,可以使用第三方密钥存储库如azure 密钥保管库,或者也可以使用x509证书来来加密。
github https://github.com/saber-wang/dataprotection