鑑於在這個問題上似乎沒有太多的活動,我會假設大多數人沒有必要/想要以前看過,或者沒有想過這是值得的時間。因此,儘管我對Go的內在運作一無所知,我仍然會將自己的發現作爲答案發布。
我應該在這個答案的前面加一個免責聲明,因爲Go是一個垃圾收集語言,我不知道它是如何在內部工作的,下面的信息實際上可能並不保證任何內存實際上被清零爲零,不要阻止我嘗試;畢竟,在我看來,內存中的純文本密碼越少越好。
考慮到這一點,這是我發現的所有工作(據我所知)與libsodium
;至少到目前爲止,它還沒有讓我的任何程序崩潰。
首先,正如你可能已經在圍棋知道string
s爲不可變的,所以在技術上它們的價值不應該被改變,但是如果我們通過CGO使用unsafe.Pointer
在圍棋中string
或C,我們實際上可以覆蓋存儲在string
值中的數據;我們不能保證內存中任何其他地方沒有任何其他數據副本。
因爲這個原因,我讓我的密碼相關函數專門處理[]byte
變量,以減少在內存中複製的可能的純文本密碼的數量。
我也返回獲取傳遞到所有的密碼功能的明文密碼的[]byte
參考,因爲轉換string
爲[]byte
將分配新的內存和複製的內容了。這樣,至少如果您將string
轉換爲[]byte
而不先將其分配給變量,則在函數調用完成後仍然可以訪問新的[]byte
,並且該內存也爲零。
以下是我想出的主旨。您可以填寫空格,包括libsodium
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]
然後這個MemZero*
功能後,被稱爲:
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
所以它看起來像一個成功的,但是因爲我們可以不保證內存中其他地方不存在明文密碼的副本,我認爲儘可能使用它去。
下面的代碼只是將一個unsafe.Pointer
與byte
的數量傳遞給C中的sodium_memzero
函數來實現此目的。所以內存的實際調零保留爲libsodium
。
我很抱歉,如果我留下任何輸入錯誤或代碼中的任何內容不起作用,但我不想粘貼太多,只是相關的部分。
例如,如果您真的需要,也可以使用像mlock
這樣的函數,但由於此問題的重點在於調零string
,所以我將在此處顯示。
package sodium
// Various imports, other functions and <sodium.h> here...
func init() {
if err := sodium.Init(); err != nil {
log.Fatalf("sodium: %s", err)
}
}
func PasswordHash(pwd []byte, opslimit, memlimit int) ([]byte, []byte, error) {
pwdPtr := unsafe.Pointer(&pwd[0])
hashPtr := unsafe.Pointer(&make([]byte, C.crypto_pwhash_STRBYTES)[0])
res := C.crypto_pwhash_str(
(*C.char)(hashPtr),
(*C.char)(pwdPtr),
C.ulonglong(len(pwd)),
C.ulonglong(opslimit),
C.size_t(memlimit),
)
if res != 0 {
return nil, pwd, fmt.Errorf("sodium: passwordhash: out of memory")
}
return C.GoBytes(hashPtr, C.crypto_pwhash_STRBYTES), pwd, nil
}
func MemZero(p unsafe.Pointer, size int) {
if p != nil && size > 0 {
C.sodium_memzero(p, C.size_t(size))
}
}
func MemZeroBytes(bytes []byte) {
if size := len(bytes); size > 0 {
MemZero(unsafe.Pointer(&bytes[0]), size)
}
}
func MemZeroStr(str *string) {
if size := len(*str); size > 0 {
MemZero(unsafe.Pointer(str), size)
}
}
然後用這一切:
package main
// Imports etc here...
func main() {
// Unfortunately there is no guarantee that this won't be
// stored elsewhere in memory, but we will try to remove it anyway
pwd := "Correct Horse Battery Staple"
// I convert the pwd string to a []byte in place here
// Because of this I have no reference to the new memory, with yet
// another copy of the plain password hanging around
// The function always returns the new []byte as the second value
// though, so we can still zero it anyway
hash, pwdBytes, err := sodium.PasswordHash([]byte(pwd), 6, 134217728)
// Byte slice and string before MemZero* functions
fmt.Println("pwd :", pwd)
fmt.Println("pwdBytes:", pwdBytes)
// No need to keep a plain-text password in memory any longer than required
sodium.MemZeroStr(&pwd)
sodium.MemZeroBytes(pwdBytes)
if err != nil {
log.Fatal(err)
}
// Byte slice and string after MemZero* functions
fmt.Println("pwd :", pwd)
fmt.Println("pwdBytes:", pwdBytes)
// We've done our best to make sure we only have the hash in memory now
fmt.Println("Hash:", string(hash))
}
當你說調零內存你的意思是調用realloc並給它一個0的大小?因爲如果你的意思是把塊中的所有位設置爲0,我不確定這個點是什麼。我的直覺說你應該依靠GC,我認爲你可以安排你的代碼,使地圖很快地離開作用域(比如將字符串傳入你在goroutine中調用的函數並移動一個函數),所以我沒有看到內存被認爲是真正的問題。我的意思是,這裏的記憶關注是什麼?密碼不是很重要。 – evanmcdonnal
如果一個密碼通過一個HTTP POST進入,那麼將一個特定的字符串置零是沒有太大意義的,因爲你沒有直接從線上讀取它,也不知道在內存中有多少個副本。 – JimB
@JimB這是非常真實的,也許我應該更好地表達我的問題,因爲我似乎已經給出了我想在我的Web應用程序中執行此操作的印象。真的,這個web應用程序的例子只是我關於這個主題的想法的觸發器,而我更關心的是,將Go的字符串內存調零實際上是否可行/「安全」足以在Go中執行,需要。 –