2009-02-11 14 views
3

是否有可能在Delphi中「擦除」字符串?讓我解釋一下:如何在Delphi中增加內存安全性?

我正在編寫一個應用程序,它將包含一個DLL來授權用戶。它會將加密文件讀取到XML DOM中,使用該信息,然後釋放DOM。

很明顯,未加密的XML仍然位於DLL的內存中,因此容易受到檢查。現在,我不打算過分保護這一點 - 用戶可以創建另一個DLL - 但我想採取一個基本步驟防止用戶名在內存中存放很長時間。但是,我不認爲我可以輕鬆地擦拭記憶因爲參考。如果我遍歷我的DOM(這是一個TNativeXML類)並查找每個字符串實例,然後將它變成類似「aaaaa」的東西,那麼它是否實際上不會將新字符串指針分配給DOM引用,然後將舊字符串保留有在內存中等待重新分配?有沒有辦法確保我殺死唯一的原始副本?

或者在D2007中有辦法告訴它從堆中清除所有未使用的內存?所以我可以釋放DOM,然後告訴它擦除。

或者我應該繼續完成下一個任務,並忘記這一點,因爲它不值得打擾。

回答

7

我不認爲值得打擾,因爲如果用戶可以使用DLL讀取進程的內存,同一用戶也可以在任何給定的時間點停止執行。在擦除內存之前停止執行仍然會讓用戶完全訪問未加密的數據。

國際海事組織的任何用戶足夠感興趣,並能夠做你所描述的將不會嚴重不方便你的DLL擦拭內存。

0

凌亂,但你可以記下你在堆滿敏感數據時使用的堆大小,然後當這個堆被釋放時,做一個GetMem來爲你分配一個大塊(比如說)200那個%。做一個填充該塊,並假設任何碎片都是無關緊要的,對檢查者來說很有用。 Bri

+0

我想過,但與內存管理器是更聰明,並從不同大小的塊分配,一個16字節的字符串是可能來從完全不同的子堆到12,000字節塊。 – mj2008 2009-02-11 11:30:22

1

如何將文件解密爲流,使用SAX處理器而不是XML DOM來進行驗證,然後在釋放之前覆蓋解密流?

+0

我已經思考了,但我也需要能夠在DLL的某些版本中更新XML,因此DOM使事情變得更簡單。 – mj2008 2009-02-11 11:31:05

+0

您可以使用SAX更新XML。 – 2009-02-11 12:19:31

+0

@Bruce McGee:如果您正在考慮一個TMemoryStream,那麼內存重新分配不能在原地執行,那麼他們不會將部分解密數據留在某個地方,而無需知道它們嗎? – mghie 2009-02-11 16:16:05

0

如何將密碼保存爲XML中的哈希值並通過比較輸入密碼的哈希和XML中的哈希密碼進行驗證。

編輯:您可以保留所有敏感數據只在最後可能的時刻加密和解密。

+0

按照您的建議,密碼被哈希。只有用戶名和訪問級別是「可見的」,但即使這些也是黑客的線索。 – mj2008 2009-02-11 11:52:16

+0

保持這些值的加密和解密及時或轉到下一個任務。 – 2009-02-11 12:13:07

2

DLL沒有擁有分配的內存,進程會這樣做。一旦進程終止,由特定進程分配的內存將被丟棄,無論DLL是否掛起(因爲它正在被另一進程使用)。

4

兩個關於這個基本點:

首先,這是那些領域之一,「如果你要問,你可能不應該這樣做。」請不要採取錯誤的方式;我的意思是不尊重你的編程技巧。這只是寫作安全,密碼強大的軟件,無論你是專家還是你不是。非常相似,知道「一點點空手道」比完全不知道空手道要危險得多。在Delphi中有許多第三方工具可用於編寫安全軟件,並提供專家支持;我強烈鼓勵沒有深入瞭解Windows中加密服務的人員,密碼學的數學基礎以及打敗旁道攻擊的經驗來使用它們,而不是試圖「自己推出」。

要回答您的具體問題:Windows API有許多有用的功能,例如CryptProtectMemory。但是,如果你加密你的內存,但是會在系統的其他地方出現漏洞,或者暴露一個旁道,這會帶來虛假的安全感。它可能就像在你的門上打開一個鎖,但讓窗戶打開。

1

如果您在完全調試模式下使用FastMM內存管理器,則可以強制它在釋放內存時覆蓋內存。

通常情況下,該行爲用於檢測野指針,但它也可以用於你想要的。

另一方面,確保你瞭解Craig Stuntz寫的是什麼:不要自己寫這個認證和授權的東西,只要有可能就使用底層操作系統。

BTW:Hallvard Vassbotn寫FastMM一個不錯的博客: http://hallvards.blogspot.com/2007/05/use-full-fastmm-consider-donating.html

問候,

的Jeroen Pluimers

0

有沒有可能到解密的XML加載到char或字節數組,而比一個字符串?那麼就沒有寫時複製的處理,所以你可以在釋放之前用#0回填內存?

將char數組賦給字符串時要小心,因爲Delphi在這裏爲了與char的傳統打包數組[1..x]兼容而進行了一些智能處理。

另外,你可以使用ShortString?

0

如果您使用XML(甚至是加密的)來存儲密碼,那麼您的用戶將面臨風險。更好的方法是存儲密碼的哈希值,然後將哈希與輸入的密碼進行比較。這種方法的優點是即使知道散列值,也不會知道產生散列的密碼。添加強力標識符(計算無效密碼嘗試次數,並在特定數量後鎖定帳戶)將進一步提高安全性。

有幾種方法可以用來創建一個字符串的散列。一個好的起點是看渦輪增壓開源項目「LockBox」,我相信它有幾個創建單向散列鍵的例子。

編輯

但如何知道如果一個方法幫助的哈希值?如果你真的偏執狂,你可以通過可預測的方式來修改散列值,只有你會知道......比如說,一個使用特定種子值加上日期的隨機數。然後,您可以將足夠的散列存儲在xml中,以便將其用作比較的起點。僞隨機數生成器的好處在於,它們總是生成相同的一系列給定相同種子的「隨機」數字。

3

這樣的事情呢?

procedure WipeString(const str: String); 
var 
    i:Integer; 
    iSize:Integer; 
    pData:PChar; 

begin 
    iSize := Length(str); 
    pData := PChar(str); 

    for i := 0 to 7 do 
    begin 
     ZeroMemory(pData, iSize); 
     FillMemory(pData, iSize, $FF); // 1111 1111 
     FillMemory(pData, iSize, $AA); // 1010 1010 
     FillMemory(pData, iSize, $55); // 0101 0101 
     ZeroMemory(pData, iSize); 
    end; 
end; 
0

小心的,嘗試將一個字符串的指針功能,並嘗試使用FillCharZeroMemory擦拭字符串的內容。

  • 這既是錯誤(字符串共享;你搞砸別人誰是目前使用的字符串)
  • ,並可能導致訪問衝突(如果字符串恰好是一個常數,它正坐在在進程地址空間中的只讀數據頁面上,並試圖寫它是訪問衝突)

 

procedure BurnString(var s: UnicodeString); 
begin 
    { 
     If the string is actually constant (reference count of -1), then any attempt to burn it will be 
     an access violation; as the memory is sitting in a read-only data page. 

     But Delphi provides no supported way to get the reference count of a string. 

     It's also an issue if someone else is currently using the string (i.e. Reference Count > 1). 
     If the string were only referenced by the caller (with a reference count of 1), then 
     our function here, which received the string through a var reference would also have the string with 
     a reference count of one. 

     Either way, we can only burn the string if there's no other reference. 

     The use of UniqueString, while counter-intuitiave, is the best approach. 
     If you pass an unencrypted password to BurnString as a var parameter, and there were another reference, 
     the string would still contain the password on exit. You can argue that what's the point of making a *copy* 
     of a string only to burn the copy. Two things: 

      - if you're debugging it, the string you passed will now be burned (i.e. your local variable will be empty) 
      - most of the time the RefCount will be 1. When RefCount is one, UniqueString does nothing, so we *are* burning 
       the only string 
    } 
    if Length(s) > 0 then 
    begin 
     System.UniqueString(s); //ensure the passed in string has a reference count of one 
     ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar)); 

     { 
      By not calling UniqueString, we only save on a memory allocation and wipe if RefCnt <> 1 
      It's an unsafe micro-optimization because we're using undocumented offsets to reference counts. 

      And i'm really uncomfortable using it because it really is undocumented. 
      It is absolutely a given that it won't change. And we'd have stopping using Delphi long before 
      it changes. But i just can't do it. 
     } 
     //if PLongInt(PByte(S) - 8)^ = 1 then //RefCnt=1 
     // ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar)); 

     s := ''; //We want the callee to see their passed string come back as empty (even if it was shared with other variables) 
    end; 
end; 

一旦你甲肝e本UnicodeString版本,您可以創建AnsiStringWideString版本:

procedure BurnString(var s: AnsiString); overload; 
begin 
    if Length(s) > 0 then 
    begin 
     System.UniqueString(s); 
     ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar)); 

     //if PLongInt(PByte(S) - 8)^ = 1 then //RefCount=1 
     // ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar)); 

     s := ''; 
    end; 
end; 

procedure BurnString(var s: WideString); 
begin 
    //WideStrings (i.e. COM BSTRs) are not reference counted, but they are modifiable 
    if Length(s) > 0 then 
    begin 
     ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar)); 

     //if PLongInt(PByte(S) - 8)^ = 1 then //RefCount=1 
     // ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar)); 

     s := ''; 
    end; 
end;