diff --git a/build.gradle.kts b/build.gradle.kts index 592b01a..939786c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ repositories { dependencies { testImplementation("org.assertj:assertj-core:3.20.2") testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.1") } configure { diff --git a/src/main/java/com/github/alexeylapin/eme/EME.java b/src/main/java/com/github/alexeylapin/eme/EME.java index 01906c7..eb3afbf 100644 --- a/src/main/java/com/github/alexeylapin/eme/EME.java +++ b/src/main/java/com/github/alexeylapin/eme/EME.java @@ -1,18 +1,21 @@ package com.github.alexeylapin.eme; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + public interface EME { static EME fromKey(byte[] key) throws Exception { return new EMEImpl(key); } - byte[] transform(byte[] tweak, byte[] inputData, Mode mode) throws Exception; + byte[] transform(byte[] tweak, byte[] inputData, Mode mode) throws IllegalBlockSizeException, BadPaddingException; - default byte[] encrypt(byte[] tweak, byte[] inputData) throws Exception { + default byte[] encrypt(byte[] tweak, byte[] inputData) throws IllegalBlockSizeException, BadPaddingException { return transform(tweak, inputData, Mode.ENCRYPT); } - default byte[] decrypt(byte[] tweak, byte[] inputData) throws Exception { + default byte[] decrypt(byte[] tweak, byte[] inputData) throws IllegalBlockSizeException, BadPaddingException { return transform(tweak, inputData, Mode.DECRYPT); } diff --git a/src/main/java/com/github/alexeylapin/eme/EMEImpl.java b/src/main/java/com/github/alexeylapin/eme/EMEImpl.java index 74ea4e0..fa5d52b 100644 --- a/src/main/java/com/github/alexeylapin/eme/EMEImpl.java +++ b/src/main/java/com/github/alexeylapin/eme/EMEImpl.java @@ -1,13 +1,18 @@ package com.github.alexeylapin.eme; +import javax.crypto.BadPaddingException; import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; public class EMEImpl implements EME { - private static final String AES = "AES"; - private static final String AES_ECB_NO_PADDING = "AES/ECB/NoPadding"; + public static final String AES = "AES"; + public static final String AES_ECB_NO_PADDING = "AES/ECB/NoPadding"; private final Cipher encryptor; private final Cipher decryptor; @@ -17,7 +22,7 @@ public EMEImpl(Cipher encryptor, Cipher decryptor) { this.decryptor = decryptor; } - public EMEImpl(byte[] key) throws Exception { + public EMEImpl(byte[] key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec skSpec = new SecretKeySpec(key, AES); this.encryptor = Cipher.getInstance(AES_ECB_NO_PADDING); encryptor.init(Cipher.ENCRYPT_MODE, skSpec); @@ -25,7 +30,8 @@ public EMEImpl(byte[] key) throws Exception { decryptor.init(Cipher.DECRYPT_MODE, skSpec); } - public byte[] transform(byte[] tweak, byte[] inputData, Mode mode) throws Exception { + public byte[] transform(byte[] tweak, byte[] inputData, Mode mode) + throws IllegalBlockSizeException, BadPaddingException { byte[] T = tweak; byte[] P = inputData; if (T.length != 16) { @@ -90,12 +96,12 @@ public byte[] transform(byte[] tweak, byte[] inputData, Mode mode) throws Except return C; } - private byte[] aesTransform(byte[] src, Mode mode) throws Exception { + private byte[] aesTransform(byte[] src, Mode mode) throws IllegalBlockSizeException, BadPaddingException { Cipher cipher = Mode.ENCRYPT == mode ? encryptor : decryptor; return cipher.doFinal(src); } - private byte[][] tabulateL(int m) throws Exception { + private byte[][] tabulateL(int m) throws IllegalBlockSizeException, BadPaddingException { byte[] eZero = new byte[16]; byte[] Li = encryptor.doFinal(eZero); byte[][] LTable = new byte[m][]; diff --git a/src/main/java/com/github/alexeylapin/eme/cipher/AES128EME.java b/src/main/java/com/github/alexeylapin/eme/cipher/AES128EME.java new file mode 100644 index 0000000..28c6af8 --- /dev/null +++ b/src/main/java/com/github/alexeylapin/eme/cipher/AES128EME.java @@ -0,0 +1,197 @@ +package com.github.alexeylapin.eme.cipher; + +import com.github.alexeylapin.eme.EME; +import com.github.alexeylapin.eme.EMEImpl; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +public class AES128EME extends CipherSpi { + + private EME eme; + private EME.Mode mode; + private byte[] iv; + private PKCS7Padding padding; + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + if (!"EME".equalsIgnoreCase(mode)) { + throw new NoSuchAlgorithmException("mode " + mode + " not supported"); + } + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + if (!"PKCS7Padding".equalsIgnoreCase(padding)) { + throw new NoSuchPaddingException("padding " + padding + " not supported"); + } + } + + @Override + protected int engineGetBlockSize() { + return 16; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + if (mode == EME.Mode.ENCRYPT) { + int remainder = inputLen % engineGetBlockSize(); + return inputLen + engineGetBlockSize() - remainder; + } else if (mode == EME.Mode.DECRYPT) { + return inputLen; + } else { + throw new IllegalStateException("Cipher not initialized"); + } + } + + @Override + protected byte[] engineGetIV() { + if (iv == null) { + return null; + } + return Arrays.copyOf(iv, iv.length); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) { + throw new UnsupportedOperationException(); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + IvParameterSpec ivParameterSpec = (IvParameterSpec) params; + iv = ivParameterSpec.getIV(); + mode = opmode == 1 ? EME.Mode.ENCRYPT : EME.Mode.DECRYPT; + padding = new PKCS7Padding(engineGetBlockSize()); + + Cipher encryptor; + try { + encryptor = Cipher.getInstance(EMEImpl.AES_ECB_NO_PADDING); + encryptor.init(Cipher.ENCRYPT_MODE, key); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new InvalidAlgorithmParameterException("failed to initialize encryptor", e); + } + + Cipher decryptor; + try { + decryptor = Cipher.getInstance(EMEImpl.AES_ECB_NO_PADDING); + decryptor.init(Cipher.DECRYPT_MODE, key); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new InvalidAlgorithmParameterException("failed to initialize decryptor", e); + } + + eme = new EMEImpl(encryptor, decryptor); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, SecureRandom random) { + throw new UnsupportedOperationException(); + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + try { + return transform(input, inputOffset, inputLen); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new ProviderException(e); + } + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException { + byte[] transformedInput = engineUpdate(input, inputOffset, inputLen); + if (outputOffset + transformedInput.length > output.length) { + throw new ShortBufferException("Output buffer too short for transformation result"); + } + System.arraycopy(transformedInput, 0, output, outputOffset, transformedInput.length); + return transformedInput.length; + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + if (input == null) { + input = new byte[0]; + inputOffset = 0; + inputLen = 0; + } + return transform(input, inputOffset, inputLen); + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + byte[] transformedInput = engineDoFinal(input, inputOffset, inputLen); + if (outputOffset + transformedInput.length > output.length) { + throw new ShortBufferException("Output buffer too short for transformation result"); + } + System.arraycopy(transformedInput, 0, output, outputOffset, transformedInput.length); + return transformedInput.length; + } + + private byte[] transform(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { + byte[] transform; + byte[] data = Arrays.copyOfRange(input, inputOffset, inputOffset + inputLen); + if (mode == EME.Mode.ENCRYPT) { + data = pad(data); + } + transform = eme.transform(iv, data, mode); + if (mode == EME.Mode.DECRYPT) { + transform = unpad(transform); + } + return transform; + } + + private byte[] pad(byte[] input) { + int blockSize = engineGetBlockSize(); + int length = input.length; + int remainder = length % blockSize; + int paddingLength = blockSize - remainder; + int paddedLength = length + paddingLength; + + byte[] padded = Arrays.copyOf(input, paddedLength); + if (paddingLength > 0) { + try { + padding.padWithLen(padded, length, paddingLength); + } catch (ShortBufferException e) { + // should never happen + throw new ProviderException("Unexpected exception", e); + } + } + return padded; + } + + private byte[] unpad(byte[] input) { + int unpadIndex = padding.unpad(input, input.length - 16, 16); + byte[] unpadded; + if (unpadIndex >= 0) { + unpadded = new byte[unpadIndex]; + System.arraycopy(input, 0, unpadded, 0, unpadIndex); + } else { + unpadded = input; + } + return unpadded; + } + +} diff --git a/src/main/java/com/github/alexeylapin/eme/cipher/AES128EMEProvider.java b/src/main/java/com/github/alexeylapin/eme/cipher/AES128EMEProvider.java new file mode 100644 index 0000000..e6b6e54 --- /dev/null +++ b/src/main/java/com/github/alexeylapin/eme/cipher/AES128EMEProvider.java @@ -0,0 +1,14 @@ +package com.github.alexeylapin.eme.cipher; + +import java.security.Provider; + +public class AES128EMEProvider extends Provider { + + public AES128EMEProvider() { + super("AES128EME", 1.0, "AES128EME Java Security Provider"); + put("Cipher.AES", AES128EME.class.getName()); + put("Cipher.AES SupportedPaddings", "PKCS7PADDING"); + put("Cipher.AES SupportedModes", "EME"); + } + +} diff --git a/src/main/java/com/github/alexeylapin/eme/cipher/PKCS7Padding.java b/src/main/java/com/github/alexeylapin/eme/cipher/PKCS7Padding.java new file mode 100644 index 0000000..72cfaf9 --- /dev/null +++ b/src/main/java/com/github/alexeylapin/eme/cipher/PKCS7Padding.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.github.alexeylapin.eme.cipher; + + +import javax.crypto.ShortBufferException; +import java.util.Arrays; + + +/** + * This class implements the PKCS7 padding scheme. + */ +public class PKCS7Padding { + + private final int blockSize; + + PKCS7Padding(int blockSize) { + this.blockSize = blockSize; + } + + /** + * Adds the given number of padding bytes to the data input. + * The value of the padding bytes is determined + * by the specific padding mechanism that implements this + * interface. + * + * @param in the input buffer with the data to pad + * @param off the offset in in where the padding bytes + * are appended + * @param len the number of padding bytes to add + * + * @exception ShortBufferException if in is too small to hold + * the padding bytes + */ + public void padWithLen(byte[] in, int off, int len) + throws ShortBufferException + { + if (in == null) + return; + + int idx = Math.addExact(off, len); + if (idx > in.length) { + throw new ShortBufferException("Buffer too small to hold padding"); + } + + byte paddingOctet = (byte) (len & 0xff); + Arrays.fill(in, off, idx, paddingOctet); + return; + } + + /** + * Returns the index where the padding starts. + * + *

Given a buffer with padded data, this method returns the + * index where the padding starts. + * + * @param in the buffer with the padded data + * @param off the offset in in where the padded data starts + * @param len the length of the padded data + * + * @return the index where the padding starts, or -1 if the input is + * not properly padded + */ + public int unpad(byte[] in, int off, int len) { + if ((in == null) || + (len == 0)) { // this can happen if input is really a padded buffer + return 0; + } + int idx = Math.addExact(off, len); + byte lastByte = in[idx - 1]; + int padValue = (int)lastByte & 0x0ff; + if ((padValue < 0x01) + || (padValue > blockSize)) { + return -1; + } + + int start = idx - padValue; + if (start < off) { + return -1; + } + + for (int i = start; i < idx; i++) { + if (in[i] != lastByte) { + return -1; + } + } + return start; + } + + /** + * Determines how long the padding will be for a given input length. + * + * @param len the length of the data to pad + * + * @return the length of the padding + */ + public int padLength(int len) { + int paddingOctet = blockSize - (len % blockSize); + return paddingOctet; + } + +} diff --git a/src/test/java/com/github/alexeylapin/eme/EMEImplTest.java b/src/test/java/com/github/alexeylapin/eme/EMEImplTest.java index ce90ed7..a0f2d3e 100644 --- a/src/test/java/com/github/alexeylapin/eme/EMEImplTest.java +++ b/src/test/java/com/github/alexeylapin/eme/EMEImplTest.java @@ -3,81 +3,9 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; -import java.util.concurrent.ThreadLocalRandom; - import static org.assertj.core.api.Assertions.assertThat; -public class EMEImplTest { - - // EME-32 encryption test vector from http://grouper.ieee.org/groups/1619/email/pdf00020.pdf - byte[] buf9F2E = toBytes(new int[]{ - 0x9F, 0x2E, 0x6C, 0x3D, 0xAE, 0xCA, 0xE7, 0x9E, 0x88, 0x39, 0xB0, 0x58, 0x8F, 0xF3, 0x78, 0xCD, - 0x06, 0x68, 0x97, 0x0B, 0x95, 0x69, 0x1C, 0xB0, 0x01, 0x82, 0xB9, 0xE3, 0x4C, 0xD6, 0x58, 0xED, - 0x3C, 0x9C, 0x27, 0x68, 0x38, 0xCC, 0x5E, 0x14, 0x11, 0xFC, 0xB8, 0xCF, 0x3D, 0xA1, 0xC0, 0xF3, - 0x08, 0x75, 0x80, 0x4C, 0x9D, 0xF5, 0x11, 0x57, 0xB0, 0x79, 0x11, 0x00, 0xD2, 0x55, 0x13, 0x34, - 0x83, 0x4C, 0xF4, 0x02, 0x4F, 0x6B, 0x71, 0x8F, 0xBC, 0x7D, 0xAB, 0xA0, 0x7D, 0x14, 0xEB, 0x7C, - 0xBC, 0x79, 0xC2, 0x61, 0xB1, 0xEB, 0x03, 0x6D, 0x0C, 0x9F, 0x85, 0xB9, 0x14, 0x38, 0x58, 0x40, - 0x72, 0x72, 0x84, 0x00, 0x5F, 0x06, 0xA9, 0xC1, 0x62, 0x7C, 0x0B, 0x7F, 0xB1, 0x2A, 0x1F, 0x81, - 0xFA, 0x83, 0xC4, 0xB0, 0x35, 0xDB, 0x00, 0x6C, 0xCE, 0x84, 0x6D, 0x07, 0x56, 0xDB, 0x9F, 0xB2, - 0x44, 0x8E, 0xE5, 0x62, 0x8D, 0x23, 0x76, 0xEE, 0x13, 0x95, 0x42, 0x13, 0xDB, 0x3D, 0xCA, 0x72, - 0x5F, 0x2C, 0x67, 0x95, 0x0E, 0xAF, 0x2C, 0xDA, 0xC8, 0xA2, 0x7A, 0x04, 0x33, 0xA1, 0x4C, 0x96, - 0x92, 0x7D, 0x91, 0x45, 0xDD, 0x93, 0xE0, 0xB4, 0x6E, 0x67, 0x0F, 0x6C, 0x4D, 0xB8, 0xAD, 0xD0, - 0x14, 0xB8, 0x88, 0x0E, 0xFB, 0x9A, 0x97, 0xBE, 0xC5, 0xCD, 0x05, 0xBB, 0xA4, 0x3D, 0xCC, 0x35, - 0x05, 0x80, 0x45, 0xAE, 0x81, 0x68, 0xDF, 0x6E, 0x67, 0x77, 0x91, 0x98, 0xFC, 0xC7, 0x28, 0x08, - 0xCE, 0x29, 0xC7, 0xB5, 0xAE, 0xFD, 0xBC, 0x9E, 0x3E, 0xE6, 0x51, 0x17, 0x28, 0x3B, 0xFA, 0x2E, - 0x19, 0x5F, 0x82, 0xCE, 0x19, 0x62, 0xDD, 0x81, 0x12, 0xCB, 0x57, 0xE8, 0x04, 0x0D, 0x77, 0x67, - 0x33, 0xD3, 0xBB, 0x33, 0x1E, 0xA6, 0x30, 0x0F, 0x91, 0xDE, 0xE0, 0xCB, 0xEB, 0x2F, 0xC9, 0xAF, - 0xD3, 0x41, 0xF5, 0x51, 0x5E, 0x22, 0x37, 0x1E, 0x44, 0x2B, 0x86, 0xE7, 0x02, 0x87, 0x54, 0x6A, - 0x16, 0x6E, 0xC2, 0xAE, 0xF8, 0x9F, 0x29, 0x1B, 0xE6, 0x2A, 0xFC, 0x2A, 0x96, 0x89, 0x1E, 0x44, - 0x6E, 0xF6, 0xF1, 0x62, 0x73, 0x55, 0x74, 0xD1, 0x0C, 0xFF, 0x4A, 0x18, 0x3D, 0xE2, 0x76, 0x0B, - 0x5E, 0x14, 0x5D, 0xEA, 0xAD, 0x3E, 0xFD, 0xE1, 0xDA, 0x4B, 0x28, 0x36, 0xC6, 0x65, 0xC5, 0xEC, - 0x4B, 0x54, 0xCB, 0x98, 0x9D, 0x27, 0x73, 0x11, 0xC4, 0x2D, 0xB4, 0x86, 0x2D, 0xB2, 0x92, 0x0C, - 0x39, 0x42, 0x95, 0x8E, 0x54, 0xF6, 0x4E, 0x36, 0x5E, 0x52, 0x19, 0x0E, 0xD8, 0x1A, 0x02, 0xD7, - 0x3B, 0xF7, 0x8A, 0x8A, 0xE5, 0xCC, 0x83, 0xE0, 0x32, 0x03, 0xEF, 0x42, 0x16, 0x14, 0xB7, 0x9A, - 0xE9, 0x84, 0xB6, 0x7E, 0xE9, 0x34, 0x83, 0xD5, 0xEB, 0x1E, 0xA7, 0xB4, 0xFD, 0x95, 0x4C, 0xC3, - 0x50, 0x59, 0xBD, 0x4D, 0x93, 0x2E, 0xF3, 0x42, 0x71, 0x82, 0x50, 0x45, 0xD7, 0x3E, 0xFF, 0xEF, - 0x2E, 0xD3, 0x48, 0x98, 0x71, 0xFD, 0xA2, 0xCC, 0x73, 0x92, 0x4B, 0x4D, 0x45, 0x9D, 0x1C, 0x6E, - 0xE5, 0x25, 0x42, 0x1E, 0x05, 0x50, 0xD3, 0xAB, 0x87, 0x6F, 0x61, 0x53, 0x95, 0xAC, 0x4A, 0x54, - 0xD2, 0x04, 0x78, 0xA4, 0x42, 0xD8, 0x5C, 0x9A, 0x3C, 0x9C, 0x7F, 0xA1, 0x48, 0xF2, 0xB9, 0xDC, - 0xAD, 0xAA, 0x83, 0xCF, 0x40, 0xE9, 0xE4, 0x64, 0xDA, 0x60, 0x36, 0xA5, 0x5C, 0xDB, 0x87, 0x3B, - 0x50, 0xC1, 0x06, 0x0E, 0xCC, 0x27, 0xB4, 0x8D, 0xC0, 0xAF, 0xC7, 0x6E, 0xF7, 0x3F, 0x14, 0x89, - 0x28, 0x1C, 0x08, 0xEF, 0xCE, 0x7F, 0xEC, 0x47, 0xED, 0xD8, 0x23, 0xF2, 0xF5, 0x62, 0xB3, 0x33, - 0xAC, 0x20, 0x9C, 0x2C, 0xD3, 0xCC, 0x57, 0x7C, 0x28, 0xEE, 0xDA, 0xAF, 0xCE, 0xDD, 0x89, 0xA6}); - - // EME-32 decryption test vector from http://grouper.ieee.org/groups/1619/email/pdf00020.pdf - byte[] buf0809 = toBytes(new int[]{ - 0x08, 0x09, 0x05, 0xDE, 0xE8, 0xEB, 0xCC, 0x89, 0xF6, 0x8B, 0xD1, 0xAF, 0x63, 0x5D, 0xB3, 0xF5, - 0xB6, 0x0C, 0x2F, 0x13, 0xF7, 0xC7, 0x68, 0xFC, 0xEB, 0x12, 0x20, 0xF6, 0xC2, 0x27, 0xFD, 0x83, - 0x5F, 0x29, 0x3E, 0x85, 0xF1, 0xEA, 0xA8, 0xEE, 0x23, 0x22, 0xF5, 0x42, 0x91, 0xBF, 0x05, 0x1E, - 0x7B, 0x15, 0xAF, 0x84, 0xC7, 0xEA, 0xA4, 0xE8, 0x51, 0x58, 0xAF, 0x7F, 0x4E, 0x6F, 0xF2, 0x4A, - 0x62, 0xBA, 0xCF, 0xF6, 0xDB, 0xF9, 0x1F, 0x43, 0x3F, 0x3B, 0xD5, 0x64, 0xDF, 0xFB, 0xE9, 0xFE, - 0x1B, 0x0E, 0x14, 0xD2, 0x76, 0x87, 0x58, 0x94, 0x98, 0xD5, 0xE8, 0xCA, 0x11, 0xAC, 0xBA, 0x2B, - 0xC6, 0x01, 0x6D, 0x78, 0x23, 0xE3, 0x03, 0x6C, 0x61, 0xCE, 0x97, 0x77, 0xEC, 0x24, 0x45, 0x89, - 0x07, 0x79, 0x02, 0x7F, 0x7D, 0x49, 0x48, 0x93, 0xD9, 0x2F, 0x19, 0xBD, 0xFE, 0x16, 0x0E, 0xF8, - 0x2C, 0x36, 0x06, 0x9C, 0xA8, 0x87, 0xD8, 0x4E, 0xA0, 0x0C, 0xCC, 0x40, 0x13, 0x0C, 0xF7, 0xC4, - 0x11, 0x8C, 0x5D, 0x08, 0x22, 0xA5, 0xE1, 0xF4, 0x93, 0xCD, 0xAE, 0x96, 0xF5, 0x75, 0x20, 0x31, - 0xB4, 0x53, 0xE4, 0xCB, 0x86, 0x08, 0xC8, 0xF2, 0xBA, 0x2C, 0x78, 0xC9, 0x41, 0x12, 0x4C, 0x18, - 0xE3, 0x9F, 0x50, 0xAB, 0x74, 0xB8, 0x31, 0x47, 0xAA, 0x3F, 0xB8, 0x00, 0x53, 0x7E, 0xB9, 0xAC, - 0x55, 0xD7, 0x37, 0x55, 0x2E, 0x05, 0x03, 0x75, 0xF6, 0x07, 0xC5, 0x9B, 0x42, 0x13, 0xD8, 0x7E, - 0x58, 0xE8, 0xDA, 0x6E, 0x23, 0x02, 0x9C, 0x9C, 0xB8, 0x07, 0xAC, 0x63, 0x13, 0x3B, 0x9F, 0xDD, - 0xDA, 0xD8, 0x71, 0x2B, 0xD7, 0x82, 0x11, 0x37, 0xD9, 0xF8, 0xFD, 0xC3, 0xE2, 0x8A, 0xEB, 0x08, - 0xEE, 0x2F, 0xAE, 0x3E, 0xC1, 0xF8, 0x0D, 0x91, 0x26, 0xA3, 0xD2, 0xD0, 0xE4, 0xE4, 0xF1, 0xC6, - 0x42, 0x4C, 0xE6, 0xB5, 0xE9, 0x73, 0xE5, 0x27, 0x03, 0xAF, 0xB3, 0x1C, 0xEE, 0x79, 0x90, 0xDA, - 0x82, 0xB3, 0x16, 0x18, 0x9A, 0xD1, 0x6F, 0xE0, 0x59, 0x92, 0x1C, 0x60, 0xA9, 0x5A, 0x12, 0x08, - 0x71, 0x06, 0x5B, 0x9E, 0xD6, 0x49, 0xD2, 0x11, 0x7D, 0xFB, 0x0C, 0xE5, 0xB5, 0x35, 0x95, 0x11, - 0x9F, 0x21, 0x77, 0xBE, 0xA4, 0x62, 0xF7, 0x66, 0x60, 0xC6, 0xA0, 0x7C, 0x81, 0x0D, 0x21, 0xE1, - 0x85, 0xE2, 0xDA, 0xE5, 0x59, 0xC2, 0x7F, 0x14, 0x09, 0x3F, 0x21, 0xA9, 0x6D, 0x4E, 0x2A, 0x81, - 0x41, 0xD7, 0x6A, 0x3F, 0x96, 0x4A, 0xA7, 0x0B, 0xF7, 0xE9, 0x29, 0xE7, 0x32, 0x24, 0xBD, 0x9F, - 0x17, 0x19, 0xFD, 0xFF, 0x96, 0xBF, 0x4C, 0xA5, 0xDB, 0x51, 0x66, 0x27, 0x22, 0x57, 0x60, 0xF3, - 0xD2, 0xD8, 0x67, 0x0A, 0x4B, 0x82, 0xE1, 0x6A, 0x8B, 0x43, 0x58, 0xEC, 0xD7, 0x81, 0xB0, 0xEE, - 0xA2, 0x2A, 0x29, 0xD0, 0x76, 0x44, 0x24, 0xE9, 0x1E, 0x3D, 0xC7, 0xA6, 0xA1, 0xCE, 0xDD, 0x14, - 0x8C, 0x4B, 0xBB, 0x1B, 0x52, 0x4B, 0x9C, 0x8D, 0xD3, 0xF3, 0xD1, 0x53, 0x40, 0x77, 0x5F, 0xE9, - 0xC9, 0x8E, 0xEC, 0x22, 0x0B, 0x52, 0x4A, 0x8D, 0x95, 0x95, 0xD2, 0xF4, 0x3C, 0x67, 0x83, 0xE6, - 0x03, 0xA3, 0x5B, 0x8D, 0xF9, 0x6A, 0x16, 0x89, 0x75, 0xAC, 0xF5, 0xAC, 0x4E, 0xA4, 0x7E, 0x02, - 0xB7, 0x3A, 0x8C, 0xE6, 0xAF, 0xF8, 0xE5, 0x2D, 0xAD, 0x76, 0x89, 0x79, 0xBD, 0x73, 0x92, 0xB3, - 0x05, 0x0D, 0xD3, 0xB4, 0xE4, 0x79, 0x0E, 0x25, 0xE9, 0xA3, 0x4E, 0xE6, 0x07, 0xDB, 0x5A, 0x58, - 0x5D, 0x16, 0xCA, 0x6B, 0x16, 0xAA, 0x76, 0x37, 0x2A, 0xB4, 0x9E, 0x31, 0xDF, 0x48, 0x65, 0x07, - 0x3A, 0xF8, 0x04, 0xA5, 0xC9, 0xDA, 0xB3, 0x44, 0x20, 0xF2, 0x60, 0xE4, 0xBD, 0x84, 0x08, 0x29}); +public class EMEImplTest extends EMETestSupport { @Test void encrypt() throws Exception { @@ -217,17 +145,4 @@ void decrypt100times() throws Exception { assertThat(out).containsExactly(expected); } - private byte[] randomBytes(byte[] bytes) { - ThreadLocalRandom.current().nextBytes(bytes); - return bytes; - } - - private byte[] toBytes(int[] ints) { - byte[] bytes = new byte[ints.length]; - for (int i = 0; i < ints.length; i++) { - bytes[i] = (byte) ints[i]; - } - return bytes; - } - } diff --git a/src/test/java/com/github/alexeylapin/eme/EMETestSupport.java b/src/test/java/com/github/alexeylapin/eme/EMETestSupport.java new file mode 100644 index 0000000..a673650 --- /dev/null +++ b/src/test/java/com/github/alexeylapin/eme/EMETestSupport.java @@ -0,0 +1,90 @@ +package com.github.alexeylapin.eme; + +import java.util.concurrent.ThreadLocalRandom; + +public class EMETestSupport { + + // EME-32 encryption test vector from http://grouper.ieee.org/groups/1619/email/pdf00020.pdf + static byte[] buf9F2E = toBytes(new int[]{ + 0x9F, 0x2E, 0x6C, 0x3D, 0xAE, 0xCA, 0xE7, 0x9E, 0x88, 0x39, 0xB0, 0x58, 0x8F, 0xF3, 0x78, 0xCD, + 0x06, 0x68, 0x97, 0x0B, 0x95, 0x69, 0x1C, 0xB0, 0x01, 0x82, 0xB9, 0xE3, 0x4C, 0xD6, 0x58, 0xED, + 0x3C, 0x9C, 0x27, 0x68, 0x38, 0xCC, 0x5E, 0x14, 0x11, 0xFC, 0xB8, 0xCF, 0x3D, 0xA1, 0xC0, 0xF3, + 0x08, 0x75, 0x80, 0x4C, 0x9D, 0xF5, 0x11, 0x57, 0xB0, 0x79, 0x11, 0x00, 0xD2, 0x55, 0x13, 0x34, + 0x83, 0x4C, 0xF4, 0x02, 0x4F, 0x6B, 0x71, 0x8F, 0xBC, 0x7D, 0xAB, 0xA0, 0x7D, 0x14, 0xEB, 0x7C, + 0xBC, 0x79, 0xC2, 0x61, 0xB1, 0xEB, 0x03, 0x6D, 0x0C, 0x9F, 0x85, 0xB9, 0x14, 0x38, 0x58, 0x40, + 0x72, 0x72, 0x84, 0x00, 0x5F, 0x06, 0xA9, 0xC1, 0x62, 0x7C, 0x0B, 0x7F, 0xB1, 0x2A, 0x1F, 0x81, + 0xFA, 0x83, 0xC4, 0xB0, 0x35, 0xDB, 0x00, 0x6C, 0xCE, 0x84, 0x6D, 0x07, 0x56, 0xDB, 0x9F, 0xB2, + 0x44, 0x8E, 0xE5, 0x62, 0x8D, 0x23, 0x76, 0xEE, 0x13, 0x95, 0x42, 0x13, 0xDB, 0x3D, 0xCA, 0x72, + 0x5F, 0x2C, 0x67, 0x95, 0x0E, 0xAF, 0x2C, 0xDA, 0xC8, 0xA2, 0x7A, 0x04, 0x33, 0xA1, 0x4C, 0x96, + 0x92, 0x7D, 0x91, 0x45, 0xDD, 0x93, 0xE0, 0xB4, 0x6E, 0x67, 0x0F, 0x6C, 0x4D, 0xB8, 0xAD, 0xD0, + 0x14, 0xB8, 0x88, 0x0E, 0xFB, 0x9A, 0x97, 0xBE, 0xC5, 0xCD, 0x05, 0xBB, 0xA4, 0x3D, 0xCC, 0x35, + 0x05, 0x80, 0x45, 0xAE, 0x81, 0x68, 0xDF, 0x6E, 0x67, 0x77, 0x91, 0x98, 0xFC, 0xC7, 0x28, 0x08, + 0xCE, 0x29, 0xC7, 0xB5, 0xAE, 0xFD, 0xBC, 0x9E, 0x3E, 0xE6, 0x51, 0x17, 0x28, 0x3B, 0xFA, 0x2E, + 0x19, 0x5F, 0x82, 0xCE, 0x19, 0x62, 0xDD, 0x81, 0x12, 0xCB, 0x57, 0xE8, 0x04, 0x0D, 0x77, 0x67, + 0x33, 0xD3, 0xBB, 0x33, 0x1E, 0xA6, 0x30, 0x0F, 0x91, 0xDE, 0xE0, 0xCB, 0xEB, 0x2F, 0xC9, 0xAF, + 0xD3, 0x41, 0xF5, 0x51, 0x5E, 0x22, 0x37, 0x1E, 0x44, 0x2B, 0x86, 0xE7, 0x02, 0x87, 0x54, 0x6A, + 0x16, 0x6E, 0xC2, 0xAE, 0xF8, 0x9F, 0x29, 0x1B, 0xE6, 0x2A, 0xFC, 0x2A, 0x96, 0x89, 0x1E, 0x44, + 0x6E, 0xF6, 0xF1, 0x62, 0x73, 0x55, 0x74, 0xD1, 0x0C, 0xFF, 0x4A, 0x18, 0x3D, 0xE2, 0x76, 0x0B, + 0x5E, 0x14, 0x5D, 0xEA, 0xAD, 0x3E, 0xFD, 0xE1, 0xDA, 0x4B, 0x28, 0x36, 0xC6, 0x65, 0xC5, 0xEC, + 0x4B, 0x54, 0xCB, 0x98, 0x9D, 0x27, 0x73, 0x11, 0xC4, 0x2D, 0xB4, 0x86, 0x2D, 0xB2, 0x92, 0x0C, + 0x39, 0x42, 0x95, 0x8E, 0x54, 0xF6, 0x4E, 0x36, 0x5E, 0x52, 0x19, 0x0E, 0xD8, 0x1A, 0x02, 0xD7, + 0x3B, 0xF7, 0x8A, 0x8A, 0xE5, 0xCC, 0x83, 0xE0, 0x32, 0x03, 0xEF, 0x42, 0x16, 0x14, 0xB7, 0x9A, + 0xE9, 0x84, 0xB6, 0x7E, 0xE9, 0x34, 0x83, 0xD5, 0xEB, 0x1E, 0xA7, 0xB4, 0xFD, 0x95, 0x4C, 0xC3, + 0x50, 0x59, 0xBD, 0x4D, 0x93, 0x2E, 0xF3, 0x42, 0x71, 0x82, 0x50, 0x45, 0xD7, 0x3E, 0xFF, 0xEF, + 0x2E, 0xD3, 0x48, 0x98, 0x71, 0xFD, 0xA2, 0xCC, 0x73, 0x92, 0x4B, 0x4D, 0x45, 0x9D, 0x1C, 0x6E, + 0xE5, 0x25, 0x42, 0x1E, 0x05, 0x50, 0xD3, 0xAB, 0x87, 0x6F, 0x61, 0x53, 0x95, 0xAC, 0x4A, 0x54, + 0xD2, 0x04, 0x78, 0xA4, 0x42, 0xD8, 0x5C, 0x9A, 0x3C, 0x9C, 0x7F, 0xA1, 0x48, 0xF2, 0xB9, 0xDC, + 0xAD, 0xAA, 0x83, 0xCF, 0x40, 0xE9, 0xE4, 0x64, 0xDA, 0x60, 0x36, 0xA5, 0x5C, 0xDB, 0x87, 0x3B, + 0x50, 0xC1, 0x06, 0x0E, 0xCC, 0x27, 0xB4, 0x8D, 0xC0, 0xAF, 0xC7, 0x6E, 0xF7, 0x3F, 0x14, 0x89, + 0x28, 0x1C, 0x08, 0xEF, 0xCE, 0x7F, 0xEC, 0x47, 0xED, 0xD8, 0x23, 0xF2, 0xF5, 0x62, 0xB3, 0x33, + 0xAC, 0x20, 0x9C, 0x2C, 0xD3, 0xCC, 0x57, 0x7C, 0x28, 0xEE, 0xDA, 0xAF, 0xCE, 0xDD, 0x89, 0xA6}); + + // EME-32 decryption test vector from http://grouper.ieee.org/groups/1619/email/pdf00020.pdf + static byte[] buf0809 = toBytes(new int[]{ + 0x08, 0x09, 0x05, 0xDE, 0xE8, 0xEB, 0xCC, 0x89, 0xF6, 0x8B, 0xD1, 0xAF, 0x63, 0x5D, 0xB3, 0xF5, + 0xB6, 0x0C, 0x2F, 0x13, 0xF7, 0xC7, 0x68, 0xFC, 0xEB, 0x12, 0x20, 0xF6, 0xC2, 0x27, 0xFD, 0x83, + 0x5F, 0x29, 0x3E, 0x85, 0xF1, 0xEA, 0xA8, 0xEE, 0x23, 0x22, 0xF5, 0x42, 0x91, 0xBF, 0x05, 0x1E, + 0x7B, 0x15, 0xAF, 0x84, 0xC7, 0xEA, 0xA4, 0xE8, 0x51, 0x58, 0xAF, 0x7F, 0x4E, 0x6F, 0xF2, 0x4A, + 0x62, 0xBA, 0xCF, 0xF6, 0xDB, 0xF9, 0x1F, 0x43, 0x3F, 0x3B, 0xD5, 0x64, 0xDF, 0xFB, 0xE9, 0xFE, + 0x1B, 0x0E, 0x14, 0xD2, 0x76, 0x87, 0x58, 0x94, 0x98, 0xD5, 0xE8, 0xCA, 0x11, 0xAC, 0xBA, 0x2B, + 0xC6, 0x01, 0x6D, 0x78, 0x23, 0xE3, 0x03, 0x6C, 0x61, 0xCE, 0x97, 0x77, 0xEC, 0x24, 0x45, 0x89, + 0x07, 0x79, 0x02, 0x7F, 0x7D, 0x49, 0x48, 0x93, 0xD9, 0x2F, 0x19, 0xBD, 0xFE, 0x16, 0x0E, 0xF8, + 0x2C, 0x36, 0x06, 0x9C, 0xA8, 0x87, 0xD8, 0x4E, 0xA0, 0x0C, 0xCC, 0x40, 0x13, 0x0C, 0xF7, 0xC4, + 0x11, 0x8C, 0x5D, 0x08, 0x22, 0xA5, 0xE1, 0xF4, 0x93, 0xCD, 0xAE, 0x96, 0xF5, 0x75, 0x20, 0x31, + 0xB4, 0x53, 0xE4, 0xCB, 0x86, 0x08, 0xC8, 0xF2, 0xBA, 0x2C, 0x78, 0xC9, 0x41, 0x12, 0x4C, 0x18, + 0xE3, 0x9F, 0x50, 0xAB, 0x74, 0xB8, 0x31, 0x47, 0xAA, 0x3F, 0xB8, 0x00, 0x53, 0x7E, 0xB9, 0xAC, + 0x55, 0xD7, 0x37, 0x55, 0x2E, 0x05, 0x03, 0x75, 0xF6, 0x07, 0xC5, 0x9B, 0x42, 0x13, 0xD8, 0x7E, + 0x58, 0xE8, 0xDA, 0x6E, 0x23, 0x02, 0x9C, 0x9C, 0xB8, 0x07, 0xAC, 0x63, 0x13, 0x3B, 0x9F, 0xDD, + 0xDA, 0xD8, 0x71, 0x2B, 0xD7, 0x82, 0x11, 0x37, 0xD9, 0xF8, 0xFD, 0xC3, 0xE2, 0x8A, 0xEB, 0x08, + 0xEE, 0x2F, 0xAE, 0x3E, 0xC1, 0xF8, 0x0D, 0x91, 0x26, 0xA3, 0xD2, 0xD0, 0xE4, 0xE4, 0xF1, 0xC6, + 0x42, 0x4C, 0xE6, 0xB5, 0xE9, 0x73, 0xE5, 0x27, 0x03, 0xAF, 0xB3, 0x1C, 0xEE, 0x79, 0x90, 0xDA, + 0x82, 0xB3, 0x16, 0x18, 0x9A, 0xD1, 0x6F, 0xE0, 0x59, 0x92, 0x1C, 0x60, 0xA9, 0x5A, 0x12, 0x08, + 0x71, 0x06, 0x5B, 0x9E, 0xD6, 0x49, 0xD2, 0x11, 0x7D, 0xFB, 0x0C, 0xE5, 0xB5, 0x35, 0x95, 0x11, + 0x9F, 0x21, 0x77, 0xBE, 0xA4, 0x62, 0xF7, 0x66, 0x60, 0xC6, 0xA0, 0x7C, 0x81, 0x0D, 0x21, 0xE1, + 0x85, 0xE2, 0xDA, 0xE5, 0x59, 0xC2, 0x7F, 0x14, 0x09, 0x3F, 0x21, 0xA9, 0x6D, 0x4E, 0x2A, 0x81, + 0x41, 0xD7, 0x6A, 0x3F, 0x96, 0x4A, 0xA7, 0x0B, 0xF7, 0xE9, 0x29, 0xE7, 0x32, 0x24, 0xBD, 0x9F, + 0x17, 0x19, 0xFD, 0xFF, 0x96, 0xBF, 0x4C, 0xA5, 0xDB, 0x51, 0x66, 0x27, 0x22, 0x57, 0x60, 0xF3, + 0xD2, 0xD8, 0x67, 0x0A, 0x4B, 0x82, 0xE1, 0x6A, 0x8B, 0x43, 0x58, 0xEC, 0xD7, 0x81, 0xB0, 0xEE, + 0xA2, 0x2A, 0x29, 0xD0, 0x76, 0x44, 0x24, 0xE9, 0x1E, 0x3D, 0xC7, 0xA6, 0xA1, 0xCE, 0xDD, 0x14, + 0x8C, 0x4B, 0xBB, 0x1B, 0x52, 0x4B, 0x9C, 0x8D, 0xD3, 0xF3, 0xD1, 0x53, 0x40, 0x77, 0x5F, 0xE9, + 0xC9, 0x8E, 0xEC, 0x22, 0x0B, 0x52, 0x4A, 0x8D, 0x95, 0x95, 0xD2, 0xF4, 0x3C, 0x67, 0x83, 0xE6, + 0x03, 0xA3, 0x5B, 0x8D, 0xF9, 0x6A, 0x16, 0x89, 0x75, 0xAC, 0xF5, 0xAC, 0x4E, 0xA4, 0x7E, 0x02, + 0xB7, 0x3A, 0x8C, 0xE6, 0xAF, 0xF8, 0xE5, 0x2D, 0xAD, 0x76, 0x89, 0x79, 0xBD, 0x73, 0x92, 0xB3, + 0x05, 0x0D, 0xD3, 0xB4, 0xE4, 0x79, 0x0E, 0x25, 0xE9, 0xA3, 0x4E, 0xE6, 0x07, 0xDB, 0x5A, 0x58, + 0x5D, 0x16, 0xCA, 0x6B, 0x16, 0xAA, 0x76, 0x37, 0x2A, 0xB4, 0x9E, 0x31, 0xDF, 0x48, 0x65, 0x07, + 0x3A, 0xF8, 0x04, 0xA5, 0xC9, 0xDA, 0xB3, 0x44, 0x20, 0xF2, 0x60, 0xE4, 0xBD, 0x84, 0x08, 0x29}); + + static byte[] randomBytes(byte[] bytes) { + ThreadLocalRandom.current().nextBytes(bytes); + return bytes; + } + + static byte[] toBytes(int[] ints) { + byte[] bytes = new byte[ints.length]; + for (int i = 0; i < ints.length; i++) { + bytes[i] = (byte) ints[i]; + } + return bytes; + } + +} diff --git a/src/test/java/com/github/alexeylapin/eme/cipher/CipherTest.java b/src/test/java/com/github/alexeylapin/eme/cipher/CipherTest.java new file mode 100644 index 0000000..d5ad59c --- /dev/null +++ b/src/test/java/com/github/alexeylapin/eme/cipher/CipherTest.java @@ -0,0 +1,111 @@ +package com.github.alexeylapin.eme.cipher; + +import com.github.alexeylapin.eme.EMETestSupport; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.Security; +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CipherTest extends EMETestSupport { + + public static final String AES_EME_PKCS_7_PADDING = "AES/EME/PKCS7Padding"; + + @Test + void should_returnCipherPropertiesCorrectly() throws Exception { + Security.addProvider(new AES128EMEProvider()); + + Cipher cipher = Cipher.getInstance(AES_EME_PKCS_7_PADDING); + + SecretKeySpec keySpec = new SecretKeySpec(new byte[32], "AES"); + byte[] iv = new byte[16]; + ThreadLocalRandom.current().nextBytes(iv); + + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv)); + + assertThat(cipher.getAlgorithm()).isEqualTo(AES_EME_PKCS_7_PADDING); + assertThat(cipher.getBlockSize()).isEqualTo(16); + assertThat(cipher.getIV()).isEqualTo(iv); + assertThat(cipher.getParameters()).isNull(); + assertThat(cipher.getOutputSize(0)).isEqualTo(16); + assertThat(cipher.getOutputSize(1)).isEqualTo(16); + assertThat(cipher.getOutputSize(5)).isEqualTo(16); + assertThat(cipher.getOutputSize(16)).isEqualTo(32); + assertThat(cipher.getOutputSize(17)).isEqualTo(32); + } + + @ValueSource(ints = {0, 1, 5, 16, 17, 32, 33}) + @ParameterizedTest + void should_encryptDecryptCorrectly(int length) throws Exception { + Security.addProvider(new AES128EMEProvider()); + + byte[] input = new byte[length]; + for (int i = 0; i < input.length; i++) { + input[i] = (byte) i; + } + + SecretKeySpec keySpec = new SecretKeySpec(new byte[32], "AES"); + IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[16]); + + Cipher encryptCipher = Cipher.getInstance(AES_EME_PKCS_7_PADDING); + encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); + + byte[] encrypted = encryptCipher.doFinal(input); + + Cipher decryptCipher = Cipher.getInstance(AES_EME_PKCS_7_PADDING); + decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec); + + byte[] decrypted = decryptCipher.doFinal(encrypted); + + assertThat(decrypted).isEqualTo(input); + } + + @Test + void should_updateCorrectly() throws Exception { + Security.addProvider(new AES128EMEProvider()); + + byte[] input = new byte[20]; + for (int i = 0; i < input.length; i++) { + input[i] = (byte) i; + } + + SecretKeySpec keySpec = new SecretKeySpec(new byte[32], "AES"); + IvParameterSpec ivParameterSpec = new IvParameterSpec(new byte[16]); + + Cipher encryptCipher = Cipher.getInstance(AES_EME_PKCS_7_PADDING); + encryptCipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); + + Cipher decryptCipher = Cipher.getInstance(AES_EME_PKCS_7_PADDING); + decryptCipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec); + + byte[] encryptedPart1 = encryptCipher.update(input); + byte[] encryptedPart2 = encryptCipher.update(input, 0, 10); + byte[] encryptedOutput3 = new byte[17]; + int encryptedLength3 = encryptCipher.update(input, 1, 12, encryptedOutput3); + byte[] encryptedOutput4 = new byte[17]; + int encryptedLength4 = encryptCipher.update(input, 2, 15, encryptedOutput4, 1); + byte[] encryptedFinal = encryptCipher.doFinal(); + + byte[] decryptedPart1 = decryptCipher.update(encryptedPart1); + byte[] decryptedPart2 = decryptCipher.update(encryptedPart2); + byte[] decryptedPart3 = decryptCipher.update(encryptedOutput3, 0, 16); + byte[] decryptedPart4 = decryptCipher.update(encryptedOutput4, 1, encryptedLength4); + byte[] decryptedFinal = decryptCipher.doFinal(encryptedFinal); + + assertThat(decryptedPart1).isEqualTo(input); + assertThat(decryptedPart2).isEqualTo(Arrays.copyOfRange(input, 0, 0 + 10)); + assertThat(encryptedLength3).isEqualTo(16); + assertThat(decryptedPart3).isEqualTo(Arrays.copyOfRange(input, 1, 1 + 12)); + assertThat(encryptedLength4).isEqualTo(16); + assertThat(decryptedPart4).isEqualTo(Arrays.copyOfRange(input, 2, 2 + 15)); + assertThat(decryptedFinal).isEmpty(); + } + +}