【问题标题】:Encrypt String in .NET Core在 .NET Core 中加密字符串
【发布时间】:2016-12-12 05:10:33
【问题描述】:

我想使用密钥加密 .NET Core 中的字符串。我有一个客户端/服务器场景,想在客户端加密一个字符串,将其发送到服务器并解密。

由于 .NET Core 仍处于早期阶段(例如,Rijndael 尚不可用),我有哪些选择?

【问题讨论】:

标签: c# encryption .net-core


【解决方案1】:

您真的不应该在 .NET 中使用 Rijndael/RijndaelManaged。如果你使用它的 BlockSize 值为 128(这是默认值),那么你使用的是 AES,因为我 explained in a similar question。

.NET Core 中可用的对称加密选项有:

  • AES (System.Security.Cryptography.Aes.Create())
  • 3DES (System.Security.Cryptography.TripleDES.Create())

对于非对称加密

  • RSA (System.Security.Cryptography.RSA.Create())

特别是在 .NET Core 上,工厂是最好的选择,因为它们会返回一个在当前执行的操作系统上工作的对象。例如,RSACng 是一种公共类型,但仅适用于 Windows;并且 RSAOpenSsl 是公共类型,但仅在 Linux 和 macOS 上受支持。

【讨论】:

  • 我正在使用带有 System.Security.Cryptograph 的 .Net Core,并且似乎缺少 Aes“Create()”函数。我已将 "System.Security.Cryptography.Algorithms": "4.3.0" 包含到我的 project.json 中,我是否缺少参考?这是下面的编译错误。错误 CS0426 类型名称“Create”在类型“Aes”中不存在
  • @phanf apisof.net/catalog/System.Security.Cryptography.Aes.Create() 表示它存在于所有版本的 .NET Core 中。您是否使用了同名的不同类型?
  • 也无法在此包中的各种类上找到 create() 方法。 macsosx 上最新的 dotnet 核心。
【解决方案2】:

这个问题已经有了答案,但我认为我们可以提供一个更简单的解决方案。

如果你只是想保护你的数据,在 .NET Core 中有一个实现,它可以让你摆脱加密的烦恼; DataProtectionProvider.

在Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection(); //Add this
    [..]
    services.AddMvc();
}

如果您愿意,可以指定用于加密和验证的算法(使用Microsoft.AspNetCore.DataProtection),如下所示:

services.AddDataProtection()
       .UseCryptographicAlgorithms(new AuthenticatedEncryptionSettings()
       {
           EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM,
           ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
       });

然后使用这样的服务加密/解密:

public class CipherService : ICipherService
{
    private readonly IDataProtectionProvider _dataProtectionProvider;
    private const string Key = "my-very-long-key-of-no-exact-size";

    public CipherService(IDataProtectionProvider dataProtectionProvider)
    {
        _dataProtectionProvider = dataProtectionProvider;
    }

    public string Encrypt(string input)
    {
        var protector = _dataProtectionProvider.CreateProtector(Key);
        return protector.Protect(input);
    }

    public string Decrypt(string cipherText)
    {
        var protector = _dataProtectionProvider.CreateProtector(Key);
        return protector.Unprotect(cipherText);
    }
}

编辑 正如下面的 cmets 中所提到的,了解像这样使用 DataProtectionProvider 只能在同一台机器上使用存储在本地磁盘上的密钥。

【讨论】:

  • 这个也可以在.NET MVC中使用吗?
  • 值得注意的是,这里的密钥存储在机器上(默认情况下),因此只能由加密它的机器解密。如果您需要能够在应用程序或不同服务的多个实例(例如单点登录)上解密密钥,请使用像 Microsoft.AspNetCore.DataProtection.Redis 这样的分布式数据保护来共享密钥。
  • "my-very-long-key-of-no-exact-size" 不是 excrpytion 键,只是保护器的唯一名称。默认情况下,实际的密钥作为文件存储在磁盘上(如果在网络场中,请注意)
  • 您应该编辑您的答案,因为当有人在多个实例和一个实例上部署此代码时,他们会遇到糟糕的一天,并且密钥将不一样。
  • Microsoft 建议不要将其用于长期受保护的数据。
【解决方案3】:

这是一个没有身份验证的简单示例:

var text = "Hello World";
var buffer = Encoding.UTF8.GetBytes(text);

var iv = GetRandomData(128);
var keyAes = GetRandomData(256);


byte[] result;
using (var aes = Aes.Create())
{
    aes.Key = keyAes;
    aes.IV = iv;

    using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
    using (var resultStream = new MemoryStream())
    {
        using (var aesStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
        using (var plainStream = new MemoryStream(buffer))
        {
            plainStream.CopyTo(aesStream);
        }

        result = resultStream.ToArray();
    }
}

对于密钥生成:

private static byte[] GetRandomData(int bits)
{
    var result = new byte[bits / 8];
    RandomNumberGenerator.Create().GetBytes(result);
    return result;
}

【讨论】:

  • 如果你的 byte[] 足够小,你也可以避免使用流,并使用 encryptor.TransformFinalBlock(buffer, 0, buffer.Length)
  • 更少的代码,更少的创建对象,有什么不喜欢的?
  • 这只是转移了问题。现在您必须将密钥和 iv 存储在安全的地方。
  • @MikeD。只有密钥必须安全存储,IV 可以公开。这是纯对称密钥算法的示例,当然您应该实现密钥建立。看看ECDiffieHellman.DeriveKeyFromHash
【解决方案4】:

我有一个不同的方法,我想用一个密钥加密一个字符串,并得到一个可以再次用同一个密钥解密的加扰字符串。请参阅以下扩展方法:

    public static string Encrypt(this string text, string key)
    {
        if (string.IsNullOrEmpty(key))
            throw new ArgumentException("Key must have valid value.", nameof(key));
        if (string.IsNullOrEmpty(text))
            throw new ArgumentException("The text must have valid value.", nameof(text));

        var buffer = Encoding.UTF8.GetBytes(text);
        var hash = new SHA512CryptoServiceProvider();
        var aesKey = new byte[24];
        Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);

        using (var aes = Aes.Create())
        {
            if (aes == null)
                throw new ArgumentException("Parameter must not be null.", nameof(aes));

            aes.Key = aesKey;

            using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
            using (var resultStream = new MemoryStream())
            {
                using (var aesStream = new CryptoStream(resultStream, encryptor, CryptoStreamMode.Write))
                using (var plainStream = new MemoryStream(buffer))
                {
                    plainStream.CopyTo(aesStream);
                }

                var result = resultStream.ToArray();
                var combined = new byte[aes.IV.Length + result.Length];
                Array.ConstrainedCopy(aes.IV, 0, combined, 0, aes.IV.Length);
                Array.ConstrainedCopy(result, 0, combined, aes.IV.Length, result.Length);

                return Convert.ToBase64String(combined);
            }
        }
    }

    public static string Decrypt(this string encryptedText, string key)
    {
        if (string.IsNullOrEmpty(key))
            throw new ArgumentException("Key must have valid value.", nameof(key));
        if (string.IsNullOrEmpty(encryptedText))
            throw new ArgumentException("The encrypted text must have valid value.", nameof(encryptedText));

        var combined = Convert.FromBase64String(encryptedText);
        var buffer = new byte[combined.Length];
        var hash = new SHA512CryptoServiceProvider();
        var aesKey = new byte[24];
        Buffer.BlockCopy(hash.ComputeHash(Encoding.UTF8.GetBytes(key)), 0, aesKey, 0, 24);

        using (var aes = Aes.Create())
        {
            if (aes == null)
                throw new ArgumentException("Parameter must not be null.", nameof(aes));

            aes.Key = aesKey;

            var iv = new byte[aes.IV.Length];
            var ciphertext = new byte[buffer.Length - iv.Length];

            Array.ConstrainedCopy(combined, 0, iv, 0, iv.Length);
            Array.ConstrainedCopy(combined, iv.Length, ciphertext, 0, ciphertext.Length);

            aes.IV = iv;

            using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
            using (var resultStream = new MemoryStream())
            {
                using (var aesStream = new CryptoStream(resultStream, decryptor, CryptoStreamMode.Write))
                using (var plainStream = new MemoryStream(ciphertext))
                {
                    plainStream.CopyTo(aesStream);
                }

                return Encoding.UTF8.GetString(resultStream.ToArray());
            }
        }
    }

【讨论】:

  • 我喜欢您发布的内容,并且有兴趣使用相同的方法,但有几个问题:combined 在被 base64 编码之前包含什么以及我们为什么要这样做而不是只使用result?
  • 数组combined 包含字节或8 位无符号整数。这完全没问题,具体取决于您想用它做什么。在我的用例中,我需要这些加密数据的有效字符串表示。使用 UTF-8 或类似的编码是行不通的,因为不是每个字节值都有有效的 UTF-8 表示。另一方面,Base64 专门设计用于将二进制数据表示为 ASCII 字符串。如果您有兴趣,请查看Wikipedia。如果您不需要字符串,则可以轻松更改代码以仅返回字节。
  • 产生的密文不是确定性的,相同密钥和明文的不同 Encrypt() 运行产生不同的密文
  • @RichardAnderssen 本例中的组合密文包括 aes.IV 初始化向量,默认情况下是随机生成的值。解密需要这个值加上用户提供的密钥文本(即字符串密钥参数),这就是为什么最终输出会有所不同以及 IV 包含在组合结果中的原因。只要您指定相同的用户提供的密钥,解密仍然会成功。这是故意的。如果你坚持让它具有确定性,你可以每次都提供一个特定的 IV,但这会破坏安全性并使破解密钥变得更容易。
  • @iss0 为什么要生成 192 位(24 字节)散列密钥而不是 256 位(32 字节)?
【解决方案5】:

默认情况下,ASP.NET Core 应用程序启用数据保护系统。除非您想重新配置默认密钥存储位置或密钥的使用寿命,否则您甚至不需要在 StartUp 方法中执行任何操作。在这种情况下,您将在 ConfigureServices 方法中执行以下操作:

services.ConfigureDataProtection(dp =>
    {
        dp.PersistKeysToFileSystem(new DirectoryInfo(@"c:\keys"));
        dp.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
    });

由于数据保护系统默认位于应用程序的服务集合中,因此可以通过依赖注入使其可用。以下是如何将 IDataProtectionProvider 注入控制器,然后使用它在控制器的构造函数中创建 IDataProtector 的实例:

public class HomeController : Controller
{
    IDataProtector _protector;

    public HomeController(IDataProtectionProvider provider)
    {
        _protector = provider.CreateProtector(GetType().FullName);
    }
}

然后您可以调用保护程序来加密内容,如下所示:

public IActionResult Index()
{
    var model = _service.GetAll().Select(c => new ContractViewModel {
        Id = _protector.Protect(c.Id.ToString()),
        Name = c.Name }).ToList();
    return View(model);
}

我希望这会有所帮助:)

【讨论】:

  • 您的方法在上面已经从 Marcus 中提到过,但是您会在那里找到很多评论,说真正的密钥保存在文件系统中,您不能使用相同的加密值在其他环境中用于解密目的
  • @Webezine,这种方法在 linux 部署中有效吗?
  • 据我所知,是的。
  • @Webezine - 如果您想将它们放在文件系统上,则需要使用 linux 样式的路径作为密钥位置。也就是说:将密钥放在文件系统上(作为原始文件)可能不是一个好主意?
  • @Webezine,我发现它在 linux 上不起作用的困难方式......鉴于 .net 核心是跨平台的,因此必须有替代方案。需要探索这些。
【解决方案6】:

您可以使用 System.Security.Cryptography 来做到这一点

string keyString = "encrypt123456789";
var key = Encoding.UTF8.GetBytes(keyString);//16 bit or 32 bit key string

using (var aesAlg = Aes.Create())
{
    using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV))
    {
        using (var msEncrypt = new MemoryStream())
        {
            using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
            using (var swEncrypt = new StreamWriter(csEncrypt))
            {
                swEncrypt.Write(text);
            }

            var iv = aesAlg.IV;

            var decryptedContent = msEncrypt.ToArray();

            var result = new byte[iv.Length + decryptedContent.Length];

            Buffer.BlockCopy(iv, 0, result, 0, iv.Length);
            Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length);

            return Convert.ToBase64String(result);
        }
    }
}

用于解密

var fullCipher = Convert.FromBase64String(cipherText);

var iv = new byte[16];
var cipher = new byte[16];

Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length);
Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length);
var key = Encoding.UTF8.GetBytes(keyString);//same key string

using (var aesAlg = Aes.Create())
{
    using (var decryptor = aesAlg.CreateDecryptor(key, iv))
    {
        string result;
        using (var msDecrypt = new MemoryStream(cipher))
        {
            using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            {
                using (var srDecrypt = new StreamReader(csDecrypt))
                {
                    result = srDecrypt.ReadToEnd();
                }
            }
        }

        return result;
    }
}

【讨论】:

  • 对于解密部分,第二个 Buffer.BlockCopy 应该是: Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, cipher.Length);而不是 iv.Length 第二次。
  • 此解密方法抛出错误 System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed.'我添加了 aesAlg.Padding = PaddingMode.PKCS7;对于加密和解密方法,仍然会发生错误。我该如何解决这个问题?这个例子是什么 x 位加密级别? 128? 256?假设密钥能够保持安全,那么这段代码是否适合在生产环境中使用?
  • 我有点困惑。你有var decryptedContent=msEncrypt.ToArray();,但它是en加密部分而不是de加密,对吧?不应该是var encryptedContent吗?
  • 这是一种从密码文本生成密钥的糟糕方法。它应该涉及某种散列算法,而不仅仅是将文本转储到字节数组中。
【解决方案7】:

对于那些使用@sundarraj 发布的上述解决方案并获得 System.Security.Cryptography.CryptographicException: 'Padding is invalid and cannot be removed。解密时出错使用这些代码行来修复解密:

var cipher = new byte[full.Length - iv.Length];

Buffer.BlockCopy(full, 0, iv, 0, iv.Length);
Buffer.BlockCopy(full, iv.Length, cipher, 0, cipher.Length);

【讨论】:

  • 为什么不直接使用在 sundarraj 的回答前一年发布的 iss0 的回答? isso 的代码做同样的事情,但包括一个基本的密码散列方案并且不会出现这个错误。 sundarray 的答案并没有为之前的答案添加任何内容。
猜你喜欢
  • 2021-03-26
  • 1970-01-01
  • 2022-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多