How can I increase memory security in Delphi?

前端 未结 11 1637
醉酒成梦
醉酒成梦 2020-12-11 09:50

Is it possible to \"wipe\" strings in Delphi? Let me explain:

I am writing an application that will include a DLL to authorise users. It will read an encrypted file

相关标签:
11条回答
  • 2020-12-11 10:18

    How about something like this?

    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 讨论(0)
  • 2020-12-11 10:21

    Be careful of functions that try to treat a string as a pointer, and try to use FillChar or ZeroMemory to wipe the string contents.

    • this is both wrong (strings are shared; you're screwing someone else who's currently using the string)
    • and can cause an access violation (if the string happens to have been a constant, it is sitting on a read-only data page in the process address space; and trying to write to it is an access violation)

     

    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;
    

    Once you have the UnicodeString version, you can create the AnsiString and WideString versions:

    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;
    
    0 讨论(0)
  • 2020-12-11 10:22

    Two general points about this:

    First, this is one of those areas where "if you have to ask, you probably shouldn't be doing this." And please don't take that the wrong way; I mean no disrespect to your programming skills. It's just that writing secure, cryptographically strong software is something that either you're an expert at or you aren't. Very much in the same way that knowing "a little bit of karate" is much more dangerous than knowing no karate at all. There are a number of third-party tools for writing secure software in Delphi which have expert support available; I would strongly encourage anyone without a deep knowledge of cryptographic services in Windows, the mathematical foundations of cryptography, and experience in defeating side channel attacks to use them instead of attempting to "roll their own."

    To answer your specific question: The Windows API has a number of functions which are helpful, such as CryptProtectMemory. However, this will bring a false sense of security if you encrypt your memory, but have a hole elsewhere in the system, or expose a side channel. It can be like putting a lock on your door but leaving the window open.

    0 讨论(0)
  • 2020-12-11 10:23

    If your using XML, even encrypted, to store passwords your putting your users at risk. A better approach would be to store the hash values of the passwords instead, and then compare the hash against the entered password. The advantage of this approach is that even in knowing the hash value, you won't know the password which makes the hash. Adding a brute force identifier (count invalid password attempts, and lock the account after a certain number) will increase security even further.

    There are several methods you can use to create a hash of a string. A good starting point would be to look at the turbo power open source project "LockBox", I believe it has several examples of creating one way hash keys.

    EDIT

    But how does knowing the hash value if its one way help? If your really paranoid, you can modify the hash value by something prediticable that only you would know... say, a random number using a specific seed value plus the date. You could then store only enough of the hash in your xml so you can use it as a starting point for comparison. The nice thing about psuedo random number generators is that they always generate the same series of "random" numbers given the same seed.

    0 讨论(0)
  • 2020-12-11 10:24

    How about decrypting the file to a stream, using a SAX processor instead of an XML DOM to do your verification and then overwriting the decrypted stream before freeing it?

    0 讨论(0)
提交回复
热议问题