Unit testing encryption and decryption in Java

2020-06-27 06:03发布

Disclaimer, I am an iOS developer that has been playing around with encryption on Android. As it stands I've managed to achieve encryption in Android but I'm asking myself how would one unit test for encryption and decryption of data?

Now the first idea that comes to mind would be something like:

String encryptedInputData = encryptedInputData("Hello");
String decryptedData = decryptData(encryptedInputData);
Assert.assertEquals(decryptedData,"Hello");

This test however poses one flaw... If something did change in the encryptedInputData and decryptData methods, this test would not tells what changed and why it is now breaking. So I would like to write far more granular tests. So for example given this code:

Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] data = cipher.doFinal(message); 

I'd like to make sure that the cipher variable is using the RSA algorithm in ECB mode with no padding. I'd like to test that the message in the .doFinal(message) follows a particular format etc.

Now I would imagine I would be able to mock the Cipher class, the problem here is that the encryption and decryption that was written, serves only as a Util class and to be able to unit test this, I would have to pass the mock Cipher into the code, which given that this is a Util class seems like it would get messy i.e. I would have to either create an init method just for unit testing purposes or create setter methods just to unit test this. Which would allow me to unit test the code but then the Util class gets clunky with code that I actually don't need for production purposes.

Are there any elegant ways of being able to unit test scenarios like this? i.e. encryptedInputData and decryptData are public methods but these methods use various private methods which frankly need to be unit tested, the issue then is how?

3条回答
Deceive 欺骗
2楼-- · 2020-06-27 06:19

As far as I can see Cipher is in javax.crypto class. So you don't need to mock it.

You can write unit tests to verify that: 1. Key is properly generated and does match requirements. 2. CipherInputStream is successfully created. 3. Copying text to file and reading it after decryption does match:

@Test
public void streamEncryptionDecryptionTest() {

    try {
        File file = tempFolder.newFile("test.txt");

        String content = "This is the text content";
        System.out.println("Original content for file = " + content);


        OutputStream fop = new FileOutputStream(file);

        byte[] key = EncryptionHelper.createKey();

        fop = EncryptionHelper.getInstance(key).getEncryptedOutputStream(fop);

        // if file doesn't exists, then create it
        if (!file.exists()) {
            file.createNewFile();
        }

        // get the content in bytes
        byte[] contentInBytes = content.getBytes();

        fop.write(contentInBytes);
        fop.flush();
        fop.close();

        System.out.println("Encryption Done");

        { // Read content w/o decryption
            InputStream fis = new FileInputStream(file);
            System.out.println("Content w/o decryption");

            int contentChars;
            while ((contentChars = fis.read()) != -1) {
                // convert to char and display it
                System.out.print((char) contentChars);
            }

        }

        { // Decrypted content
            InputStream fis = new FileInputStream(file);
            fis = EncryptionHelper.getInstance(key).getDecryptedInputStream(fis);

            System.out.println("Total file size to read (in bytes) : "
                    + fis.available());

            System.out.println("Decrypted content");

            int contentChars;
            while ((contentChars = fis.read()) != -1) {
                // convert to char and display it
                System.out.print((char) contentChars);
            }
        }


    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
        Assert.assertTrue("security exception", false);
    }
}

Although that test doesn't have explicit Assertion check, you can add:

for (int i = 0; i < buffer.length; i++) {
    byte b = buffer[i];
    System.out.print(b);
    decryptedContent[i] = b;
}

and then check arrays:

Assert.assertArrayEquals(bytesEncoded, decryptedContent);

Keep in mind that you should not compare bytes arrays by converting them to string, since it will give you wrong implementation.

  1. Partial buffer encryption and decryption is properly working. Depending on your needs.
查看更多
相关推荐>>
3楼-- · 2020-06-27 06:34

The real answer is you shouldn't have to. You should never implement your own encryption routines. Not only are you very likely to get it wrong, there are extremely complex things you need to do to make sure it actually isn't hackable due to implementation issues (for example, if one branch of an if statement takes longer to run than another, you can figure out what the value of the check is). You should always use an open source, well reviewed library.

Since you aren't implementing it yourself, you don't need to unit test it. The library writers should be. If you feel like it, run their test suite as part of your own, but I'd consider that a waste of time- they did it before release, and you really only need to run it once at most.

查看更多
劫难
4楼-- · 2020-06-27 06:41

Gabe is correct in his answer about being really really being conservative when re-inventing the wheel; especially when that wheel is about cryptography/security/... such things. Chances are: you will get it wrong. And hackers love people trying to come with their own "secure cryptography".

But to answer you actual question: try go with with TDD (test driven development). You see, your problem is that you created some APIs that are hard to test.

You are correct, you want that "end to end" like test that makes sure that decrypt(encrypt("something")) comes with with "something". Now your problem is: if you only write your code with those two methods in mind; then unit testing is hard.

Thus: right from the beginning, when you consider "which classes do I need"; and "which methods go into which class" you have to focus on "and how do I test that".

In other words: this is one of the occasions where TDD is super important - because you really want to design units that can be tested. And the best practice to get there is: write your tests first. If you design your units so that they can be tested (because you wrote the tests first); surprise - they can be tested. Making them "testable" after the fact is always cumbersome; and most often: close to impossible.

查看更多
登录 后发表回答