Java tutorial
/******************************************************************************* * Copyright (c) 2014 Sebastian Stenzel * This file is licensed under the terms of the MIT license. * See the LICENSE.txt file for more info. * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ package org.cryptomator.crypto.aes256; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.io.IOUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.cryptomator.crypto.CryptorIOSupport; import org.cryptomator.crypto.exceptions.DecryptFailedException; import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException; import org.cryptomator.crypto.exceptions.WrongPasswordException; import org.junit.Assert; import org.junit.Test; public class Aes256CryptorTest { private static final Random TEST_PRNG = new Random(); @Test public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException { final String pw = "asd"; final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); final ByteArrayOutputStream out = new ByteArrayOutputStream(); cryptor.encryptMasterKey(out, pw); cryptor.swipeSensitiveData(); final Aes256Cryptor decryptor = new Aes256Cryptor(TEST_PRNG); final InputStream in = new ByteArrayInputStream(out.toByteArray()); decryptor.decryptMasterKey(in, pw); IOUtils.closeQuietly(out); IOUtils.closeQuietly(in); } @Test public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException { final String pw = "asd"; final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); final ByteArrayOutputStream out = new ByteArrayOutputStream(); cryptor.encryptMasterKey(out, pw); cryptor.swipeSensitiveData(); IOUtils.closeQuietly(out); // all these passwords are expected to fail. final String[] wrongPws = { "a", "as", "asdf", "sdf", "das", "dsa", "foo", "bar", "baz" }; final Aes256Cryptor decryptor = new Aes256Cryptor(TEST_PRNG); for (final String wrongPw : wrongPws) { final InputStream in = new ByteArrayInputStream(out.toByteArray()); try { decryptor.decryptMasterKey(in, wrongPw); Assert.fail("should not succeed."); } catch (WrongPasswordException e) { continue; } finally { IOUtils.closeQuietly(in); } } } @Test public void testIntegrityAuthentication() throws IOException { // our test plaintext data: final byte[] plaintextData = "Hello World".getBytes(); final InputStream plaintextIn = new ByteArrayInputStream(plaintextData); // init cryptor: final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); // encrypt: final ByteBuffer encryptedData = ByteBuffer.allocate(96); final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData); cryptor.encryptFile(plaintextIn, encryptedOut); IOUtils.closeQuietly(plaintextIn); IOUtils.closeQuietly(encryptedOut); encryptedData.position(0); // authenticate unmodified content: final SeekableByteChannel encryptedIn1 = new ByteBufferBackedSeekableChannel(encryptedData); final boolean isContentUnmodified1 = cryptor.authenticateContent(encryptedIn1); Assert.assertTrue(isContentUnmodified1); // toggle one bit inf first content byte: encryptedData.position(64); final byte fifthByte = encryptedData.get(); encryptedData.position(64); encryptedData.put((byte) (fifthByte ^ 0x01)); encryptedData.position(0); // authenticate modified content: final SeekableByteChannel encryptedIn2 = new ByteBufferBackedSeekableChannel(encryptedData); final boolean isContentUnmodified2 = cryptor.authenticateContent(encryptedIn2); Assert.assertFalse(isContentUnmodified2); } @Test public void foo() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException { Security.addProvider(new BouncyCastleProvider()); final byte[] iv = new byte[16]; final byte[] keyBytes = new byte[16]; final SecretKey key = new SecretKeySpec(keyBytes, "AES"); final Cipher pkcs5PaddedCipher = Cipher.getInstance("AES/CTR/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME); pkcs5PaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); final Cipher unpaddedCipher = Cipher.getInstance("AES/CTR/NoPadding"); unpaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); // test data: final byte[] plaintextData = "Hello World".getBytes(); final byte[] pkcs5PaddedCiphertext = pkcs5PaddedCipher.doFinal(plaintextData); final byte[] unpaddedCiphertext = unpaddedCipher.doFinal(plaintextData); Assert.assertFalse(Arrays.equals(pkcs5PaddedCiphertext, unpaddedCiphertext)); } @Test public void testEncryptionAndDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException { // our test plaintext data: final byte[] plaintextData = "Hello World".getBytes(); final InputStream plaintextIn = new ByteArrayInputStream(plaintextData); // init cryptor: final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); // encrypt: final ByteBuffer encryptedData = ByteBuffer.allocate(96); final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData); cryptor.encryptFile(plaintextIn, encryptedOut); IOUtils.closeQuietly(plaintextIn); IOUtils.closeQuietly(encryptedOut); encryptedData.position(0); // decrypt file size: final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData); final Long filesize = cryptor.decryptedContentLength(encryptedIn); Assert.assertEquals(plaintextData.length, filesize.longValue()); // decrypt: final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); final Long numDecryptedBytes = cryptor.decryptedFile(encryptedIn, plaintextOut); IOUtils.closeQuietly(encryptedIn); IOUtils.closeQuietly(plaintextOut); Assert.assertEquals(filesize.longValue(), numDecryptedBytes.longValue()); // check decrypted data: final byte[] result = plaintextOut.toByteArray(); Assert.assertArrayEquals(plaintextData, result); } @Test public void testPartialDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException { // our test plaintext data: final byte[] plaintextData = new byte[65536 * Integer.BYTES]; final ByteBuffer bbIn = ByteBuffer.wrap(plaintextData); for (int i = 0; i < 65536; i++) { bbIn.putInt(i); } final InputStream plaintextIn = new ByteArrayInputStream(plaintextData); // init cryptor: final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); // encrypt: final ByteBuffer encryptedData = ByteBuffer.allocate((int) (64 + plaintextData.length * 1.2)); final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData); cryptor.encryptFile(plaintextIn, encryptedOut); IOUtils.closeQuietly(plaintextIn); IOUtils.closeQuietly(encryptedOut); encryptedData.position(0); // decrypt: final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData); final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); final Long numDecryptedBytes = cryptor.decryptRange(encryptedIn, plaintextOut, 25000 * Integer.BYTES, 30000 * Integer.BYTES); IOUtils.closeQuietly(encryptedIn); IOUtils.closeQuietly(plaintextOut); Assert.assertTrue(numDecryptedBytes > 0); // check decrypted data: final byte[] result = plaintextOut.toByteArray(); final byte[] expected = Arrays.copyOfRange(plaintextData, 25000 * Integer.BYTES, 55000 * Integer.BYTES); Assert.assertArrayEquals(expected, result); } @Test public void testEncryptionOfFilenames() throws IOException { final CryptorIOSupport ioSupportMock = new CryptoIOSupportMock(); final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG); // short path components final String originalPath1 = "foo/bar/baz"; final String encryptedPath1a = cryptor.encryptPath(originalPath1, '/', '/', ioSupportMock); final String encryptedPath1b = cryptor.encryptPath(originalPath1, '/', '/', ioSupportMock); Assert.assertEquals(encryptedPath1a, encryptedPath1b); final String decryptedPath1 = cryptor.decryptPath(encryptedPath1a, '/', '/', ioSupportMock); Assert.assertEquals(originalPath1, decryptedPath1); // long path components final String str50chars = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee"; final String originalPath2 = "foo/" + str50chars + str50chars + str50chars + str50chars + str50chars + "/baz"; final String encryptedPath2a = cryptor.encryptPath(originalPath2, '/', '/', ioSupportMock); final String encryptedPath2b = cryptor.encryptPath(originalPath2, '/', '/', ioSupportMock); Assert.assertEquals(encryptedPath2a, encryptedPath2b); final String decryptedPath2 = cryptor.decryptPath(encryptedPath2a, '/', '/', ioSupportMock); Assert.assertEquals(originalPath2, decryptedPath2); // block size length path components final String originalPath3 = "aaaabbbbccccdddd"; final String encryptedPath3a = cryptor.encryptPath(originalPath3, '/', '/', ioSupportMock); final String encryptedPath3b = cryptor.encryptPath(originalPath3, '/', '/', ioSupportMock); Assert.assertEquals(encryptedPath3a, encryptedPath3b); final String decryptedPath3 = cryptor.decryptPath(encryptedPath3a, '/', '/', ioSupportMock); Assert.assertEquals(originalPath3, decryptedPath3); } private static class CryptoIOSupportMock implements CryptorIOSupport { private final Map<String, byte[]> map = new HashMap<>(); @Override public void writePathSpecificMetadata(String encryptedPath, byte[] encryptedMetadata) { map.put(encryptedPath, encryptedMetadata); } @Override public byte[] readPathSpecificMetadata(String encryptedPath) { return map.get(encryptedPath); } } }