3

是否可以在 Delphi 中“擦除”字符串?让我解释:

我正在编写一个应用程序,其中将包含一个用于授权用户的 DLL。它将加密文件读入 XML DOM,使用那里的信息,然后释放 DOM。

很明显,未加密的 XML 仍然位于 DLL 的内存中,因此容易受到检查。现在,我不会过分保护它——用户可以创建另一个 DLL——但我想采取一个基本步骤来防止用户名在内存中存在很长时间。但是,由于引用,我认为无论如何我都不能轻易擦除内存。如果我遍历我的 DOM(这是一个 TNativeXML 类)并找到每个字符串实例,然后将其变成类似“aaaaa”的内容,那么它实际上不会将新字符串指针分配给 DOM 引用,然后将旧字符串留在原处内存中有等待重新分配吗?有没有办法确保我正在杀死唯一的原始副本?

还是在 D2007 中有一种方法可以告诉它从堆中擦除所有未使用的内存?所以我可以释放 DOM,然后告诉它擦除。

或者我应该继续我的下一个任务而忘记这一点,因为它真的不值得打扰。

4

11 回答 11

7

我认为这不值得打扰,因为如果用户可以使用 DLL 读取进程的内存,则同一用户也可以在任何给定时间点停止执行。在擦除内存之前停止执行仍将使用户可以完全访问未加密的数据。

IMO 任何足够感兴趣并能够执行您所描述的操作的用户都不会因您的 DLL 擦除内存而受到严重不便。

于 2009-02-11T11:51:23.990 回答
4

关于此的两个一般要点:

首先,这是“如果你不得不问,你可能不应该这样做”的领域之一。请不要采取错误的方式;我的意思不是不尊重你的编程技能。只是编写安全、加密功能强大的软件是你要么是专家要么不是专家的事情。就像知道“一点点空手道”比完全不知道空手道要危险得多一样。有许多第三方工具可用于在 Delphi 中编写安全软件,并提供专家支持;我强烈鼓励没有深入了解 Windows 中的密码服务、密码学的数学基础以及击败侧信道攻击经验的人使用它们,而不是尝试“自己动手”。

要回答您的具体问题:Windows API 有许多有用的函数,例如CryptProtectMemory。然而,如果你加密了你的记忆,但在系统的其他地方有一个漏洞,或者暴露了一个侧通道,这会带来一种错误的安全感。这就像在你的门上放了一把锁,但让窗户保持打开状态。

于 2009-02-11T17:10:38.607 回答
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;
于 2010-03-30T22:15:18.363 回答
2

DLL 不拥有分配的内存,进程拥有。一旦进程终止,您的特定进程分配的内存将被丢弃,无论 DLL 是否挂起(因为它正在被另一个进程使用)。

于 2009-02-11T11:43:13.153 回答
1

如何将文件解密为流,使用 SAX 处理器而不是 XML DOM 进行验证,然后在释放之前覆盖解密的流?

于 2009-02-11T11:23:00.330 回答
1

如果您在完全调试模式下使用 FastMM 内存管理器,则可以在释放内存时强制它覆盖内存。

通常,该行为用于检测野指针,但它也可以用于您想要的。

另一方面,请确保您了解 Craig Stuntz 所写的内容:不要自己编写这些身份验证和授权内容,尽可能使用底层操作系统。

顺便说一句:Hallvard Vassbotn 写了一篇关于 FastMM 的不错的博客: http ://hallvards.blogspot.com/2007/05/use-full-fastmm-consider-donating.html

问候,

杰伦·普莱默斯

于 2009-02-11T21:46:59.343 回答
0

凌乱,但你可以记下你在堆中填充敏感数据时使用的堆大小,然后在释放时执行 GetMem 为你分配一个大块,跨越(比如说)200%。对该块进行填充,并假设任何碎片对考官都有很大用处。布里

于 2009-02-11T11:16:13.640 回答
0

如何将密码保留为 XML 中的哈希值,并通过将输入密码的哈希与 XML 中的哈希密码进行比较来验证。

编辑:您可以仅在最后一刻加密和解密所有敏感数据。

于 2009-02-11T11:37:07.567 回答
0

是否可以将解密的 XML 加载到 char 或 byte 数组而不是字符串中?那么就没有写时复制处理,所以你可以在释放之前用#0回填内存吗?

将 char 数组分配给字符串时要小心,因为 Delphi 在这里有一些智能处理以兼容传统的 char 压缩数组 [1..x]。

另外,你可以使用 ShortString 吗?

于 2009-02-11T21:57:56.743 回答
0

如果您使用 XML(即使是加密的)来存储密码,您的用户也会面临风险。更好的方法是存储密码的哈希值,然后将哈希值与输入的密码进行比较。这种方法的优点是即使知道散列值,您也不会知道产生散列的密码。添加蛮力标识符(计算无效密码尝试,并在一定次数后锁定帐户)将进一步提高安全性。

您可以使用多种方法来创建字符串的哈希值。一个好的起点是查看 turbo power 开源项目“ LockBox ”,我相信它有几个创建单向哈希键的示例。

编辑

但是,如果它的一种方式有帮助,那么知道哈希值有什么帮助呢?如果您真的很偏执,您可以通过只有您知道的可预测的东西来修改哈希值......比如说,使用特定种子值加上日期的随机数。然后,您可以在 xml 中仅存储足够的哈希值,以便将其用作比较的起点。伪随机数生成器的好处在于,它们总是在给定相同种子的情况下生成相同系列的“随机”数。

于 2009-03-10T17:38:18.787 回答
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;

获得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;
于 2017-11-21T20:23:00.427 回答