Yet Another XOR Encryptor...

I originally wanted to write this in C++ and then post it in the Source Codes section.
However, it appears that C++11's random library is only a stub in both MinGW G++ 4.6.1 and 4.6.2, throwing an std::runtime_error for valid code.

So I decided to try and write the program in D instead, and here it is. Compiles beautifully with DMD 2.057.
Posting this for educational purposes, and for promoting the D language, mwhuahoohahaha!
Employ extra care when using it with large files, for it eats bytes like a whaleshark eats plankton.

DISCLAIMER: This is a beta version. Keep an extinguisher near you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*
    Some of the things I learned about D so far, are:

    1. There is no `&' denoting a reference, instead `ref' is used.
    2. Template parameters are put inside `!( )' rather than `< >'.
       Single arguments can also follow a simple `! ', see line 53.
    3. One may omit parentheses when calling a function that requires no parameters.
    4. D's foreach() is equivalent to C++11's range-based for().
    5. Automatic type deduction is on by default, even if `auto' is absent.
    6. "Property" functions are called as if they were a member function:
       e.g. "C:\\pagefile.sys".isFile;
*/

import std.file;
import std.random;
import std.stdio;
import std.stream;

int main(string[] args)
{
    if (args.length != 4)
    {
        stderr.writeln("\nXorCrypt usage:\n"
            "\tprogram.exe inputFile outputFile keyFile\n\n"
            "ATTN:\tIf the keyFile doesn't exist, it will be created.\n"
            "\tIf the keyFile is empty, it will be filled with random data\n"
            "\t\tuntil it reaches the size of inputFile.\n"
            "\tThe outputFile will be overwritten.");
        return 1;
    }

    if (args[1] == args[2] ||
        args[2] == args[3] ||
        args[1] == args[3])
    {
        stderr.writeln("\ninputFile, outputFile and keyFile must be distinct.");
        return 1;
    }

    writeln(); // for prettiness

    // here a keyFile is constructed, if needed
    if (!args[3].exists || (args[3].isFile && args[3].getSize == 0))
    {
        write("\tWriting keyFile `", args[3], "', please wait... ");
        stdout.flush;

        auto keyFile = new BufferedFile(args[3], FileMode.Out);
        auto prng = Random(unpredictableSeed);
        auto remainingBytes = args[1].getSize;

        while (remainingBytes-- != 0)
            keyFile.write(uniform!"[]"(ubyte.min, ubyte.max, prng));

        keyFile.close;
        writeln("keyFile ready.");
    }

    auto inputFile  = new BufferedFile(args[1], FileMode.In);
    auto outputFile = new BufferedFile(args[2], FileMode.OutNew);
    auto keyFile    = new BufferedFile(args[3], FileMode.In);

    write("\tWriting outputFile `", args[2], "', please wait... ");
    stdout.flush;

    while (!inputFile.eof)
    {
        if (keyFile.eof)
            keyFile.seekSet(0);

        ubyte ib, kb;

        inputFile.read(ib);
        keyFile.read(kb);
        outputFile.write(ib ^= kb);
    }

    outputFile.close;
    writeln("outputFile ready.");
    return 0;
}

Second version, with Adaptive Buffer Length TechnologyTM.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import std.algorithm;
import std.file;
import std.random;
import std.stdio;
import std.stream;

uint decideBufferLength(string ifn, string kfn)
{
    immutable uint maxBuffer = 64 * 1024 * 1024;
    auto temp = ifn.getSize;
    alias getSize S;

    if (kfn.exists && kfn.isFile && kfn.getSize != 0)
        temp = (S(kfn) + S(ifn)) * min(S(kfn), S(ifn)) / max(S(kfn), S(ifn));

    if (temp > maxBuffer)
        return maxBuffer;

    return cast(uint)(temp);
}

int main(string[] args)
{
    if (args.length != 4)
    {
        stderr.writeln("\nXorCrypt2 usage:\n"
            "\tprogram.exe inputFile outputFile keyFile\n\n"
            "ATTN:\tIf the keyFile doesn't exist, it will be created.\n"
            "\tIf the keyFile is empty, it will be filled with random data\n"
            "\t\tuntil it reaches the size of inputFile.\n"
            "\tThe outputFile will be overwritten.");
        return 1;
    }

    if (args[1] == args[2] || args[2] == args[3] || args[1] == args[3])
    {
        stderr.writeln("\ninputFile, outputFile and keyFile must be distinct.");
        return 1;
    }

    const uint bufferLength = decideBufferLength(args[1], args[3]);

    if (bufferLength == 0)
    {
        stderr.writeln("\ninputFile is empty. Nothing to do.");
        return 1;
    }

    writeln("\n - Internal buffer length set to ", bufferLength, " bytes.");

    // here a keyFile is constructed, if needed
    if (!args[3].exists || (args[3].isFile && args[3].getSize == 0))
    {
        write(" - Writing keyFile `", args[3], "', please wait... ");
        stdout.flush;

        auto keyFile = new BufferedFile(args[3], FileMode.Out);
        auto prng = Random(unpredictableSeed);
        auto remainingBytes = args[1].getSize;
        auto buffer = new ubyte[remainingBytes % bufferLength];

        do
        {
            foreach (ref r; buffer)
                r = uniform!"[]"(ubyte.min, ubyte.max, prng);

            keyFile.writeExact(buffer.ptr, buffer.length);
            remainingBytes -= buffer.length;
            buffer.length = bufferLength;
        }
        while (remainingBytes != 0);

        keyFile.close;
        writeln("keyFile ready.");
    }

    auto inputFile  = new BufferedFile(args[1], FileMode.In);
    auto outputFile = new BufferedFile(args[2], FileMode.OutNew);
    auto keyFile    = new BufferedFile(args[3], FileMode.In);

    write(" - Writing outputFile `", args[2], "', please wait... ");
    stdout.flush;

    auto ioBuffer = new ubyte[inputFile.size % bufferLength];
    auto kBuffer = new ubyte[ioBuffer.length];

    while (!inputFile.eof)
    {
        uint kTotalRead = 0;

        while (kTotalRead != kBuffer.length)
        {
            if (keyFile.eof)
                keyFile.seekSet(0);

            kTotalRead += keyFile.read(kBuffer[kTotalRead .. $]);
        }

        inputFile.readExact(ioBuffer.ptr, ioBuffer.length);

        foreach (i, ref b; ioBuffer)
            b ^= kBuffer[i];

        outputFile.writeExact(ioBuffer.ptr, ioBuffer.length);
        ioBuffer.length = kBuffer.length = bufferLength;
    }

    outputFile.close;
    writeln("outputFile ready.\n");
    return 0;
}


Edit: squashed a little bug.
Edit2: some more improvements and fixes.
Last edited on
Version 3. This one is slightly ugly, but it should be the fastest in the most cases.
Now I guess it's time to investigate the MmFileStream class, because I see Cubbi loves memory mapping his files.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import std.algorithm;
import std.file;
import std.random;
import std.stdio;
import std.stream;

uint decideBufferLength(string inputFilename, string keyFilename)
{
    immutable uint maxBuffer = 64 * 1024 * 1024;
    immutable uint minBuffer = 1024 * 1024;
    auto temp = inputFilename.getSize;

    alias inputFilename I;
    alias keyFilename   K;
    alias getSize       S;

    if (keyFilename.exists && keyFilename.isFile && keyFilename.getSize != 0)
        temp = (S(I) + S(K)) * min(S(I), S(K)) / max(S(I), S(K));

    if (temp > maxBuffer)
        return maxBuffer;

    if (temp < minBuffer)
        return minBuffer;

    return cast(uint)(temp);
}

int main(string[] args)
{
    if (args.length != 4)
    {
        stderr.writeln("\nXorCrypt3 usage:\n"
            "\tprogram.exe inputFile outputFile keyFile\n\n"
            "ATTN:\tIf the keyFile doesn't exist, it will be created.\n"
            "\tIf the keyFile is empty, it will be filled with random data\n"
            "\t\tuntil it reaches the size of inputFile.\n"
            "\tThe outputFile will be overwritten.");
        return 1;
    }

    if (args[1] == args[2] || args[2] == args[3] || args[1] == args[3])
    {
        stderr.writeln("\ninputFile, outputFile and keyFile must be distinct.");
        return 1;
    }

    if (args[1].getSize == 0)
    {
        stderr.writeln("\ninputFile is empty. Nothing to do.");
        return 1;
    }

    const uint bufferLength = decideBufferLength(args[1], args[3]);

    writeln("\n - Internal buffer length set to ", bufferLength, " bytes.");

    // here a keyFile is constructed, if needed
    if (!args[3].exists || (args[3].isFile && args[3].getSize == 0))
    {
        write(" - Writing keyFile `", args[3], "', please wait... ");
        stdout.flush;

        auto keyFile = new BufferedFile(args[3], FileMode.Out);
        auto prng = Random(unpredictableSeed);
        auto remainingBytes = args[1].getSize;
        auto buffer = new ubyte[remainingBytes % bufferLength];

        do
        {
            foreach (ref r; buffer)
                r = uniform!"[]"(ubyte.min, ubyte.max, prng);

            keyFile.writeExact(buffer.ptr, buffer.length);
            remainingBytes -= buffer.length;
            buffer.length = bufferLength;
        }
        while (remainingBytes != 0);

        keyFile.close;
        writeln("keyFile ready.");
    }

    auto inputFile      = new BufferedFile(args[1], FileMode.In);
    auto outputFile     = new BufferedFile(args[2], FileMode.OutNew);
    auto keyFile        = new BufferedFile(args[3], FileMode.In);
    auto ioBuffer       = new ubyte[inputFile.size % bufferLength];
    auto keyBuffer      = new ubyte[ioBuffer.length];
    bool cycleKeyBuffer = false;

    write(" - Writing outputFile `", args[2], "', please wait... ");
    stdout.flush;

    if (keyFile.size <= bufferLength)
    {
        keyBuffer.length = cast(uint)(keyFile.size);
        keyFile.readExact(keyBuffer.ptr, keyBuffer.length);
        cycleKeyBuffer = true;
    }

    uint k = 0; // for iterating the keyBuffer, if cycled

    while (!inputFile.eof)
    {
        inputFile.readExact(ioBuffer.ptr, ioBuffer.length);

        if (!cycleKeyBuffer)
        {
            auto keyBytesRead = keyFile.read(keyBuffer);

            while (keyBytesRead != keyBuffer.length)
            {
                if (keyFile.eof)
                    keyFile.seekSet(0);

                keyBytesRead += keyFile.read(keyBuffer[keyBytesRead .. $]);
            }

            foreach (i, ref b; ioBuffer)
                b ^= keyBuffer[i];

            keyBuffer.length = bufferLength;
        }
        else
            foreach (ref b; ioBuffer)
            {
                b ^= keyBuffer[k++];

                if (k == keyBuffer.length)
                    k = 0;
            }

        outputFile.writeExact(ioBuffer.ptr, ioBuffer.length);
        ioBuffer.length = bufferLength;
    }

    outputFile.close;
    writeln("outputFile ready.\n");
    return 0;
}
because I see Cubbi loves memory mapping his files.

I was just gonna ask, cause I remember reading (on wiki, I think) that D has them in the standard library.
@ Framework: Please elaborate. There's a reason my program generates a random keyfile...
Here's version 4, with memory mapped files (based on version 1 for simplicity).
It's the slowest, though. I'll admit I wrote it in a hurry... but still!
Maybe CPU cycles get wasted by the one-byte reads and writes?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import std.file;
import std.mmfile;
import std.random;
import std.stdio;
import std.stream;

int main(string[] args)
{
    if (args.length != 4)
    {
        stderr.writeln("\nXorCrypt4 usage:\n"
            "\tprogram.exe inputFile outputFile keyFile\n\n"
            "ATTN:\tIf the keyFile doesn't exist, it will be created.\n"
            "\tIf the keyFile is empty, it will be filled with random data\n"
            "\t\tuntil it reaches the size of inputFile.\n"
            "\tThe outputFile will be overwritten.");
        return 1;
    }

    if (args[1] == args[2] || args[2] == args[3] || args[1] == args[3])
    {
        stderr.writeln("\ninputFile, outputFile and keyFile must be distinct.");
        return 1;
    }

    if (args[1].getSize == 0)
    {
        stderr.writeln("\ninputFile is empty. Nothing to do.");
        return 1;
    }

    writeln(); // for prettiness

    // here a keyFile is constructed, if needed
    if (!args[3].exists || (args[3].isFile && args[3].getSize == 0))
    {
        write(" - Writing keyFile `", args[3], "', please wait... ");
        stdout.flush;

        auto keyFile = new MmFileStream(new MmFile(args[3], MmFile.Mode.readWrite, args[1].getSize, null));
        auto prng = Random(unpredictableSeed);
        auto remainingBytes = args[1].getSize;

        while (remainingBytes-- != 0)
            keyFile.write(uniform!"[]"(ubyte.min, ubyte.max, prng));

        keyFile.close;
        writeln("keyFile ready.");
    }

    auto inputFile  = new MmFileStream(new MmFile(args[1], MmFile.Mode.read, args[1].getSize, null));
    auto outputFile = new MmFileStream(new MmFile(args[2], MmFile.Mode.readWriteNew, inputFile.size, null));
    auto keyFile    = new MmFileStream(new MmFile(args[3], MmFile.Mode.read, args[3].getSize, null));

    write(" - Writing outputFile `", args[2], "', please wait... ");
    stdout.flush;

    while (!inputFile.eof)
    {
        if (keyFile.eof)
            keyFile.seekSet(0);

        ubyte ib, kb;

        inputFile.read(ib);
        keyFile.read(kb);
        outputFile.write(ib ^= kb);
    }

    outputFile.close;
    writeln("outputFile ready.");
    return 0;
}


One-run speed tests for ~210 MB file, and a new random key file each time:
version 1 (dumb one-byte + buffered files):
-------------------------------
Real Time: 00:00:40.906
User Time: 00:00:29.687
Sys Time:  00:00:00.953

version 2 (crazy variable buffer + buffered files):
-------------------------------
Real Time: 00:00:18.359
User Time: 00:00:05.843
Sys Time:  00:00:10.031

version 3 (variable buffer optimized for small key files + buffered files):
-------------------------------
Real Time: 00:00:18.875
User Time: 00:00:06.015
Sys Time:  00:00:10.171

version 4 (dumb one-byte + memory mapped files)
-------------------------------
Real Time: 00:01:28.046
User Time: 00:01:11.296
Sys Time:  00:00:09.937


For anyone who cares, I used the timep.exe utility, which you can find in here:
http://jmhartsoftware.com/WSP4_Examples.zip
Version 5 followed, immediately after a major WTFAID moment.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import std.file;
import std.mmfile;
import std.random;
import std.stdio;

int main(string[] args)
{
    if (args.length != 4)
    {
        stderr.writeln("\nXorCrypt5 usage:\n"
            "\tprogram.exe inputFile outputFile keyFile\n\n"
            "ATTN:\tIf the keyFile doesn't exist, it will be created.\n"
            "\tIf the keyFile is empty, it will be filled with random data\n"
            "\t\tuntil it reaches the size of inputFile.\n"
            "\tThe outputFile will be overwritten.");
        return 1;
    }

    if (args[1] == args[2] || args[2] == args[3] || args[1] == args[3])
    {
        stderr.writeln("\ninputFile, outputFile and keyFile must be distinct.");
        return 1;
    }

    if (args[1].getSize == 0)
    {
        stderr.writeln("\ninputFile is empty. Nothing to do.");
        return 1;
    }

    writeln(); // for prettiness

    // here a keyFile is constructed, if needed
    if (!args[3].exists || (args[3].isFile && args[3].getSize == 0))
    {
        write(" - Writing keyFile `", args[3], "', please wait... ");
        stdout.flush;

        auto keyFile = new MmFile(args[3], MmFile.Mode.readWrite, args[1].getSize, null);
        auto prng = Random(unpredictableSeed);

        foreach (ref k; cast(ubyte[])(keyFile.opSlice))
            k = uniform!"[]"(ubyte.min, ubyte.max, prng);

        clear(keyFile);
        writeln("keyFile ready.");
    }

    auto inputFile  = new MmFile(args[1], MmFile.Mode.read, args[1].getSize, null);
    auto outputFile = new MmFile(args[2], MmFile.Mode.readWriteNew, args[1].getSize, null);
    auto keyFile    = new MmFile(args[3], MmFile.Mode.read, args[3].getSize, null);

    write(" - Writing outputFile `", args[2], "', please wait... ");
    stdout.flush;

    foreach (i, ref o; cast(ubyte[])(outputFile.opSlice))
        o = inputFile.opIndex(i) ^ keyFile.opIndex(i % keyFile.length);

    writeln("outputFile ready.");
    return 0;
}


Real Time: 00:00:20.609
User Time: 00:00:19.796
Sys Time:  00:00:00.453


That's better. The only thing that worries me at this point is that computerquip said they might deprecate the delete keyword... and in my case, the program won't run correctly without it.

Edit: replaced delete keyFile; with clear(keyFile);
Last edited on
Topic archived. No new replies allowed.