String 是否可以将Golang字符串';“记忆”;“安全”吗;?

String 是否可以将Golang字符串';“记忆”;“安全”吗;?,string,security,memory,go,cgo,String,Security,Memory,Go,Cgo,最近,我在一个项目中使用cgo设置了libnadium,以便使用crypto\u pwhash\u str和crypto\u pwhash\u str\u verify函数 这一切都进行得非常顺利,我现在有一个小函数集合,它以纯文本密码的形式接收一个[]字节,然后对它进行哈希运算,或者将它与另一个[]字节进行比较以验证它 我之所以使用[]字节而不是字符串,是因为,从我到目前为止对Go的了解来看,我至少可以循环使用纯文本密码并将所有字节归零,甚至可以将指针传递到libnaid的钠memzero函数

最近,我在一个项目中使用cgo设置了
libnadium
,以便使用
crypto\u pwhash\u str
crypto\u pwhash\u str\u verify
函数

这一切都进行得非常顺利,我现在有一个小函数集合,它以纯文本密码的形式接收一个
[]字节
,然后对它进行哈希运算,或者将它与另一个
[]字节
进行比较以验证它

我之所以使用
[]字节
而不是
字符串
,是因为,从我到目前为止对Go的了解来看,我至少可以循环使用纯文本密码并将所有字节归零,甚至可以将指针传递到
libnaid
钠memzero
函数,为了不让它在内存中停留的时间超过它需要的时间

这对于我能够直接以字节形式读取输入的应用程序来说很好,但我现在正尝试在一个小型web应用程序中使用它,在这个应用程序中,我需要使用
POST
方法从表单读取密码

从Go源代码和文档中可以看到,在请求处理程序中使用
r.ParseForm
将所有表单值解析为
string
s的
map

问题是,因为Go中的
string
s是不可变的,所以我认为我无法将表单中
POST
ed的密码的内存归零;至少,只使用Go

因此,似乎我唯一(简单)的选择是将
不安全的.Pointer
与字节数一起传递到C中的函数中,并让C为我将内存归零(例如,将其传递到前面提到的
Na钠memzero
函数)

我已经尝试过这个方法,毫不奇怪它当然会起作用,但是我在Go中留下了一个不安全的
字符串
,如果在
fmt.Println
这样的函数中使用它,程序就会崩溃

我的问题如下:

  • 我是否应该接受密码将被
    POST
    ed并解析为字符串,我不应该弄乱它,而只是等待GC启动?(不理想)
  • 如果代码中明确说明字符串变量不应再次使用,那么使用cgo将
    字符串的内存归零是否可行
  • 使用cgo将
    字符串
    的内存归零是否会导致GC崩溃
  • 是否值得为
    http.Request
    编写一种修饰符,它添加了一个函数,将表单值直接解析为
    []字节
    ,这样我就可以完全控制它们到达时的值

编辑:为了澄清这一点,web应用程序和表单
POST
只是一个简单的例子,我可能仅仅通过使用Go的标准库以
字符串的形式获得敏感数据。我更感兴趣的是我所有的问题是否可能/值得在某些情况下,尽快清理内存中的数据更是一个安全问题。

鉴于在这个问题上似乎没有太多活动,我只想假设大多数人以前不需要/不想研究这个问题,或者觉得不值得花时间。因此,尽管我对Go的内部工作机制一无所知,但我还是将自己的发现作为答案发布

在回答这个问题之前,我应该先声明,因为Go是一种垃圾收集语言,我不知道它在内部是如何工作的,下面的信息可能根本不能保证任何内存被清除为零,但这不会阻止我尝试;毕竟,在我看来,内存中的纯文本密码越少越好

考虑到这一点,这就是我发现的与
libnail
一起工作的一切(据我所知);到目前为止,至少我的任何程序都没有崩溃

首先,正如您可能已经知道的那样,Go中的
string
s是不可变的,因此从技术上讲,它们的值不应该更改,但是如果我们使用
不安全的指针,通过Cgo将
指向Go中或C中的
string
字符串,我们实际上可以覆盖存储在
string
值中的数据;我们不能保证在内存中的任何地方都没有其他数据拷贝

出于这个原因,我让我的密码相关函数专门处理
[]字节
变量,以减少内存中可能复制的纯文本密码的数量

我还返回传递到所有密码函数中的纯文本密码的
[]byte
引用,因为将
字符串
转换为
[]byte
将分配新内存并复制内容。这样,至少如果您将
字符串
就地转换为
[]字节
,而不首先将其分配给变量,那么您仍然可以在函数调用完成后访问新的
[]字节
,并将内存归零

下面是我想到的要点。您可以填空,包括
libnail
C库,并编译它以自己查看结果

对我来说,它在调用
MemZero*
函数之前输出:

pwd     : Correct Horse Battery Staple
pwdBytes: [67 111 114 114 101 99 116 32 72 111 114 115 101 32 66 97 116 116 101 114 121 32 83 116 97 112 108 101]
pwd     :
pwdBytes: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Hash: $argon2i$v=19$m=131072,t=6,p=1$N05osI8nuTjftzfAYBIcbA$3yb92yt9S9dRmPtlSV/J8jY4DG3reqm+2eV+fi54Its
然后调用
MemZero*
函数之后:

pwd     : Correct Horse Battery Staple
pwdBytes: [67 111 114 114 101 99 116 32 72 111 114 115 101 32 66 97 116 116 101 114 121 32 83 116 97 112 108 101]
pwd     :
pwdBytes: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
Hash: $argon2i$v=19$m=131072,t=6,p=1$N05osI8nuTjftzfAYBIcbA$3yb92yt9S9dRmPtlSV/J8jY4DG3reqm+2eV+fi54Its
因此,它看起来很成功,但由于我们不能保证内存中其他地方没有明文密码的副本,我认为这是我们所能做到的

下面的代码只是将一个带有
字节数的
不安全指针
传递到C中的
Na钠memzero
函数来实现这一点。因此,内存的实际归零取决于
libnail

如果我在代码中留下任何拼写错误或任何不起作用的东西,我表示歉意,但我不想粘贴太多,只想重新发布