Click here to Skip to main content
15,860,943 members
Articles / Programming Languages / C#
Article

Secure File Shredder

Rate me:
Please Sign up or sign in to vote.
4.91/5 (24 votes)
28 Oct 2008CPOL2 min read 109K   2.6K   65   28
A secure file shredder in C#

Introduction

This article describes a C style shredder in C# .NET.
Pizza van with tinted windows parked across the road for days? Strange clicking sound on the landline? Crooked CEO maybe? Then this is exactly the tool you have been searching for..

Background

I originally published v1 in VB6 as 'SDS' on Planet Source Code.

NShred 2.0

I decided to rewrite the SDS file shredder as one of my first forays into the C# .NET language, the reason being that it is a fairly compact and class-driven application. This time around, however, not being saddled with the limitations of the VB6 language, I was able to create a faster and more thorough application engine. The cShredder class is almost completely Win32 driven, using virtual memory buffers and the WriteFile API to overwrite the file with several passes of 0s, 1s, and random data. After some initial preamble of file path checks, attribute stripping, and enabling key access tokens within the process, we create the buffer:

C#
...
hFile = CreateFileW(pName, GENERIC_ALL, FILE_SHARE_NONE, 
        IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
// get the file size
nFileLen = fileSize(hFile);
if (nFileLen > BUFFER_SIZE)
    nFileLen = BUFFER_SIZE;
if (hFile.ToInt32() == -1)
    return false;
// set the table
SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
pBuffer = VirtualAlloc(IntPtr.Zero, nFileLen, MEM_COMMIT, PAGE_READWRITE);
if (pBuffer == IntPtr.Zero)
    return false;
// fill the buffer with zeros
RtlZeroMemory(pBuffer, nFileLen);
...

Once the buffer is allocated and written to, call the overwriteFile method that uses WriteFile to overwrite the contents in buffered 'chunks'. Note that the file was opened with the WRITE_THROUGH flag, which causes the file to be written through the buffers and straight to disk. Also, all APIs used are of the 'W' flavor, so the shredder should be fully Unicode compliant.

C#
private Boolean overwriteFile(IntPtr hFile, IntPtr pBuffer)
{
    UInt32 nFileLen = fileSize(hFile);
    UInt32 dwSeek = 0;
    UInt32 btWritten = 0;

    try
    {
        if (nFileLen < BUFFER_SIZE)
        {
            SetFilePointerEx(hFile, dwSeek, IntPtr.Zero, FILE_BEGIN);
            WriteFile(hFile, pBuffer, nFileLen, ref btWritten, IntPtr.Zero);
        }
        else
        {
            do
            {
                SetFilePointerEx(hFile, dwSeek, IntPtr.Zero, FILE_BEGIN);
                WriteFile(hFile, pBuffer, BUFFER_SIZE, ref btWritten, IntPtrZero);
                dwSeek += btWritten;
            } while ((nFileLen - dwSeek) > BUFFER_SIZE);
            WriteFile(hFile, pBuffer, (nFileLen - dwSeek), ref btWritten, IntPtr.Zero);
        }
        // reset file pointer
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        // add it up
        if ((btWritten + dwSeek) == nFileLen)
            return true;
        return false;
    }
    catch
    {
        return false;
    }
}

The buffers are filled first with zeros, then ones, random data, then zeros again; this ensures that even with the most sophisticated software techniques, using a modern hard drive, all data will be rendered permanently unreadable. The random data phase uses the Crypto API to fill the buffer; intended for secure key creation, it also works well in this implementation.

C#
private Boolean randomData(IntPtr pBuffer, UInt32 nSize)
{
    IntPtr iProv = IntPtr.Zero;

    try
    {
        // acquire context
        if (CryptAcquireContextW(ref iProv, "", MS_ENHANCED_PROV, 
            PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != true)
            return false;
        // generate random block
        if (CryptGenRandom(iProv, nSize, pBuffer) != true)
            return false;
        return true;
    }
    finally
    {
        // release crypto engine
        if (iProv != IntPtr.Zero)
            CryptReleaseContext(iProv, 0);
    }
}

One thing that most open source shredders I have seen fail to do, is to verify the read on the file. This is accomplished by comparing the buffer with the file contents using RtlCompareMemory:

C#
private Boolean writeVerify(IntPtr hFile, IntPtr pCompare, UInt32 pSize)
{
    IntPtr pBuffer = IntPtr.Zero;
    UInt32 iRead = 0;

    try
    {
        pBuffer = VirtualAlloc(IntPtr.Zero, pSize, MEM_COMMIT, PAGE_READWRITE);
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        if (ReadFile(hFile, pBuffer, pSize, ref iRead, IntPtr.Zero) == 0)
        {
            if (InError != null)
                InError(004, "The file write failed verification test.");
            return false; // bad read
        }
        if (RtlCompareMemory(pCompare, pBuffer, pSize) == pSize)
            return true; // equal
        return false;
    }
    finally
    {
        if (pBuffer != IntPtr.Zero)
            VirtualFree(pBuffer, pSize, MEM_RELEASE);
    }
}

After the overwrite cycles, the file is then zero sized ten times, and renamed thirty times:

C#
private Boolean zeroFile(IntPtr pName)
{
    for (Int32 i = 0; i < 10; i++)
    {
        IntPtr hFile = CreateFileW(pName, GENERIC_ALL, FILE_SHARE_NONE,
            IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
        if (hFile == IntPtr.Zero)
            return false;
        SetFilePointerEx(hFile, 0, IntPtr.Zero, FILE_BEGIN);
        // unnecessary but..
        FlushFileBuffers(hFile);
        CloseHandle(hFile);
    }
    return true;
}

private Boolean renameFile(string sPath)
{
    string sNewName = String.Empty;
    string sPartial = sPath.Substring(0, sPath.LastIndexOf(@"\") + 1);
    Int32 nLen = 10;
    char[] cName = new char[nLen];
    for (Int32 i = 0; i < 30; i++)
    {
        for (Int32 j = 97; j < 123; j++)
        {
            for (Int32 k = 0; k < nLen; k++)
            {
                if (k == (nLen - 4))
                    sNewName += ".";
                else
                    sNewName += (char)j;
            }
            if (MoveFileExW(sPath, sPartial + sNewName, 
		MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0)
                sPath = sPartial + sNewName;
            sNewName = String.Empty;
        }
    }
    // last step: delete the file
    if (deleteFile(sPath) != true)
        return false;
    return true;
}

For the truly paranoid user, there is a hidden startup switch, '/p', that enables the paranoid mode. With this setting, after the overwrite cycles, the files object identifier is deleted, effectively orphaning the file from the file and security subsystems:

C#
private Boolean orphanFile(IntPtr pName)
{
    UInt32 lpBytesReturned = 0;
    IntPtr hFile = CreateFileW(pName, GENERIC_WRITE, FILE_SHARE_NONE,
        IntPtr.Zero, OPEN_EXISTING, WRITE_THROUGH, IntPtr.Zero);
    if (DeviceIoControl(hFile, FsctlDeleteObjectId, IntPtr.Zero, 
        0, IntPtr.Zero, 0, out lpBytesReturned, IntPtr.Zero))
        return false;
    return true;
}

History

  • 26th October, 2008: Initial post
  • 27th October, 2008: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Network Administrator vtdev.com
Canada Canada
Network and programming specialist. Started in C, and have learned about 14 languages since then. Cisco programmer, and lately writing a lot of C# and WPF code, (learning Java too). If I can dream it up, I can probably put it to code. My software company, (VTDev), is on the verge of releasing a couple of very cool things.. keep you posted.

Comments and Discussions

 
GeneralMy vote of 5 Pin
majid torfi2-Nov-14 3:00
professionalmajid torfi2-Nov-14 3:00 
GeneralFile Renames before delete Pin
brennan13134-Apr-10 16:18
brennan13134-Apr-10 16:18 
GeneralRe: File Renames before delete Pin
John Underhill5-Apr-10 3:53
John Underhill5-Apr-10 3:53 
GeneralAbout orphan method.. Pin
Alberto M.3-Apr-10 13:05
Alberto M.3-Apr-10 13:05 
GeneralRe: About orphan method.. Pin
John Underhill3-Apr-10 14:06
John Underhill3-Apr-10 14:06 
GeneralSteppenwolfe: would you tell me why you use 65536 buffer size Pin
joshzhang22-Oct-09 18:32
joshzhang22-Oct-09 18:32 
GeneralRe: Steppenwolfe: would you tell me why you use 65536 buffer size Pin
John Underhill3-Apr-10 14:14
John Underhill3-Apr-10 14:14 
GeneralMemory leak Pin
MARC5614-Oct-09 2:35
MARC5614-Oct-09 2:35 
GeneralRe: Memory leak Pin
John Underhill3-Apr-10 14:19
John Underhill3-Apr-10 14:19 
GeneralRe: Memory leak Pin
sir-West27-Oct-11 22:16
sir-West27-Oct-11 22:16 
QuestionShadow Copies Pin
TobiasP2-Nov-08 1:22
TobiasP2-Nov-08 1:22 
AnswerRe: Shadow Copies Pin
John Underhill2-Nov-08 1:57
John Underhill2-Nov-08 1:57 
GeneralRe: Shadow Copies Pin
TobiasP2-Nov-08 2:21
TobiasP2-Nov-08 2:21 
GeneralRe: Shadow Copies Pin
John Underhill3-Nov-08 6:02
John Underhill3-Nov-08 6:02 
AnswerRe: Shadow Copies Pin
Keith Vinson4-Nov-08 5:10
Keith Vinson4-Nov-08 5:10 
GeneralRe: Shadow Copies Pin
John Underhill4-Nov-08 15:49
John Underhill4-Nov-08 15:49 
QuestionEver looked at SDelete? Pin
Alois Kraus28-Oct-08 21:37
Alois Kraus28-Oct-08 21:37 
AnswerRe: Ever looked at SDelete? Pin
John Underhill29-Oct-08 7:56
John Underhill29-Oct-08 7:56 
GeneralRe: Ever looked at SDelete? Pin
Alovera20-May-09 8:37
Alovera20-May-09 8:37 
GeneralThoughts Pin
PIEBALDconsult26-Oct-08 16:35
mvePIEBALDconsult26-Oct-08 16:35 
GeneralRe: Thoughts [modified] Pin
John Underhill26-Oct-08 16:54
John Underhill26-Oct-08 16:54 
GeneralRe: Thoughts Pin
supercat927-Oct-08 7:26
supercat927-Oct-08 7:26 
GeneralRe: Thoughts Pin
John Underhill27-Oct-08 7:40
John Underhill27-Oct-08 7:40 
GeneralRe: Thoughts Pin
supercat927-Oct-08 13:08
supercat927-Oct-08 13:08 
GeneralRe: Thoughts Pin
PIEBALDconsult28-Oct-08 10:23
mvePIEBALDconsult28-Oct-08 10:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.