Skip to main content
edited tags
Link
coderodde
  • 29.8k
  • 14
  • 77
  • 194
Source Link
coderodde
  • 29.8k
  • 14
  • 77
  • 194

An easy algorithm for encrypting and decrypting binary data using a cipher key in Java

I have this easy en-/decryption algorithm.

Disclaimer

However, I have absolutely no prior experience in information security, encryption, and so on, so bare with me.

Encryption

Encryption works this way: first we read in four first bytes from the data being encrypted; then, we treat the four bytes as a single 32-bit integer and we add the value of the cipher to that value; then, we store the sum into the same location (four first bytes). Next we "shift the window" one byte to the right and read once again a 32-bit value that, however, starts at the byte 1 (indexing starts from 0); add cipher, store, and so on.

Decryption

The decryption algorithm undoes what encryption method does: it begins from the four last bytes; reads them, subtracts the cipher key, and stores back, moves the window one byte towards beginning of the data array.

The code follows:

CipherTools.java:

package net.coderodde.encryption;

import java.util.Arrays;
import java.util.Random;

/**
 * This class provides static methods for encrypting and decrypting binary data
 * represented by arrays of bytes.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Feb 29, 2016)
 */
public class CipherTools {

    private static final int BYTES_PER_INT = 4;

    /**
     * Encrypts the input data {@code input} using the cipher key 
     * {@code cipherKey}.
     * 
     * @param  input     the data to encrypt.
     * @param  cipherKey the cipher key.
     * @return           the encrypted data.
     */
    public static byte[] encrypt(byte[] input, int cipherKey) {
        checkCipherNotZero(cipherKey);
        byte[] output = input.clone();

        for (int i = 0; i <= output.length - BYTES_PER_INT; ++i) {
            writeInt(output, i, readInt(output, i) + cipherKey);
        }

        return output;
    }

    /**
     * Decrypts the input data {@code input} using the cipher key 
     * {@code cipherKey}. 
     * 
     * @param  input     the input data to decrypt.
     * @param  cipherKey the cipher key.
     * @return           the decrypted data.
     */
    public static byte[] decrypt(byte[] input, int cipherKey) {
        checkCipherNotZero(cipherKey);
        byte[] output = input.clone();

        for (int i = output.length - BYTES_PER_INT; i >= 0; --i) {
            writeInt(output, i, readInt(output, i) - cipherKey);
        }

        return output;
    }

    private static void checkCipherNotZero(int cipherKey) {
        if (cipherKey == 0) {
            throw new IllegalArgumentException(
                    "The input cipher key is zero. For this reason, the data " +
                    "would not be encrypted.");
        }
    }

    /**
     * Returns the integer represented by bytes {@code data[offset], 
     * data[offset + 1], data[offset + 2], data[offset + 3]}, where the bytes
     * are listed from least significant to most significant.
     * 
     * @param data   the data array holding the bytes.
     * @param offset the offset of the integer to read.
     * @return       a four byte integer value.
     */
    private static int readInt(byte[] data, int offset) {
        int b0 = Byte.toUnsignedInt(data[offset]);
        int b1 = Byte.toUnsignedInt(data[offset + 1]);
        int b2 = Byte.toUnsignedInt(data[offset + 2]);
        int b3 = Byte.toUnsignedInt(data[offset + 3]);

        return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
    }

    /**
     * Writes the value {@code value} to the byte array {@code data} starting
     * from index {@code offset}, or namely, to the bytes {@code data[offset],
     * data[offset + 1], data[offset + 2], data[offset + 3]}, where the least
     * significant byte of the value is stored in the byte {@code data[offset]},
     * i.e., we assume a <b>little-endian</b> machine.
     * 
     * @param data   the array holding the data to write to.
     * @param offset the index of the least significant byte of the target
     *               data integer.
     * @param value  the value to write.
     */
    private static void writeInt(byte[] data, int offset, int value) {
        data[offset] = (byte)(value & 0xff);
        data[offset + 1] = (byte)((value >>> 8) & 0xff);
        data[offset + 2] = (byte)((value >>> 16) & 0xff);
        data[offset + 3] = (byte)((value >>> 24) & 0xff);
    }

    public static void main(final String... args) {
        Random random = new Random();
        byte[] before = new byte[10];
        random.nextBytes(before);

        int cipherKey = random.nextInt();
        byte[] encrypted = encrypt(before, cipherKey);
        byte[] after = decrypt(encrypted, cipherKey);

        System.out.println("Before:    " + Arrays.toString(before));
        System.out.println("Encrypted: " + Arrays.toString(encrypted));
        System.out.println("After:     " + Arrays.toString(after));
        System.out.println("Match: " + Arrays.equals(before, after));
    }
}

CipherToolsTest.java:

package net.coderodde.encryption;

import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
import static org.junit.Assert.*;

public class CipherToolsTest {

    private static final int ITERATIONS = 100;
    private static final int MAXIMUM_LENGTH = 1000;

    @Test
    public void testEncryptionDecryption() {
        long seed = System.nanoTime();
        Random random = new Random(seed);
        System.out.println("Seed = " + seed);

        for (int iteration = 0; iteration < ITERATIONS; iteration++) {
            int cipherKey = random.nextInt();

            if (cipherKey == 0) {
                cipherKey = 1;
            }

            byte[] before = new byte[random.nextInt(MAXIMUM_LENGTH + 1)];
            random.nextBytes(before);

            byte[] encrypted = CipherTools.encrypt(before, cipherKey);
            byte[] after = CipherTools.decrypt(encrypted,  cipherKey);

            assertTrue(Arrays.equals(before, after));
            assertFalse(Arrays.equals(before, encrypted));
        }
    }

    @Test(expected = IllegalArgumentException.class)
    public void testEncryptionThrowsOnZeroCipher() {
        CipherTools.encrypt(new byte[2], 0);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testDecryptionThrowsOnZeroCipher() {
        CipherTools.decrypt(new byte[2], 0);
    }
}

Please, tell me anything that comes to mind. Also, is it easy to crack that cipher if the hacker, say, knows that the file encrypted is a source code file in some particular language?