All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.akeyless.crypto.utils.AkeylessCiphertext Maven / Gradle / Ivy

There is a newer version: 0.0.10
Show newest version
package com.akeyless.crypto.utils;

import com.akeyless.crypto.exceptions.BadCiphertextException;
import com.akeyless.exceptions.AkeylessCryptoException;
import com.akeyless.exceptions.AkeylessRuntimeException;
import org.apache.commons.codec.binary.Base64;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class AkeylessCiphertext {

    private static final int AKEYLESS_VERSION = 1;

    private static final int AKEYLESS_VERSION_BYTES_LEN = 1;
    private static final int KEY_VERSION_BYTES_LEN = 4;
    private static final int HEADER_DD_LEN = 2;

    private CryptoAlgorithm alg;
    private byte akeylessVersion = -1;
    private Integer keyVersion = -1;
    private ArrayList derivationsData;
    private byte[] encryptedData;
    private byte[] finalCipher;

    public AkeylessCiphertext(final CryptoAlgorithm alg, final Integer keyVersion,
                              final ArrayList derivationsData, final byte[] encryptedData) {
        this.alg = alg;
        this.akeylessVersion = AKEYLESS_VERSION;
        this.keyVersion = keyVersion;
        setDerivationsDataByAlg(alg, derivationsData);
        this.encryptedData = encryptedData;
        this.finalCipher = serialize();
    }

    public AkeylessCiphertext(final CryptoAlgorithm alg, final byte[] cipher) throws BadCiphertextException {
        this.alg = alg;
        this.finalCipher = cipher;
        deserialize(finalCipher);
    }

    public AkeylessCiphertext(final CryptoAlgorithm alg, final String cipherBase64) throws BadCiphertextException {
        this.alg = alg;
        this.finalCipher = Base64.decodeBase64(cipherBase64);
        deserialize(this.finalCipher);
    }

    public byte getAkeylessVersion() {
        return akeylessVersion;
    }

    public Integer getKeyVersion() {
        return keyVersion;
    }

    public ArrayList getDerivationsData(int numOfFragments) throws AkeylessCryptoException {
        if(!alg.isDeterministic() && derivationsData.size() == 1) {
            return new ArrayList<>(Collections.nCopies(numOfFragments, derivationsData.get(0)));
        }

        if(derivationsData.size() != numOfFragments) {
            throw new AkeylessCryptoException("The number of derivations data does not match the number of the key fragments");
        }

        return derivationsData;
    }

    public byte[] getEncryptedData() {
        return encryptedData;
    }

    public byte[] toByteArray() {
        return finalCipher;
    }

    public String toString() {
        return new String(Base64.encodeBase64(finalCipher));
    }

    public static Integer extractKeyVersionFromCipher(final byte[] cipher) throws BadCiphertextException {
        byte[] keyVersionBytes = parseBytes(cipher,AKEYLESS_VERSION_BYTES_LEN,KEY_VERSION_BYTES_LEN,"key version");
        ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).put(keyVersionBytes);
        buf.flip();
        Integer version = buf.getInt();
        if(version < 1) {
            throw new BadCiphertextException("Invalid key version");
        }
        return version;
    }

    private void setDerivationsDataByAlg(final CryptoAlgorithm alg, final ArrayList derivationsData) {
        if(derivationsData == null || derivationsData.size() == 0) {
            throw new AkeylessRuntimeException("No derivation data was provided");
        }

        if(alg.isDeterministic()) {
            this.derivationsData = derivationsData;
            return;
        }
        this.derivationsData = new ArrayList<>(Collections.nCopies(1, derivationsData.get(0)));
    }

    private byte[] serialize() {
        byte[] combinedDD = serializeDerivationsData();
        assert combinedDD != null;

        ByteBuffer bb = ByteBuffer.allocate(AKEYLESS_VERSION_BYTES_LEN + KEY_VERSION_BYTES_LEN +
                combinedDD.length+encryptedData.length);

        bb.put((byte) AKEYLESS_VERSION_BYTES_LEN);
        bb.put(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(keyVersion).array());
        bb.put(combinedDD);
        bb.put(encryptedData);
        return bb.array();
    }

    private byte[] serializeDerivationsData() {
        if(alg.isDeterministic()){
            return null; //TODO
        }
        return serializeDDForNondeterministicAlg();
    }

    private byte[] serializeDDForNondeterministicAlg() {
        if(derivationsData == null || derivationsData.size() == 0) {
            throw new AkeylessRuntimeException("No derivation data was provided");
        }

        for (int i = 1; i < derivationsData.size(); i++) {
            if(!Arrays.equals(derivationsData.get(i-1), derivationsData.get(i))) {
                throw new AkeylessRuntimeException("For a non-deterministic algorithm, all the derivations data should be identical");
            }
        }

        byte[] derivationData = derivationsData.get(0);

        byte numOfDD = 1;
        ByteBuffer bb = ByteBuffer.allocate(2+derivationData.length);
        bb.put((byte) derivationData.length);
        bb.put(numOfDD);
        bb.put(derivationData);
        return bb.array();
    }

    private int deserialize(final byte[] finalCipher) throws BadCiphertextException {
        if(finalCipher == null || finalCipher.length == 0) {
            throw new BadCiphertextException("Empty ciphertext");
        }

        int parsedBytes = 0;
        parsedBytes += parseAkeylessVersion(finalCipher, parsedBytes);
        parsedBytes += parseKeyVersion(finalCipher, parsedBytes);
        parsedBytes += parseDerivationsData(finalCipher, parsedBytes);
        parsedBytes += parseEncryptedData(finalCipher, parsedBytes);
        return parsedBytes;
    }

    private int parseAkeylessVersion(final byte[] b, final int off) throws BadCiphertextException {
        akeylessVersion = parseBytes(b,off,AKEYLESS_VERSION_BYTES_LEN,"cipher version")[0];
        if(akeylessVersion < 1) {
            throw new BadCiphertextException("Invalid cipher version");
        }
        return AKEYLESS_VERSION_BYTES_LEN;
    }

    private int parseKeyVersion(final byte[] b, final int off) throws BadCiphertextException {
        byte[] keyVersionBytes = parseBytes(b,off,KEY_VERSION_BYTES_LEN,"key version");
        ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).put(keyVersionBytes);
        buf.flip();
        keyVersion = buf.getInt();
        if(keyVersion < 1) {
            throw new BadCiphertextException("Invalid key version");
        }
        return KEY_VERSION_BYTES_LEN;
    }

    private int parseDerivationsData(final byte[] b, final int off)  throws BadCiphertextException {
        if(alg.isDeterministic()){
            return parseDerivationsDataForDeterministicAlg(b, off);
        }
        return parseDerivationsDataForNondeterministicAlg(b, off);
    }

    private int parseDerivationsDataForDeterministicAlg(final byte[] b, final int off) throws BadCiphertextException {
        if(b == null || b.length - off  < HEADER_DD_LEN) {
            throw new BadCiphertextException("No derivation data was provided");
        }

        int derivationDataLen = b[off];
        int numOfDD = b[off +1];

        if(numOfDD < 1){
            throw new BadCiphertextException("Invalid number of derivations data");
        }

        if(b.length - off < derivationDataLen*numOfDD + HEADER_DD_LEN) {
            throw new BadCiphertextException("Invalid serialized derivations data length");
        }

        derivationsData = new ArrayList<>(Collections.nCopies(numOfDD, new byte[0]));
        int indx = off+HEADER_DD_LEN;
        for (int i = 0; i < numOfDD; i++) {
            derivationsData.set(i,Arrays.copyOfRange(b, indx, indx + derivationDataLen));
            indx = indx + derivationDataLen;
        }
        return indx - off;
    }

    private int parseDerivationsDataForNondeterministicAlg(final byte[] b, final int off) throws BadCiphertextException {
        if(b == null || b.length - off  < HEADER_DD_LEN) {
            throw new BadCiphertextException("No derivation data was provided");
        }

        int derivationDataLen = b[off];
        int numOfDD = b[off +1];

        if(numOfDD != 1){
            throw new BadCiphertextException("Invalid number of derivations data for non-deterministic algorithm");
        }

        if(b.length - off < derivationDataLen*numOfDD + HEADER_DD_LEN) {
            throw new BadCiphertextException("Invalid serialized derivations data length");
        }

        int ddStIndx = off+HEADER_DD_LEN;
        byte[] dd = Arrays.copyOfRange(b, ddStIndx, ddStIndx + derivationDataLen);
        derivationsData = new ArrayList<>(Collections.nCopies(numOfDD, dd));
        return HEADER_DD_LEN + derivationDataLen;
    }

    private int parseEncryptedData(final byte[] b, final int off) {
        encryptedData = Arrays.copyOfRange(b, off, b.length);
        return b.length - off;
    }

    private static byte[] parseBytes(final byte[] b,
                                          final int off,
                                          final int size,
                                          final String name) throws BadCiphertextException {
        final int len = b.length - off;
        if (len < size) {
            throw new BadCiphertextException("Not enough bytes to parse "+name);
        }
        return Arrays.copyOfRange(b, off, off+size);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy