URL 安全 C# AES 加密

joe*_*our 0 c# encryption aes .net-core

我在创建在 URL 中传递的加密字符串时遇到问题。我在解密后得到不正确的字符或错误。“输入数据不是一个完整的块”

这是我的类来加密和解密我的字符串:

    public static class StringCipher
    {
        public static string Encrypt(string s)
        {
            return AesProvider(s, (aes, raw) =>
            {
                using var ms = new MemoryStream();
                using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(raw, 0, raw.Length);
                }

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

        public static string Decrypt(string s)
        {
            return AesProvider(s, (aes, raw) =>
            {
                using var ms = new MemoryStream();
                using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(raw, 0, raw.Length);
                }
                return Encoding.UTF8.GetString(ms.ToArray());
            });

        }
        
        private static string AesProvider(string s, Func<Aes, byte[], string> fn)
        {
            var raw = Encoding.UTF8.GetBytes(s);
            using var aes = Aes.Create();

            aes.Padding = PaddingMode.Zeros;
            aes.KeySize = 128;                // in bits
            aes.Key     = new byte[128 / 8];  // 16 bytes for 128 bit encryption
            aes.IV      = new byte[128 / 8];  // AES needs a 16-byte IV
            aes.Mode    = CipherMode.CBC;

            return fn.Invoke(aes, raw);
        }
    }
Run Code Online (Sandbox Code Playgroud)

示例用法如下:

var encrypted = StringCipher.Encrypt("my payload");

Run Code Online (Sandbox Code Playgroud)

然后我使用Base64UrlEncoder我理解的 URL 安全的 base64 编码。

var url = $"https://example.com?code={Base64UrlEncoder.Encode(encrypted)}"
Run Code Online (Sandbox Code Playgroud)

当我想解密它时,我尝试执行以下操作:

// https://example.com?code=a5e52f...
var decoded = Base64UrlEncoder.Decode(code);
var plainText = StringCipher.Decrypt(decoded); // expect "my payload"
Run Code Online (Sandbox Code Playgroud)

我知道我一定犯了一个愚蠢的错误。

问题:

  1. 为什么这个实现不能正常工作?
  2. 有更好的方法来实现这一目标吗?

Eti*_*tel 5

简而言之:因为并非每个字节序列都是有效的 UTF-8 字符序列,并且由于 \xc2\xa0Encoding.UTF8默认情况下只是忽略无效序列,因此当您使用 将这些字节转换为字符串时GetString,最终会丢失一些数据。

\n

使用 Base64 是正确的方法,但你做错了:你想将字节数组传递给Base64UrlEncoder.Encode,而不是字符串。不做这GetString一步,直接把结果传过去ms.ToArray()。Base64 的全部意义是将任意二进制数据转换为文本,不需要中间转换为字符串。

\n

同样,解密时,您应该Base64UrlEncoder.DecodeBytes将该字符串转换为字节,然后解密这些字节。

\n

最终结果如下所示。首先,我修改了AesProvider方法以Aes直接返回对象:

\n
private static Aes CreateProvider()\n{\n    var aes = Aes.Create();\n\n    // etc.\n\n    return aes;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后,这里是Encrypt

\n
public static string Encrypt(string s)\n{\n    using var aes = CreateProvider();\n    \n    using var ms = new MemoryStream();\n    using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))\n    {\n        var raw = Encoding.UTF8.GetBytes(s);\n        cs.Write(raw, 0, raw.Length);\n    }\n\n    return Base64UrlEncoder.Encode(ms.ToArray());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是解密:

\n
public static string Decrypt(string s)\n{\n    using var aes = CreateProvider();\n\n    using var ms = new MemoryStream();\n    using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))\n    {\n        var raw = Base64UrlEncoder.DecodeBytes(s);\n        cs.Write(raw, 0, raw.Length);\n    }\n    return Encoding.UTF8.GetString(ms.ToArray());\n}\n
Run Code Online (Sandbox Code Playgroud)\n