【问题标题】:SecureString to Byte[] C#SecureString 到字节 [] C#
【发布时间】:2013-08-25 21:32:32
【问题描述】:

我如何获得与SecureString 等效的byte[](我从PasswordBox 获得)?

我的目标是使用CryptoStream 将这些字节写入文件,并且该类的Write 方法采用byte[] 输入,所以我想将SecureString 转换为byte[] 所以我可以使用CryptoStream

编辑:我不想使用string,因为它违背了拥有SecureString 的意义

【问题讨论】:

    标签: c# wpf encryption securestring


    【解决方案1】:

    根据 http://www.microsoft.com/indonesia/msdn/credmgmt.aspx,您可以将其编组为常用 C# 字符串,然后将其转换为字节数组:

    static string SecureStringToString( SecureString value )
    {
      string s ;
      IntPtr p = Marshal.SecureStringToBSTR( value );
      try
      {
        s = Marshal.PtrToStringBSTR( p ) ;
      }
      finally
      {
        Marshal.FreeBSTR( p ) ;
      }
      return s ;
    }
    

    或者根据这个答案How to convert SecureString to System.String?,您可以在IntPtr 上使用Marshal.ReadByteMarshal.ReadInt16 来获得您需要的东西。

    【讨论】:

    • 虽然这确实违背了SecureString 的目的,即允许在内存中安全存储敏感字符串数据,例如密码。
    • 我不想使用string,所以有没有其他方法可以保证密码安全并完成同样的事情?
    【解决方案2】:

    我从original answer修改为处理unicode

    IntPtr unmanagedBytes = Marshal.SecureStringToGlobalAllocUnicode(password);
    byte[] bValue = null;
    try
    {
        byte* byteArray = (byte*)unmanagedBytes.GetPointer();
    
        // Find the end of the string
        byte* pEnd = byteArray;
        char c='\0';
        do
        {
            byte b1=*pEnd++;
            byte b2=*pEnd++;
            c = '\0';
            c= (char)(b1 << 8);                 
            c += (char)b2;
        }while (c != '\0');
    
        // Length is effectively the difference here (note we're 2 past end) 
        int length = (int)((pEnd - byteArray) - 2);
        bValue = new byte[length];
        for (int i=0;i<length;++i)
        {
            // Work with data in byte array as necessary, via pointers, here
            bValue[i] = *(byteArray + i);
        }
    }
    finally
    {
        // This will completely remove the data from memory
        Marshal.ZeroFreeGlobalAllocUnicode(unmanagedBytes);
    }
    

    【讨论】:

    • 与 ANSI C 字符串不同,BSTR 可以包含空字符,因此您对空值的扫描是无效的。只需使用源 SecureString 的 Length 成员(乘以 2 即可获得字节数)。
    【解决方案3】:

    假设您想使用字节数组并在完成后立即摆脱它,您应该封装整个操作,以便它自己清理:

    public static T Process<T>(this SecureString src, Func<byte[], T> func)
    {
        IntPtr bstr = IntPtr.Zero;
        byte[] workArray = null;
        GCHandle? handle = null; // Hats off to Tobias Bauer
        try
        {
            /*** PLAINTEXT EXPOSURE BEGINS HERE ***/
            bstr = Marshal.SecureStringToBSTR(src);
            unsafe
            {
                byte* bstrBytes = (byte*)bstr;
                workArray = new byte[src.Length * 2];
                handle = GCHandle.Alloc(workArray, GCHandleType.Pinned); // Hats off to Tobias Bauer
                for (int i = 0; i < workArray.Length; i++)
                    workArray[i] = *bstrBytes++;
            }
    
            return func(workArray);
        }
        finally
        {
            if (workArray != null)
                for (int i = 0; i < workArray.Length; i++)
                    workArray[i] = 0;
            handle.Free();
            if (bstr != IntPtr.Zero)
                Marshal.ZeroFreeBSTR(bstr);
            /*** PLAINTEXT EXPOSURE ENDS HERE ***/
        }
    }
    

    下面是用例的外观:

    private byte[] GetHash(SecureString password)
    {
        using (var h = new SHA256Cng()) // or your hash of choice
        {
            return password.Process(h.ComputeHash);
        }
    }
    

    没有混乱,没有大惊小怪,没有明文留在内存中。

    请记住,传递给 func() 的字节数组包含明文的原始 Unicode 呈现,这对于大多数加密应用程序来说应该不是问题。

    【讨论】:

    • Eric - 我认为你的 workArray 有点问题。很高兴你将它归零,但如果垃圾收集器决定移动它怎么办?那么您将暴露的数据作为内存中的“垃圾”。在将敏感数据放入之前,您需要将字节数组固定到内存中
    • 很好,@ArielB。我添加了 GCHandle.Alloc() 和 handle.Free() 来固定 workArray,直到它被清除。迟到总比不到好。谢谢!
    • 根据@tobias-bauer,我已经更新了这个版本(吞下的例外除外)。好球,托拜厄斯!
    【解决方案4】:

    由于我没有足够的声望点来评论 Eric 的回答,所以我必须发表这篇文章。

    在我看来,Eric 的代码存在问题,因为 GCHandle.Alloc(workArray, ...) 执行不正确。它不应该固定 workArraynull 值,而是将创建的实际数组再往下几行。

    此外,handle.Free() 可以抛出 InvalidOperationException,因此我建议将其放在 Marshal.ZeroFreeBSTR(...) 之后,以至少使二进制字符串 bstr 指向归零。

    修改后的代码是这样的:

    public static T Process<T>(this SecureString src, Func<byte[], T> func)
    {
        IntPtr bstr = IntPtr.Zero;
        byte[] workArray = null;
        GCHandle? handle = null; // Change no. 1
        try
        {
            /*** PLAINTEXT EXPOSURE BEGINS HERE ***/
            bstr = Marshal.SecureStringToBSTR(src);
            unsafe
            {
                byte* bstrBytes = (byte*)bstr;
                workArray = new byte[src.Length * 2];
                handle = GCHandle.Alloc(workArray, GCHandleType.Pinned); // Change no. 2
    
                for (int i = 0; i < workArray.Length; i++)
                    workArray[i] = *bstrBytes++;
            }
    
            return func(workArray);
        }
        finally
        {
            if (workArray != null)
                for (int i = 0; i < workArray.Length; i++)
                    workArray[i] = 0;
            
            if (bstr != IntPtr.Zero)
                Marshal.ZeroFreeBSTR(bstr);
    
            handle?.Free(); // Change no. 3 (Edit: no try-catch but after Marshal.ZeroFreeBSTR)
    
            /*** PLAINTEXT EXPOSURE ENDS HERE ***/
        }
    }
    

    这些修改确保正确的 byte 数组被固定在内存中(更改编号 1 和 2)。此外,如果handle?.Free() 抛出异常,它们会避免将未加密的二进制字符串加载到内存中(更改编号 3)。

    【讨论】:

    • GCHandle.Alloc(...) 很好。看看当您实际上没有针对它运行单元测试时会发生什么? ? 我会更新我的答案以匹配。然而,你永远不会想要接受异常,除非你真的要对失败做点什么。
    • @EricLloyd 感谢您更新您的答案!关于异常吞咽,我还有一件事:我完全同意你的观点。但是,我建议将handle?.Free() after Marshal.ZeroFreeBSTR(...) 放在后面的函数中,因为它不会在失败时抛出异常,并确保bstr 数组被清零。我会相应地调整我的答案。
    【解决方案5】:

    这个 100% 托管的代码似乎对我有用:

    var pUnicodeBytes = Marshal.SecureStringToGlobalAllocUnicode(secureString);
    try
    {
        byte[] unicodeBytes = new byte[secureString.Length * 2];
    
        for( var idx = 0; idx < unicodeBytes.Length; ++idx )
        {
            bytes[idx] = Marshal.ReadByte(pUnicodeBytes, idx);
        }
    
        return bytes;
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(pUnicodeBytes);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-01
      • 2010-11-22
      • 1970-01-01
      • 2021-01-03
      相关资源
      最近更新 更多