简体   繁体   English

ProtectedData.Protect间歇性失败

[英]ProtectedData.Protect intermittent failure

I'm writing a password encryption routine. 我正在写一个密码加密例程。 I've written the below app to illustrate my problem. 我写了下面的应用来说明我的问题。 About 20% of the time, this code works as expected. 大约20%的时间,此代码按预期工作。 The rest of the time, the decryption throws a cryptographic exception - "The data is invalid". 其余时间,解密会引发加密异常 - “数据无效”。

I believe the problem is in the encryption portion, because the decryption portion works the same every time. 我认为问题出在加密部分,因为解密部分每次都是一样的。 That is, if the encryption routine yields a value that the decryption routine can decrypt, it can always decrypt it. 也就是说,如果加密例程产生解密例程可以解密的值,则它总是可以解密它。 But if the encryption routine yields a value that chokes the decryption routine, it always chokes. 但是,如果加密例程产生一个阻塞解密例程的值,它总是会窒息。 So the decrypt routine is consistent; 所以解密程序是一致的; the encrypt routine is not. 加密例程不是。

I suspect my use of Unicode encoding is incorrect, but I've tried others with the same result. 我怀疑我对Unicode编码的使用是不正确的,但我尝试过其他具有相同结果的人。

What am I doing wrong? 我究竟做错了什么?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;

namespace DataProtectionTest
{
    public partial class Form1 : Form
    {
        private static readonly byte[] entropy = { 1, 2, 3, 4, 1, 2, 3, 4 };
        private string password;
        public Form1()
        {
            InitializeComponent();
        }

        private void btnEncryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pw = Encoding.Unicode.GetBytes(textBox1.Text);
            Byte[] encryptedPw = ProtectedData.Protect(pw, entropy, DataProtectionScope.LocalMachine);
            password = Encoding.Unicode.GetString(encryptedPw);     
        }

        private void btnDecryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pwBytes = Encoding.Unicode.GetBytes(password);
            try
            {
                Byte[] decryptedPw = ProtectedData.Unprotect(pwBytes, entropy, DataProtectionScope.LocalMachine);
                string pw = Encoding.Unicode.GetString(decryptedPw);
                textBox2.Text = pw;
            }
            catch (CryptographicException ce)
            {
                textBox2.Text = ce.Message;
            }
        }
    }
}

On the advice of a colleague, I opted for Convert.ToBase64String. 根据同事的建议,我选择了Convert.ToBase64String。 Works well. 效果很好。 Corrected program below. 修正了以下程序。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;

namespace DataProtectionTest
{
    public partial class Form1 : Form
    {
        private static readonly byte[] entropy = { 1, 2, 3, 4, 1, 2, 3, 4 };
        private string password;
        public Form1()
        {
            InitializeComponent();
        }

        private void btnEncryptIt_Click(object sender, EventArgs e)
        {
            Byte[] pw = Encoding.Unicode.GetBytes(textBox1.Text);
            Byte[] encryptedPw = ProtectedData.Protect(pw, entropy, DataProtectionScope.LocalMachine);
            //password = Encoding.Unicode.GetString(encryptedPw);       
            password = Convert.ToBase64String(encryptedPw);
        }

        private void btnDecryptIt_Click(object sender, EventArgs e)
        {
            //Byte[] pwBytes = Encoding.Unicode.GetBytes(password);
            Byte[] pwBytes = Convert.FromBase64String(password);
            try
            {
                Byte[] decryptedPw = ProtectedData.Unprotect(pwBytes, entropy, DataProtectionScope.LocalMachine);
                string pw = Encoding.Unicode.GetString(decryptedPw);
                textBox2.Text = pw;
            }
            catch (CryptographicException ce)
            {
                textBox2.Text = ce.Message;
            }
        }
    }
}

The best solution is to convert the byte array to a base 64 string . 最好的解决方案是将字节数组转换为base 64字符串

You can also use Latin-1 aka ISO-8859-1 aka codepage 28591 for this scenario, as it maps values in the range 0-255 unchanged. 您也可以在此方案中使用Latin-1 aka ISO-8859-1 aka代码页28591,因为它将0-255范围内的值映射不变。 The following are interchangeable: 以下是可以互换的:

Encoding.GetEncoding(28591)
Encoding.GetEncoding("Latin1")
Encoding.GetEncoding("iso-8859-1")

With this encoding you will always be able to convert byte[] -> string -> byte[] without loss. 使用此编码,您将始终能够无损地转换byte [] - > string - > byte []。

See this post for a sample that illustrates the use of this encoding. 有关说明此编码使用情况的示例,请参阅此文章

The problem is the conversion to unicode and the end of the encryption method, Encoding.Unicode.GetString works only if the bytes you give it form a valid UTF-16 string. 问题是转换为unicode和加密方法的结束,Encoding.Unicode.GetString只有在你给它的字节形成有效的UTF-16字符串时才有效。

I suspect that sometimes the result of ProtectedData.Protect is not a valid UTF-16 string - so Encoding.Unicode.GetString drops bytes that not make sense out of the returned string resulting in a string that can't be converted back into the encrypted data. 我怀疑有时ProtectedData.Protect的结果不是有效的UTF-16字符串 - 所以Encoding.Unicode.GetString会丢弃返回字符串中没有意义的字节,从而导致无法转换回加密字符串的字符串数据。

This class should help: 这堂课应该有所帮助:

public static class StringEncryptor
{
    private static readonly byte[] key = { 0x45, 0x4E, 0x3A, 0x8C, 0x89, 0x70, 0x37, 0x99, 0x58, 0x31, 0x24, 0x98, 0x3A, 0x87, 0x9B, 0x34 };

    public static string EncryptString(this string sourceString)
    {
        if (string.IsNullOrEmpty(sourceString))
        {
            return string.Empty;
        }

        var base64String = Base64Encode(sourceString);
        var protectedBytes = ProtectedData.Protect(Convert.FromBase64String(base64String), key, DataProtectionScope.CurrentUser);
        return Convert.ToBase64String(protectedBytes);
    }

    public static string DecryptString(this string sourceString)
    {
        if (string.IsNullOrEmpty(sourceString))
        {
            return string.Empty;
        }

        var unprotectedBytes = ProtectedData.Unprotect(Convert.FromBase64String(sourceString), key, DataProtectionScope.CurrentUser);
        var base64String = Convert.ToBase64String(unprotectedBytes);
        return Base64Decode(base64String);
    }

    private static string Base64Encode(string plainText)
    {
        var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
        return Convert.ToBase64String(plainTextBytes);
    }

    private static string Base64Decode(string base64EncodedData)
    {
        var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
        return Encoding.UTF8.GetString(base64EncodedBytes);
    }
}

You should never use any of the System.Text.Encoding classes for cipher text. 永远不要将任何System.Text.Encoding类用于密文。 You will experience intermittent errors. 遇到间歇性错误。 You should use Base64 encoding and the System.Convert class methods. 您应该使用Base64编码和System.Convert类方法。

  1. To obtain an encrypted string from an encrypted byte[] , you should use: 要从加密byte[]获取加密string ,您应该使用:

     Convert.ToBase64String(byte[] bytes) 
  2. To obtain aa raw byte[] from a string to be encrypted, you should use: 要从要加密的string获取原始byte[] ,您应该使用:

     Convert.FromBase64String(string data) 

For additional info, please refer to MS Security guru Shawn Fanning's post. 有关其他信息,请参阅MS安全大师Shawn Fanning的帖子。

I strongly suspect that it's the call to Encoding.Unicode.GetString that's causing the problem. 我强烈怀疑这是导致问题的Encoding.Unicode.GetString的调用。 You need to ensure that the data passed to the Unprotect call is exactly the same as that returned from the Protect call. 您需要确保传递给Unprotect调用的数据与从Protect调用返回的数据完全相同 If you're encoding the binary data as Unicode text as an interim step then you can't guarantee this. 如果您将二进制数据编码为Unicode文本作为临时步骤,那么您无法保证这一点。 Why do you need this step anyway - why not just store the byte[]? 为什么你还需要这一步 - 为什么不只是存储字节[]?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM