代码之家  ›  专栏  ›  技术社区  ›  Callum Watkins

C安全地将secureString转换为utf-8 byte[]。

  •  1
  • Callum Watkins  · 技术社区  · 8 年前

    我想得到一个 SecureString 以A的形式 byte[] 我可以保持gc固定,以utf-8格式编码。我已经成功地完成了这项工作,但是使用了UTF-16(默认编码),但是如果GC没有机会在某个地方创建数据的托管副本(数据需要保持安全),我就不知道如何进行编码转换。

    这是我到目前为止所拥有的(上下文:计算SecureString散列的算法)

    public static byte[] Hash(this SecureString secureString, HashAlgorithm hashAlgorithm)
    {
      IntPtr bstr = Marshal.SecureStringToBSTR(secureString);
      int length = Marshal.ReadInt32(bstr, -4);
      var utf16Bytes = new byte[length];
      GCHandle utf16BytesPin = GCHandle.Alloc(utf16Bytes, GCHandleType.Pinned);
      byte[] utf8Bytes = null;
    
      try
      {
        Marshal.Copy(bstr, utf16Bytes, 0, length);
        Marshal.ZeroFreeBSTR(bstr);
        // At this point I have the UTF-16 byte[] perfectly.
        // The next line works at converting the encoding, but it does nothing
        // to protect the data from being spread throughout memory.
        utf8Bytes = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, utf16Bytes);
        return hashAlgorithm.ComputeHash(utf8Bytes);
      }
      finally
      {
        if (utf8Bytes != null)
        {
          for (var i = 0; i < utf8Bytes.Length; i++)
          { 
            utf8Bytes[i] = 0;
          }
        }
        for (var i = 0; i < utf16Bytes.Length; i++)
        { 
          utf16Bytes[i] = 0;
        }
        utf16BytesPin.Free();
      }
    }
    

    做这个转换的最好方法是什么?我是在正确的地方做这个转换,还是应该早点做?通过完全跳过UTF-16字节[]步骤,这会提高内存效率吗?

    2 回复  |  直到 8 年前
        1
  •  1
  •   Callum Watkins    8 年前

    我已经找到了一种我想要的方法。我这里的代码还没有完成(在失败的情况下需要更好的异常处理和内存管理),但这里是:

    [DllImport("kernel32.dll")]
    static extern void RtlZeroMemory(IntPtr dst, int length);
    
    public unsafe static byte[] HashNew(this SecureString secureString, HashAlgorithm hashAlgorithm)
    {
      IntPtr bstr = Marshal.SecureStringToBSTR(secureString);
      int maxUtf8BytesCount = Encoding.UTF8.GetMaxByteCount(secureString.Length);
      IntPtr utf8Buffer = Marshal.AllocHGlobal(maxUtf8BytesCount);
    
      // Here's the magic:
      char* utf16CharsPtr = (char*)bstr.ToPointer();
      byte* utf8BytesPtr  = (byte*)utf8Buffer.ToPointer();
      int utf8BytesCount = Encoding.UTF8.GetBytes(utf16CharsPtr, secureString.Length, utf8BytesPtr, maxUtf8BytesCount);
    
      Marshal.ZeroFreeBSTR(bstr);
      var utf8Bytes = new byte[utf8BytesCount];
      GCHandle utf8BytesPin = GCHandle.Alloc(utf8Bytes, GCHandleType.Pinned);
      Marshal.Copy(utf8Buffer, utf8Bytes, 0, utf8BytesCount);
      RtlZeroMemory(utf8Buffer, utf8BytesCount);
      Marshal.FreeHGlobal(utf8Buffer);
      try
      {
        return hashAlgorithm.ComputeHash(utf8Bytes);
      }
      finally
      {
        for (int i = 0; i < utf8Bytes.Length; i++)
        {
          utf8Bytes[i] = 0;
        }
        utf8BytesPin.Free();
      }
    }
    

    它依赖于获取指向原始utf-16字符串和utf-8缓冲区的指针,然后使用 Encoding.UTF8.GetBytes(Char*, Int32, Byte*, Int32) 将转换保持在非托管内存中。

        2
  •  0
  •   Leonardo Trocato    8 年前

    你考虑过打电话吗 GC.Collect() 获取哈希之后?

    根据 MSDN on GC.Collect :

    强制立即收集所有代的垃圾。 使用此方法尝试回收所有不可访问的内存。它执行所有代的阻塞垃圾收集。

    所有对象,不管它们在内存中存在多长时间,都会被考虑进行收集;但是,在托管代码中引用的对象不会被收集。使用此方法强制系统尝试回收最大可用内存量。

    根据我在代码中看到的,它不应该保留对转换中使用的对象的任何引用。所有这些都应该由GC收集和处理。

    推荐文章