Skip to content

Commit

Permalink
Feature/provider (#5)
Browse files Browse the repository at this point in the history
* provider

* implement cipher spi

* implement cipher spi
alexey-lapin authored Sep 24, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent dd492c2 commit c2fb14d
Showing 9 changed files with 557 additions and 95 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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<JavaPluginExtension> {
9 changes: 6 additions & 3 deletions src/main/java/com/github/alexeylapin/eme/EME.java
Original file line number Diff line number Diff line change
@@ -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);
}

18 changes: 12 additions & 6 deletions src/main/java/com/github/alexeylapin/eme/EMEImpl.java
Original file line number Diff line number Diff line change
@@ -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,15 +22,16 @@ 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);
this.decryptor = Cipher.getInstance(AES_ECB_NO_PADDING);
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][];
197 changes: 197 additions & 0 deletions src/main/java/com/github/alexeylapin/eme/cipher/AES128EME.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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");
}

}
Loading

0 comments on commit c2fb14d

Please sign in to comment.