为什么只有一半的加密字符串无法正确解密?

Why is only one half of my encrypted string not decrypting properly?

似乎无法弄清楚这一点...我正在使用 DESCryptoServiceProvider 进行快速的双向加密(与安全无关,并且安全不是此问题的目的)。

总之这很奇怪,因为进入然后返回的字符串只能正确解密一半的字符串。我似乎没有注意到这个错误,所以也许有人会从中得到一些乐趣...

我将两个字符串合并为冒号作为分隔符,因此 'abc12345:xyz56789' 是输入。然后注意在输出中只有字符串的第一部分被搞砸了,而不是第二部分。我希望如果我完全错了,那么整个事情就无法正确解密。

这是全部代码:

class Program
{
    static void Main(string[] args)
    {
        var userId = "abc12345";
        var appId = "xyz56789";

        Console.WriteLine($"UserId: {userId}, AppId: {appId}");

        var code = QuickEncode(userId, appId);

        Console.WriteLine(code);

        var result = QuickDecode(code);

        var uId = result.Item1;
        var aId = result.Item2;

        Console.WriteLine($"UserId: {uId}, AppId: {aId}");

        Console.ReadKey();
    }

    private static string QuickEncode(string userId, string appId)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;

        ICryptoTransform encryptor = des.CreateEncryptor();

        var encryptMe = $"{userId}:{appId}";

        Console.WriteLine($"Input String: {encryptMe}");

        byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes(encryptMe);

        byte[] enc = encryptor.TransformFinalBlock(stringBytes, 0, stringBytes.Length);

        var encryptedBytesString = Convert.ToBase64String(enc);

        return encryptedBytesString;
    }

    private static Tuple<string, string> QuickDecode(string code)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;

        ICryptoTransform decryptor = des.CreateDecryptor();

        var codeBytes = Convert.FromBase64String(code);

        byte[] originalAgain = decryptor.TransformFinalBlock(codeBytes, 0, codeBytes.Length);

        var decryptMe = System.Text.Encoding.UTF8.GetString(originalAgain);

        Console.WriteLine($"Output String: {decryptMe}");

        var ids = decryptMe.Split(':');

        return new Tuple<string, string>(ids[0], ids[1]);
    }

    public static string ByteArrayToString(byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);
        foreach (byte b in ba)
            hex.AppendFormat("{0:x2}", b);
        return hex.ToString();
    }

    public static byte[] StringToByteArray(String hex)
    {
        int NumberChars = hex.Length;
        byte[] bytes = new byte[NumberChars / 2];
        for (int i = 0; i < NumberChars; i += 2)
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        return bytes;
    }
}

您必须将 initialization vector (IV) 设置为相同的加密值和解密值。因为新的 IV 是为每个新的 DESCryptoServiceProvider 实例自动生成的,所以你的 IV 不同并且解密不成功。

一半消息被正确解密的原因是使用了 CBC mode (which is default mode), which has one really nasty property, that only first block of encrypted message actually depends on value of IV, so potential attacker can decode all message, except first block, without knowing correct IV (of course, correct Key is still needed). So it is not recommended to use this mode. See Block cipher mode of operation 了解更多信息。

所以解决方案很简单 - 将用于加密的 IV 存储在某处并使用相同的 IV 进行解密。如果可能,也使用另一种密码模式。像这样的东西:

using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        var userId = "abc12345";
        var appId = "xyz56789";

        Console.WriteLine($"UserId: {userId}, AppId: {appId}");

        byte[] IV;
        var code = QuickEncode(userId, appId, out IV);

        Console.WriteLine(code);

        var result = QuickDecode(code, IV);

        var uId = result.Item1;
        var aId = result.Item2;

        Console.WriteLine($"UserId: {uId}, AppId: {aId}");

        Console.ReadKey();
    }

    private static string QuickEncode(string userId, string appId, out byte[] IV)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;
        des.GenerateIV();
        IV = des.IV;

        ICryptoTransform encryptor = des.CreateEncryptor();

        var encryptMe = $"{userId}:{appId}";

        Console.WriteLine($"Input String: {encryptMe}");

        byte[] stringBytes = System.Text.Encoding.UTF8.GetBytes(encryptMe);

        byte[] enc = encryptor.TransformFinalBlock(stringBytes, 0, stringBytes.Length);

        var encryptedBytesString = Convert.ToBase64String(enc);

        return encryptedBytesString;
    }

    private static Tuple<string, string> QuickDecode(string code, byte[] IV)
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();

        var desKey = StringToByteArray("437459133faf42cb");

        des.Key = desKey;
        des.IV = IV;

        ICryptoTransform decryptor = des.CreateDecryptor();

        var codeBytes = Convert.FromBase64String(code);

        byte[] originalAgain = decryptor.TransformFinalBlock(codeBytes, 0, codeBytes.Length);

        var decryptMe = System.Text.Encoding.UTF8.GetString(originalAgain);

        Console.WriteLine($"Output String: {decryptMe}");

        var ids = decryptMe.Split(':');

        return new Tuple<string, string>(ids[0], ids[1]);
    }

    public static string ByteArrayToString(byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);
        foreach (byte b in ba)
            hex.AppendFormat("{0:x2}", b);
        return hex.ToString();
    }

    public static byte[] StringToByteArray(String hex)
    {
        int NumberChars = hex.Length;
        byte[] bytes = new byte[NumberChars / 2];
        for (int i = 0; i < NumberChars; i += 2)
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        return bytes;
    }
}