One time pad encryption/decryption

Hi. I was searching for a working implementation of the one time pad encryption method in C++, but wasn't able to find one, so I wrote it myself. Wanted to share this in case someone needs it. Feel free to use, distribute, sell or modify the code as you see fit: https://github.com/DDomjosa/One-time-pad-encryption

Everything is explained in the program itself, so I won't go over it again. If you want to add (remove) legal characters, all you have to do is add (remove) them to (from) the tempAlphabetChars array on line 225 and increase (decrease) the alphabetSize on line 10 accordingly (that's the size of the array) and you're done.

If you have any suggestions or observations to share, please do. Thanks!
Last edited on
Mersenne Twister is not a cryptographically secure PRNG. Extreme care should be taken when generating a pad, since the security of OTP relies entirely on the quality of the random numbers. Ideally, a true randomness source should be used.

The encryption and decryption algorithms are too complex. They should not be more complex than
1
2
3
4
5
6
7
void otp_crypto(void *_data, const void *_pad, size_t n){
    auto data = (unsigned char *)_data;
    auto pad = (const unsigned char *)_pad;
    while (n--){
        data[n] ^= pad[n];
    }
}
Note that otp_crypto() is self-inverse when the second and third parameters are maintained.

I don't see any reason why the program needs to be aware of an alphabet. Just process byte values.
I tried using random_device to seed the Mersenne Twister, but apparently there's a bug in MinGW that prevents me from doing that, so I just stuck to using the time (I know it's a horrible idea!). It's not like I'll be encrypting top secret documents anyway (or will I?)...

At first I was going to just XOR the bits, but soon I found out that that would result in encrypted text containing a ton of unreadable characters, such as NULLs, hearts, etc., so I just resorted to letting the user only use characters that have been given integer values to prevent that sort of thing.

For example, XORing the chars 'a' and 'b' gives a binary value of 00000011, that's the end of text character, console interprets it as a heart, notepad as a superscript L and notepad++ as an ETX character and I don't want to deal with that.
Last edited on
I tried using random_device to seed the Mersenne Twister, but apparently there's a bug in MinGW that prevents me from doing that, so I just stuck to using the time (I know it's a horrible idea!).
You would still be using MT to generate the numbers, so it doesn't matter much. It'd be better to use std::random_device instead, which is supposed to be as close as the system can get to a true randomness source.

It's not like I'll be encrypting top secret documents anyway (or will I?)
Then the implementation is pointless. The only people who use OTP use it because they need a mathematical guarantee of uncrackability.

I found out that that would result in encrypted text containing a ton of unreadable characters, such as NULLs, hearts, etc.
And?
If you want "human readable" output then just encode the ciphertext in Base64 or even hex. That complexity doesn't belong in the encryption functions.
Like I said, I can't use random_device due to a bug (for the record, I'm using the Code::Blocks IDE). On the note of output, thank you for opening my eyes. I will rewrite the program to just XOR it. Could you please explain a few things though? What do these notations mean:

void *_data, const void *_pad,
auto data = (unsigned char *)_data;,
auto pad = (const unsigned char *)_pad;

Also, how would you go about encoding (and then decoding) the ciphertext in Base64? How would you go about generating the key? A random sequence of 1s and 0s? How can you convert that into chars? char a = (char)01100001; doesn't seem to work. Thank you a lot!

EDIT:

Found a way to generate 8 random bits and convert them into a char:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <bitset>
#include <iostream>
#include <random>
#include <string>
#include <time.h>
int main()
{
    std::mt19937 randomGenerator(time(NULL));
    std::uniform_int_distribution<int> randomNumber(0, 1);
    std::string bits;
    for (int i=0;i<8;i++)
        bits += std::to_string(randomNumber(randomGenerator));
    std::bitset<8> byte(bits);
    unsigned char randomChar = byte.to_ulong();
    std::cout << randomChar << std::endl << bits;
}


Still not sure on how to encode the resulting ciphertext/key in Base64. I'll have to look into that further.
Last edited on
Like I said, I can't use random_device due to a bug
You said you couldn't use it to seed Mersenne Twister. I'm saying don't use Mersenne Twister at all. Just get your numbers from std::random_device. An OTP implementation that gets its pads from Mersenne Twister is simply broken. It doesn't matter if you get your numbers as bits or as multibit integers.

What do these notations mean:
The first one is simply a function parameter list. The other two are casts.
No, I literally cannot use std::random_device in my code. Something like this:

1
2
3
4
5
6
7
8
#include <iostream>
#include <random>
int main()
{
    std::random_device rd;
    std::uniform_int_distribution<int> dist(0, 9);
    std::cout << dist(rd);
}


Results in an output like this:

1
2
3
4
5
terminate called after throwing an instance of 'std::runtime_error'
  what():  random_device::random_device(const std::string&)

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information. 


As for the function parameters, what I meant to say is why are you passing something in as void? Doesn't that just result in it being nothing or am I thinking completely wrong? Thanks!
Ok, I rewrote the whole program ( https://github.com/DDomjosa/One-time-pad-encryption ) and it works up until a point. It can encrypt/decrypt small pieces of text easily, but when it comes to a wall of text ( http://uncyclopedia.wikia.com/wiki/Wall_of_Text for example) it outputs a key and ciphertext files that are both different sizes from each other and the original text (???) and when decrypting it only decrypts like a few first words. Where did I go wrong? Also, on line 11 it doesn't let me change the type of input to unsigned char (maybe that's the problem?)...

Oh, and I created my own file extension just to feel cool (and to lower the risk of people messing with the files manually).
Last edited on
Use a different compiler, then.
I will look into that, now I just want it to work fully. I figured out why it decrypts only a few words. When it encounters a SUB character (decimal 26), it thinks that's the end of the file. How can this be handled? This doesn't work:

1
2
3
4
5
6
7
    while (read.get(input))
    {
        if (input == '\x1A')
            chars.push_back('\x1A');
        else
            chars.push_back(input);
    }
https://github.com/DDomjosa/One-time-pad-encryption I figured it out!!! It works fully, implementing base64 encoding fixed the SUB character issue too! Thank you so much for helping and not telling me the answers directly, helios!
Topic archived. No new replies allowed.