#StackBounty: #java #api #networking #socket #web-services Java Messenger data transmission

Bounty: 50

This is a very simple concern, could somebody take a look at a few lines of my code? I’m writing a simple Java multi-user messenger, it is structured in a desktop client with a Swing GUI, a java server based on plain sockets and a service library holding general utilities… At the moment I transmit messages through a ObjectOutputStream/ObjectInputStream with a custom Packet class…

package org.x1c1b.carrierpigeon.service.packet;

import java.io.Serializable;
import java.util.Objects;

public class Packet implements Serializable
{
    protected String source;
    protected String destination;

    protected int type;
    protected String payload;

    public Packet(String source, String destination, PacketType type, String payload)
    {
        this.source = source;
        this.destination = destination;

        this.type = type.getIdentifier();
        this.payload = payload;
    }

    public Packet(Packet packet)
    {
        this.source = packet.source;
        this.destination = packet.destination;

        this.type = packet.type;
        this.payload = packet.payload;
    }

    protected Packet(PacketBuilder builer)
    {
        this.source = builer.source;
        this.destination = builer.destination;

        this.type = builer.type;
        this.payload = builer.payload;
    }

    public String getSource()
    {
        return this.source;
    }

    public String getDestination()
    {
        return this.destination;
    }

    public PacketType getType()
    {
        return PacketType.getByIdentifier(this.type);
    }

    public String getPayload()
    {
        return this.payload;
    }

    @Override public boolean equals(Object object)
    {
        if(this == object)
        {
            return true;
        }

        if(object == null || getClass() != object.getClass())
        {
            return false;
        }

        Packet packet = (Packet) object;

        return type == packet.type && Objects.equals(source, packet.source) && Objects
                .equals(destination, packet.destination) && Objects.equals(payload, packet.payload);
    }

    @Override public int hashCode()
    {
        return Objects.hash(this.source, this.destination, this.type, this.payload);
    }
}

This is the Packet class used to transmit the message, for identifying the message purpose I uses a Enum defining different PacketType‘s…

package org.x1c1b.carrierpigeon.service.packet;

import java.util.NoSuchElementException;

public enum PacketType
{
    HANDSHAKE_REQUEST(1),
    HANDSHAKE_REPLY(2),
    HANDSHAKE_ERROR(3),
    AUTHENTICATION_REQUEST(4),
    AUTHENTICATION_REPLY(5),
    AUTHENTICATION_ERROR(6),
    CONNECTIVITY_ESTABLISHED(7),
    CONNECTIVITY_HALTED(8),
    CONNECTIVITY_STATUS(9),
    CONNECTIVITY_SETUP(10),
    CONNECTIVITY_ERROR(11),
    DATA_TRANSFER(12),
    DATA_TRANSFER_ERROR(13);

    private int identifier;

    private PacketType(int identifier)
    {
        this.identifier = identifier;
    }

    public int getIdentifier()
    {
        return this.identifier;
    }

    public static PacketType getByIdentifier(int identifier)
    {
        for(PacketType type : PacketType.values())
        {
            if(type.identifier == identifier)
            {
                return type;
            }
        }

        throw new NoSuchElementException("Identifier: " + identifier);
    }
}

Maybe for taking a look at the whole project:

I’m not that much expired in network development with java, so are there any improvements by transmission of messages? Is it common practice to use ObjectStream‘s with a custom “Protocol”/Packet to transmit data or should I use already existing protocols like HTTP instead? I tried to hold it simple without REST or huge webservers…

I will be glad about any improvements or tips for the data transmission…

EDIT

I also anticipate to write a basic API with the Remote Procedure Call pattern, here a basic example to illustrate what I mean:

public interface MessengerServiceAPI
{
    public abstract boolean login(String name);
}

Here the server-side implementation of the service API:

public class MessengerService implements MessengerServiceAPI
{
    @Override public boolean login(String name)
    {
        // Some login logic and database interaction on server-side
    }
}

Now I superior to call this server-side method with RPC through a ObjectStream:

// Method used on client-side to call remote method of service API

public Object call(String name, Object [] params) throws Exception
{
    try(Socket socket = new Socket(this.address, this.port);
            ObjectOutputStream sout = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream sin = new ObjectInputStream(socket.getInputStream()))
    {
        sout.writeObject(name);
        sout.writeObject(params);
        sout.flush();

        Object object = sin.readObject();

        if(object instanceof Exception)
        {
            throw (Exception) object;
        }

        return object;
    }
}

// The server-side handler to handle API requests, which execute the requested action on server-side and return the return value of it

@Override public void handle(Socket socket)
{
    try (ObjectInputStream sin = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream sout = new ObjectOutputStream(socket.getOutputStream()))
    {
        Object object;
        String name = (String) sin.readObject();
        Object [] params = (Object[]) sin.readObject();

        try
        {
            Class <?> [] paramTypes = null;

            if(null != params)
            {
                types = new Class[params.length];

                for(int index = 0; index < params.length; ++index)
                {
                    paramTypes[index] = params[index].getClass();
                }
            }

            Method method = this.service.getClass().getMethod(name, paramTypes);
            object = method.invoke(this.service, params);
        }
        catch(InvocationTargetException exc)
        {
            object = exc.getTargetException();
        }
        catch(Exception exc)
        {
            object = exc;
        }

        sout.writeObject(object);
        sout.flush();
    }
    catch(Exception exc)
    {
        exc.printStackTrace();
    }
}

This is one possible implementation of such a service handling, but is it recommended to write such a basic API and service? Or is it recommended to send a string or packet instead with a identifier and the server executes the action by parsing this identifier?

Sorry because it is also in big parts software design not just code review but I’m waiting for somebody who takes a look on my code to give tips or improvements…


Get this bounty!!!

#StackBounty: #pi-2 #java #pi4j #lcd #adafruit PI4J + Adafruit LCD with MCP23017

Bounty: 50

Got this device.

enter image description here

enter image description here

Python example char_lcd_plate.py is working perfect.

But I would like to manage it with Pi4J.

I copied bits from python example

LCD_PLATE_RS            = 15
LCD_PLATE_RW            = 14
LCD_PLATE_EN            = 13
LCD_PLATE_D4            = 12
LCD_PLATE_D5            = 11
LCD_PLATE_D6            = 10
LCD_PLATE_D7            = 9

Java:

LCD lcd = new I2CLcdDisplay(2, 16, 1, 0x20, 0, 15, 14, 13, 9, 10, 11, 12);
lcd.clear();
lcd.write(0, "111 ");
lcd.write(1, "222");

Init is fine but I have blue boxes on the screen and no any updates.
Any advice?


Get this bounty!!!

#StackBounty: #java #spring #vaadin Scoped Spring events possible?

Bounty: 50

The Spring event mechanism supports publishing application events and listening to these events within Spring components via the @EventListener annotation. However, I cannot find anything about sending events within a specific scope in the documentation. My specific need for Vaadin is:

  • in context of a user interaction, send an event (e.g. logged-in event)
  • this event should only be consumed by beans in same @UIScope, i.e. other user UIs shouldn’t be effected

Is that possible? NOTE: This is not really specific to Vaadin. I could also ask how it would be done with Spring web mvc request scope.


Get this bounty!!!

#StackBounty: #java #android #android-fragments #android-viewpager java.lang.IllegalStateException: Fragment not attached to a context

Bounty: 50

I have a tablayout with a viewpager in my MainActivity.

My PagerAdapter looks like this:

public class MainActivityPagerAdapter extends PagerAdapter {

    public MainActivityPagerAdapter(FragmentManager fm, int numOfTabs) {
        super(fm, numOfTabs);
    }

    @Override
    public Fragment getItem(int position) {

        switch (position) {
            case 0:
                return new StoresFragment();
            case 1:
                return new OrdersFragment();
            default:
                return null;
        }
    }
}

I am coming back from another activity like this:

Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish(); //finishAffinity();

But then I get an java.lang.IllegalStateException in one of my Fragments in the viewpager of the MainActivity.

I read many related questions and tried to solve this. It is said, that this happens when one keeps references to Fragments outside of the PagerAdapter. But I am not doing this, as you can see in my code.

Does anyone know what I am doing wrong?

Edit – Stacktrace

FATAL EXCEPTION: main
    Process: com.lifo.skipandgo, PID: 23665
    java.lang.IllegalStateException: Fragment OrdersFragment{42c2a740} not attached to a context.
    at android.support.v4.app.Fragment.requireContext(Fragment.java:614)
    at android.support.v4.app.Fragment.getResources(Fragment.java:678)
    at com.lifo.skipandgo.activities.fragments.OrdersFragment$1.results(OrdersFragment.java:111)
    at com.lifo.skipandgo.connectors.firestore.QueryResult.receivedResult(QueryResult.java:37)
    at com.lifo.skipandgo.controllers.UserController$2.onUpdate(UserController.java:88)
    at com.lifo.skipandgo.connectors.firestore.QuerySubscription.onEvent(QuerySubscription.java:59)
    at com.lifo.skipandgo.connectors.firestore.QuerySubscription.onEvent(QuerySubscription.java:18)
    at com.google.firebase.firestore.zzg.onEvent(Unknown Source)
    at com.google.firebase.firestore.g.zzh.zza(SourceFile:28)
    at com.google.firebase.firestore.g.zzi.run(Unknown Source)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5653)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
    at dalvik.system.NativeStart.main(Native Method)

Edit:
Interesting is, that the view has defenitely loaded when the error occurs. Because the error occurs about 10-15 seconds later after the fragment is shown again. I this in my orderFragment, where the error occurs:

orders = new QueryResult<UserOrder>(UserOrder.class) {
            @Override
            public void results(List<UserOrder> results) { 
orderLoadingMessage.setBackgroundColor(getResources().getColor(R.color.green)); 
    }
}

I do this in onCreateView and this result comes about 10-15 seconds after the view loaded.


Get this bounty!!!

#StackBounty: #java #android #sockets #inputstream #serversocket Error while reading inputstream : Software caused connection abort&#39…

Bounty: 100

I am basically trying to host a server on an Android Device. Client devices connect to the server over TCP and send requests. The server performs the action requested by the client and then writes data back to the socket. The connection is not terminated by the server and requests are to be continuously read over the socket and replied to.

Note: The first 4 bytes of each request message contain the length of the actual message/request.

The parseXmlInputAndExecuteCmd function executes various asynchronous operations depending on the content of the input XML string. This eventually causes a change in boolean value of ‘allowResponse’ variable to true and a certain response is generated which is stored in the variable of type String called ‘response’. Once the boolean ‘allowResponse’ becomes true, the thread resumes execution and writes the response back to the socket’s OutputStream

Some of these asynchronous operations include connecting and disconnecting from the corporate VPN. Could that be a cause of the error ?

Some Class level variables being used are :

private volatile boolean allowResponse = false;
private String response;

Server Code:

    private void startServer() {
    try {
        ServerSocket serverSocket = new ServerSocket(9200);
        while (true) {
            Socket connectionSocket = serverSocket.accept();
            BufferedInputStream bufInpStream = new BufferedInputStream(connectionSocket.getInputStream());
            BufferedOutputStream bufOutStream = new BufferedOutputStream(connectionSocket.getOutputStream());
            ByteArrayOutputStream contentLengthBaos = new ByteArrayOutputStream();
            int c;
            int count = 0;
            while ((c = bufInpStream.read()) != -1) {
                contentLengthBaos.write(c);
                count++;
                if (count == 4) {
                    int contLen = getMessageLength(contentLengthBaos);
                    String content = getMessageContent(bufInpStream, contLen);
                    parseXmlInputAndExecuteCmd(content);
                    count = 0;
                    while (!allowResponse) {
                        Thread.sleep(1000);
                    }
                    allowResponse = false;
                    byte[] responseDataBytes = response.getBytes();
                    int outputContLen = responseDataBytes.length;
                    byte[] contLengthBytes = ByteBuffer.allocate(4).putInt(outputContLen).array();
                    ByteArrayOutputStream o = new ByteArrayOutputStream();
                    o.write(contLengthBytes);
                    o.write(responseDataBytes);
                    byte finalOutPutBytes[] = o.toByteArray();
                    bufOutStream.write(finalOutPutBytes);
                    bufOutStream.flush();
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@NonNull
private String getMessageContent(BufferedInputStream inFromClient, int contLen) throws IOException {
    ByteArrayOutputStream contentBaos = new ByteArrayOutputStream();
    byte[] contentBytes = new byte[contLen];
    for (int i = 0; i < contLen; i++) {
        contentBaos.write(inFromClient.read());
        ByteArrayInputStream bais = new ByteArrayInputStream(contentBaos.toByteArray());
        bais.read(contentBytes);
    }
    String content = new String(contentBytes);
    Log.d(TAG, "Content : " + content);
    return content;
}

private int getMessageLength(ByteArrayOutputStream contentLengthBaos) {
    byte[] firstFourBytesArr = contentLengthBaos.toByteArray();
    int contLen = new BigInteger(firstFourBytesArr).intValue();
    contentLengthBaos = new ByteArrayOutputStream();
    Log.d(TAG, "Content length: " + contLen);
    return contLen;
}

The server is started using the following lines of code:

Thread serverThread = new Thread(new Runnable() {
        @Override
        public void run() {
            startServer();
        }
    });
    serverThread.start();

I am getting the following error stacktrace:

W/System.err: java.net.SocketException: Software caused connection abort
                  at java.net.SocketInputStream.socketRead0(Native Method)
                  at java.net.SocketInputStream.socketRead(SocketInputStream.java:114)
W/System.err:     at java.net.SocketInputStream.read(SocketInputStream.java:170)
W/System.err:     at java.net.SocketInputStream.read(SocketInputStream.java:139)
W/System.err:     at java.io.BufferedInputStream.fill(BufferedInputStream.java:248)
W/System.err:     at java.io.BufferedInputStream.read(BufferedInputStream.java:267)
                  at com.example.umathur.myapplication.MainActivity.startServer(MainActivity.java:192)
                  at com.example.umathur.myapplication.MainActivity.access$000(MainActivity.java:61)
                  at com.example.umathur.myapplication.MainActivity$1.run(MainActivity.java:139)
                  at java.lang.Thread.run(Thread.java:764)

I am getting an error called: java.net.SocketException: Software caused connection abort

I’m not sure where I’m going wrong in the usage of the inputstream/outputstream. Im getting the error at the following line(Line number 192 as mentioned in the stacktrace) :

while ((c = inputStream.read()) != -1)

In some similar questions on StackOverflow, I saw people stating that it might be a corporate firewall configuration issue ? Is that correct ? If so, how do I get it fixed ?

Could this be an issue with the Client code (To which I don’t have access) not being written correctly ?


Get this bounty!!!

#StackBounty: #java #sql-server #hibernate #jpa #hql Hibernate @Formula doesn't include Schema

Bounty: 100

I have an entity with a property @Formula like this:

@Entity
@Table(name = "areasAuxiliar")
public final class AreaAuxiliar implements Serializable {

    @Id
    @Column(name = "idArea")
    private Integer idArea;

    @Formula("RUTAAREA(idArea)")
    private String ruta;

when I configure my hibernate to point to an Oracle DB I have no problem,
BUT, when I switch to an SQLServer, hibernate is not including the shema and the query fails,

the query generated for hibernate looks like this:

select
    areaauxili4_.idArea as idArea1_6_4_,
    rutaArea(areaauxili4_.idArea) as formula2_4_
from
    SIGAP.areasAuxiliar areaauxili4_ 

the param hibernate.default_schema=SIGAP is being read and included in the table but not in the function,

is there an option/annotation to force the shema in in that function?

I have tried hibernate 5.1 and 5.2 with the same result 🙁


Get this bounty!!!

#StackBounty: #java #jsf #foreach #jstl #tomahawk inputCalendar forEach calling a exception

Bounty: 100

I’m receiving a exception with the code below.

<c:forEach var="calculoNotaUnidade" varStatus="counter" items="#{ configuracoesAva.calculoNotaUnidades }">
    <tr>
        <td>
            <t:inputCalendar id="${ counter.count }" value="#{ calculoNotaUnidade.dataFinalizacaoUnidade }" style="z-index:999;" popupButtonStyle="z-index:0;" renderAsPopup="true" renderPopupButtonAsImage="true" size="10" onkeypress="return (formataData(this,event));"  maxlength="10" title="Data de Finalização">
                <f:convertDateTime pattern="dd/MM/yyyy" />
            </t:inputCalendar>
        </td>
    </tr>
</c:forEach>

The exception it is:

enter image description here

The exception it is called because my inputCalendar ID it is wrong (and i don’t know fix it).

When i don’t put any ID, the page it is loaded, but the inputCalendars dosen’t work.


Get this bounty!!!

#StackBounty: #java #gradle #intellij-idea #kotlin #antlr4 IntelliJ IDEA Gradle project not recognizing/locating Antlr generated sources

Bounty: 300

I’m using Antlr in a simple Kotlin/Gradle project, and while my Gradle build is generating Antlr sources, they are not available for importing into the project.

As you can see (on the left), the classes (Lexer/Parser, etc.) are being generated. I have also configured this generated-src/antlr/main directory as a Source Root. Most questions I see list this as a solution, but I’ve already done it.

The issue persists after multiple rebuilds (both in IDEA and on the CLI), and following all the usual “Invalidate Cache and Restart” issues.

Further, the import issue is listed in the Gradle build on the CLI so it doesn’t seem isolated to IDEA.

What am I missing here?

enter image description here

Here’s the build.gradle file produced by IDEA when I was creating the project initially, and which IDEA is using for project/workspace synchronization.

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.50'
}

group 'com.craigotis'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

apply plugin: 'antlr'

dependencies {
    antlr "org.antlr:antlr4:4.5"
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}


Get this bounty!!!

#StackBounty: #java #security #cryptography #aes JSafebox – Encrypted vault – 2nd drill

Bounty: 50

I am developing JSafebox, a portable safebox project written in Java.

A first review have been completed here.

The goal is to provide a secured environment where the user can browse encrypted files without leaking data on the drive.

The project is highly focused on security and I would appreciate any feedback that would help improve the quality of the code.

Core logic and sensitive method are located in below classes but feedback from other classes is welcome as well 🙂

org/ortis/jsafebox/Safe.java

/*******************************************************************************
 * Copyright 2018 Ortis (cao.ortis.org@gmail.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under the License.
 ******************************************************************************/

package org.ortis.jsafebox;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.Key;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CancellationException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.ortis.jsafebox.hash.Hasher;
import org.ortis.jsafebox.hash.SHA256;
import org.ortis.jsafebox.task.TaskProbe;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * Virtual vault where files are stored
 * 
 * @author Ortis <br>
 *         2018 Apr 26 7:29:29 PM <br>
 */
public class Safe implements Closeable
{
    public static final String VERSION = "0.2 beta";

    public static final Gson GSON = new Gson();
    private static final Type MAP_STRING_STRING_TYPE = new TypeToken<Map<String, String>>()
    {
    }.getType();

    public static final Type BYTE_ARRAY_TYPE = new TypeToken<byte []>()
    {
    }.getType();

    private final static Hasher HASHER = new SHA256();

    public static final String ENCRYPTION_LABEL = "encryption";
    public final static String ENCRYPTION_IV_LENGTH_LABEL = "iv length";
    public static final String KEY_ALGO_LABEL = "algo";
    public static final String PROTOCOL_SPEC_LABEL = "protocol description";
    public static final String PBKDF2_SALT_LABEL = "pbkdf2 salt";
    public static final int PBKDF2_ITERATION = 100000;
    public static final String PBKDF2_ITERATION_LABEL = "pbkdf2 iteration";

    public static final String PROTOCOL_SPEC = "JSafebox is using a very simple protocol so encrypted files can be easily read by another program, as long as you have the password. The encryption key is derived from the password using PBKDF2 hashing with 100000 iteration. A JSafebox file contains a SHA256 integrity hash followed by blocks: [ integrity hash | block 0 | block 1 | ... | block N ]. Each block is stored as followed: [ IV | metadata length | metadata | data length | data ] where 'IV' is the Initialization_vector of the encryption (16 bytes), 'metadata' is a JSON string and 'length' are 64 bits (8 bytes) integer. The first block 'block 0' is the 'header' and is the only block not encrypted and therefore, the only block without IV. The 'header' only have metadata ('data length' is 0) and contains text entries specified by the user and various additional entries including a protocol explanation, the type of encoding and the parameters of the encryption. The 'header's metadata is stored as JSON string and can be seen by opening the safe file with a basic text editor. The second block 'block 1' is the 'properties'. It is similar to the 'header' except that it is encrypted and have an IV. The 'properties' contains text entries specified by the user and stored in JSON. The following blocks (from 2 to N) are the encrypted files. (Full manual at https://github.com/0rtis/jsafebox)";

    private final File originalFile;

    private final SecretKey encryptionKey;
    private final int ivLength;
    private final RandomAccessFile original;

    private final File tempFile;
    private final RandomAccessFile temp;

    private final byte [] hash;

    private final Map<String, String> publicHeader;
    private final Map<String, String> privateProperties;
    private final Map<String, Block> roBlocks;
    private final Map<String, Block> blocks;
    private final Map<String, Block> tempBlocks;
    private final Map<String, Block> deletedBlocks;

    private final int bufferSize;

    private final Folder root;

    /**
     * Create an instance of {@link Safe}
     * 
     * @param file:
     *            the safe file
     * @param cipher:
     *            cipher to decrypt the data
     * @param keySpec:
     *            key specification
     * @param algoSpec:
     *            encryption specification
     * @param bufferSize:
     *            size of the <code>byte</code> buffer to be used in IO operation
     * @throws Exception
     */
    public Safe(final File file, final SecretKey key, final int bufferSize) throws Exception
    {

        this.originalFile = file.getAbsoluteFile();

        this.encryptionKey = key;

        this.bufferSize = bufferSize;

        this.original = new RandomAccessFile(file, "rw");
        this.tempFile = Files.createTempFile(null, null).toFile();
        this.temp = new RandomAccessFile(this.tempFile, "rw");

        final HashMap<String, String> publicProps = new LinkedHashMap<>();
        this.publicHeader = Collections.unmodifiableMap(publicProps);
        final HashMap<String, String> props = new LinkedHashMap<>();
        this.privateProperties = Collections.unmodifiableMap(props);
        this.blocks = new LinkedHashMap<>();
        this.roBlocks = Collections.unmodifiableMap(blocks);
        this.tempBlocks = new LinkedHashMap<>();
        this.deletedBlocks = new LinkedHashMap<>();
        this.root = new Folder(null, Folder.ROOT_NAME);

        final byte [] buffer = new byte[bufferSize];
        final byte [] outBuffer = new byte[bufferSize];

        this.original.read(buffer, 0, HASHER.getHashLength());
        this.hash = new byte[HASHER.getHashLength()];
        System.arraycopy(buffer, 0, this.hash, 0, this.hash.length);

        long length;
        int read;
        final ByteArrayOutputStream baos = new ByteArrayOutputStream(buffer.length);

        final long headerLength = this.original.readLong();
        length = headerLength;

        while (length > 0)
        {

            if (length < buffer.length)
                read = this.original.read(buffer, 0, (int) length);
            else
                read = this.original.read(buffer);

            baos.write(buffer, 0, read);
            length -= read;

        }

        String json = new String(baos.toByteArray(), StandardCharsets.UTF_8);

        publicProps.putAll(GSON.fromJson(json, MAP_STRING_STRING_TYPE));

        this.ivLength = Integer.parseInt(this.publicHeader.get(ENCRYPTION_IV_LENGTH_LABEL));

        this.original.readLong();// data length 0

        // init cipher
        final Cipher cipher = getCipher();

        // read private properties
        this.original.read(buffer, 0, this.ivLength);// read properties iv
        IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(buffer, this.ivLength));
        cipher.init(Cipher.DECRYPT_MODE, this.encryptionKey, iv);

        final long propLength = this.original.readLong();
        length = propLength;

        baos.reset();

        while (length > 0)
        {

            if (length < buffer.length)
                read = this.original.read(buffer, 0, (int) length);
            else
                read = this.original.read(buffer);

            final int decrypted = cipher.update(buffer, 0, read, outBuffer);

            baos.write(outBuffer, 0, decrypted);
            length -= read;

        }

        baos.write(cipher.doFinal());

        json = new String(baos.toByteArray());

        props.putAll(GSON.fromJson(json, MAP_STRING_STRING_TYPE));
        this.original.readLong();// data length 0

        while (this.original.getFilePointer() < this.original.length())
        {
            baos.reset();

            final long offset = this.original.getFilePointer();

            this.original.read(buffer, 0, this.ivLength);// read properties iv
            iv = new IvParameterSpec(Arrays.copyOf(buffer, this.ivLength));
            cipher.init(Cipher.DECRYPT_MODE, this.encryptionKey, iv);

            final long metaLength = this.original.readLong();
            final long metaOffset = this.original.getFilePointer();

            length = metaLength;
            while (length > 0)
            {

                if (length < buffer.length)
                    read = this.original.read(buffer, 0, (int) length);
                else
                    read = this.original.read(buffer);

                final int decrypted = cipher.update(buffer, 0, read, outBuffer);
                baos.write(outBuffer, 0, decrypted);
                length -= read;
            }
            baos.write(cipher.doFinal());
            json = new String(baos.toByteArray());

            final Map<String, String> properties = new HashMap<>(GSON.fromJson(json, MAP_STRING_STRING_TYPE));
            final String path = properties.get(Block.PATH_LABEL);
            if (path == null)
                throw new IllegalStateException("Path of block starting at " + offset + " is not set");

            if (blocks.containsKey(path.toUpperCase(Environment.getLocale())))
                throw new IllegalStateException("Block path " + path + " already exist");

            final long dataLength = this.original.readLong();
            final long dataOffset = this.original.getFilePointer();

            final String [] tokens = path.split(Folder.REGEX_DELIMITER);

            this.root.mkdir(tokens, 1, true);

            final org.ortis.jsafebox.SafeFile dstFile;

            if (tokens.length == 2)
                dstFile = this.root;
            else
                dstFile = this.root.get(tokens, 1, tokens.length - 1);

            if (dstFile == null)
                throw new Exception("Could not find destination folder for block path " + path);

            if (!dstFile.isFolder())
                throw new Exception("Destination folder " + dstFile + " is a block");

            final Folder destinationFolder = ((Folder) dstFile);

            final long blockLength = original.getFilePointer() - offset + dataLength;
            final Block block = new Block(path, properties, offset, blockLength, metaOffset, metaLength, dataOffset, dataLength, destinationFolder);

            destinationFolder.add(block);

            blocks.put(block.getComparablePath(), block);
            this.original.seek(block.getOffset() + block.getLength());
        }

    }

    /**
     * Add data into the {@link Safe}. <b>Note that the data will be stored into the temporary safe file</b>. Use {@link Safe#save()} to save all temporary data
     * 
     * @param properties:
     *            metadata
     * @param data:
     *            data to encrypt
     * @return
     * @throws Exception
     */
    public synchronized Block add(final Map<String, String> properties, final InputStream data, TaskProbe probe) throws Exception
    {
        if (probe == null)
            probe = TaskProbe.DULL_PROBE;

        try
        {
            final String path = properties.get(Block.PATH_LABEL);

            if (path == null)
                throw new IllegalArgumentException("Property " + Block.PATH_LABEL + " is missing");

            org.ortis.jsafebox.SafeFile destinationFile = SafeFiles.get(path, this.root, this.root);

            if (destinationFile != null)
                throw new Exception("Block file " + destinationFile + " already exist");

            final String comparablePath = properties.get(Block.PATH_LABEL).toUpperCase(Environment.getLocale());

            final String [] comparableTokens = comparablePath.split(Folder.REGEX_DELIMITER);

            if (comparableTokens.length == 2 && root.getComparableName().equals(comparableTokens[0]))
                destinationFile = this.root;
            else
                destinationFile = this.root.get(comparableTokens, 1, comparableTokens.length - 1);

            if (destinationFile == null)
                throw new Exception("Destination folder " + destinationFile + " does not exists");

            if (!destinationFile.isFolder())
                throw new Exception("Destination " + destinationFile + " is not a folder");

            final Folder destinationFolder = (Folder) destinationFile;

            if (this.roBlocks.containsKey(path) || this.tempBlocks.containsKey(path))
                throw new Exception("Block path " + path + " already exist");

            final String name = properties.get(Block.NAME_LABEL);

            if (name == null)
                throw new IllegalArgumentException("Property " + Block.NAME_LABEL + " is missing");

            final Cipher cipher = getCipher();

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }

            cipher.init(Cipher.ENCRYPT_MODE, this.encryptionKey, getSecureRandom());

            final RandomAccessFile temp = getTemp();

            final long offset = temp.getFilePointer();

            temp.write(cipher.getIV());

            // write metadata

            temp.writeLong(0);
            final String metadataserial = GSON.toJson(properties);
            final byte [] metaBuffer = metadataserial.getBytes();
            final long metaOffset = temp.getFilePointer();
            final long metaLength = encrypt(new ByteArrayInputStream(metaBuffer), cipher, temp, this.bufferSize, probe);
            long position = temp.getFilePointer();

            temp.seek(offset + cipher.getIV().length);
            temp.writeLong(metaLength);
            temp.seek(position);

            // write data
            position = temp.getFilePointer();
            temp.writeLong(0);

            final long dataOffset = temp.getFilePointer();

            final long dataLength = encrypt(data, cipher, temp, this.bufferSize, probe);

            temp.seek(position);
            temp.writeLong(dataLength);
            temp.seek(temp.length());

            final Block block = new Block(path, properties, offset, temp.getFilePointer() - offset, metaOffset, metaLength, dataOffset, dataLength, destinationFolder);
            this.tempBlocks.put(block.getComparablePath(), block);

            destinationFolder.add(block);

            return block;

        } catch (final CancellationException e)
        {
            throw e;
        } catch (final Exception e)
        {
            probe.fireException(e);
            throw e;
        } finally
        {
            probe.fireTerminated();
        }
    }

    /**
     * Delete data from the {@link Safe}. <b>Note that the data wont be deleted until a call to {@link Safe#save()} is made</b>
     * 
     * @param path:
     *            path of the data to delete
     */
    public  synchronized  void delete(final String path)
    {

        final String comparablePath = path.toUpperCase(Environment.getLocale());
        Block deleted = this.blocks.get(comparablePath);

        if (deleted != null)
        {
            final Folder folder = deleted.getParent();
            folder.remove(deleted.getName());
            this.deletedBlocks.put(comparablePath, deleted);
        }

        deleted = this.tempBlocks.remove(comparablePath);

        if (deleted != null)
        {
            final Folder folder = deleted.getParent();
            folder.remove(deleted.getName());
            this.deletedBlocks.put(comparablePath, deleted);
        }
    }

    /**
     * Extract data from the {@link Safe}
     * 
     * @param block:
     *            block to extract
     * @param outputStream:
     *            destination of extracted block
     * @throws Exception
     */
    public void extract(final Block block, final OutputStream outputStream) throws Exception
    {
        extract(block.getPath(), outputStream);
    }

    /**
     * Extract data from the {@link Safe}
     * 
     * @param path:
     *            path of the block to extract
     * @param outputStream:
     *            destination of extracted block
     * @throws Exception
     */
    public  synchronized  void extract(String path, final OutputStream outputStream) throws Exception
    {

        path = path.toUpperCase(Environment.getLocale());

        Block block = this.roBlocks.get(path);

        final RandomAccessFile raf;
        if (block == null)
        {
            block = this.tempBlocks.get(path);
            raf = this.temp;
        } else
            raf = this.original;

        if (block == null)
            throw new Exception("Block " + path + " not found");

        raf.seek(block.getOffset());

        final byte [] ivBytes = new byte[this.ivLength];
        raf.read(ivBytes);

        final Cipher cipher = getCipher();

        final IvParameterSpec iv = new IvParameterSpec(ivBytes);

        cipher.init(Cipher.DECRYPT_MODE, this.encryptionKey, iv);
        raf.seek(block.getDataOffset());
        decrypt(raf, block.getDataLength(), cipher, outputStream, this.bufferSize);

    }

    /**
     * Read the metadata of a {@link Block}
     * 
     * @param block:
     *            block to read
     * @return
     * @throws Exception
     */
    public  synchronized  Map<String, String> readMetadata(final Block block) throws Exception
    {

        this.original.seek(block.getOffset());
        final byte [] ivBytes = new byte[this.ivLength];
        this.original.read(ivBytes);

        final Cipher cipher = getCipher();

        final IvParameterSpec iv = new IvParameterSpec(ivBytes);
        cipher.init(Cipher.DECRYPT_MODE, this.encryptionKey, iv);

        this.original.seek(block.getMetaOffset());

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        decrypt(this.original, block.getMetaLength(), cipher, baos, this.bufferSize);

        final String metadata = new String(baos.toByteArray());

        final Map<String, String> jsonMap = GSON.fromJson(metadata, MAP_STRING_STRING_TYPE);
        return new TreeMap<>(jsonMap);
    }

    /**
     * Discard pending modification
     */
    public  synchronized  void discardChanges() throws Exception
    {

        for (final Map.Entry<String, Block> temp : this.tempBlocks.entrySet())
        {

            Folder folder = temp.getValue().getParent();
            folder.remove(temp.getValue().getName());
        }

        this.tempBlocks.clear();

        for (final Map.Entry<String, Block> deleted : this.deletedBlocks.entrySet())
        {
            Folder folder = deleted.getValue().getParent();
            folder.add(deleted.getValue());
        }
        this.deletedBlocks.clear();

    }

    /**
     * Save the modification into the safe file. The current file is renamed and a new file is written. This is to reduce the risk of data loss. This method calls the {@link Safe#close()} before returning
     * 
     * @return
     * @throws Exception
     */
    public Safe save() throws Exception
    {
        return save(null);
    }

    public  synchronized  Safe save(TaskProbe probe) throws Exception
    {

        if (probe == null)
            probe = TaskProbe.DULL_PROBE;
        try
        {
            double progress = 0;
            probe.fireProgress(progress);

            probe.fireMessage("Creating temporary file");
            final File newFile = Files.createTempFile(originalFile.getParentFile().toPath(), null, null).toFile();

            try (RandomAccessFile destination = new RandomAccessFile(newFile, "rw"))
            {

                Cipher cipher = getCipher();

                if (probe.isCancelRequested())
                {
                    probe.fireCanceled();
                    throw new CancellationException();
                }

                destination.write(HASHER.getEmptyHash());// skip hash

                // public properties
                probe.fireMessage("Writing public header");

                String json = GSON.toJson(this.publicHeader);

                long previousPosition = destination.getFilePointer();

                destination.writeLong(0);
                long total = write(new ByteArrayInputStream(json.getBytes()), destination, this.bufferSize, probe);
                destination.writeLong(0);// no data in header
                long position = destination.getFilePointer();
                destination.seek(previousPosition);
                destination.writeLong(total);
                destination.seek(position);

                if (probe.isCancelRequested())
                {
                    probe.fireCanceled();
                    throw new CancellationException();
                }

                // private properties
                probe.fireMessage("Writing private properties");

                cipher.init(Cipher.ENCRYPT_MODE, this.encryptionKey, getSecureRandom());
                destination.write(cipher.getIV());

                json = GSON.toJson(this.privateProperties);

                previousPosition = destination.getFilePointer();
                destination.writeLong(0);
                total = encrypt(new ByteArrayInputStream(json.getBytes()), cipher, destination, this.bufferSize, probe);
                destination.writeLong(0);// no data in header
                position = destination.getFilePointer();
                destination.seek(previousPosition);
                destination.writeLong(total);
                destination.seek(position);

                if (probe.isCancelRequested())
                {
                    probe.fireCanceled();
                    throw new CancellationException();
                }

                final double steps = this.roBlocks.size() + this.tempBlocks.size() + 1;
                int completed = 0;

                for (final Block block : this.roBlocks.values())
                {
                    // add non deleted only
                    if (this.deletedBlocks.containsKey(block.getComparablePath()))
                    {
                        probe.fireMessage("Skipping deleted block " + block.getPath());
                        continue;
                    }

                    probe.fireMessage("Writing block " + block.getPath());
                    this.original.seek(block.getOffset());
                    write(this.original, block.getLength(), destination, this.bufferSize, probe);
                    completed++;
                    progress = completed / steps;
                    probe.fireProgress(progress);
                }

                final RandomAccessFile temp = getTemp();
                for (final Block block : this.tempBlocks.values())
                {

                    if (this.deletedBlocks.containsKey(block.getComparablePath()))
                    {
                        probe.fireMessage("Skipping deleted block " + block.getPath());
                        continue;
                    }

                    probe.fireMessage("Writing block " + block.getPath());
                    temp.seek(block.getOffset());

                    write(temp, block.getLength(), destination, this.bufferSize, probe);
                    completed++;
                    progress = completed / steps;
                    probe.fireProgress(progress);

                }

                probe.fireMessage("Computing hash");
                final byte [] hash = computeHash(destination, cipher, this.ivLength, this.encryptionKey, this.bufferSize, probe);
                destination.seek(0);
                destination.write(hash);

                probe.fireMessage("Closing IO streams");
                destination.close();

                close();

                probe.fireMessage("Deleting old file");

                if (!this.originalFile.delete())
                    throw new IOException("Unable to delete " + this.originalFile.getAbsolutePath());

                probe.fireMessage("Renaming file");

                if (!newFile.renameTo(this.originalFile))
                    throw new IOException("Unable to rename " + newFile.getAbsolutePath());

                if (probe.isCancelRequested())
                {
                    probe.fireCanceled();
                    throw new CancellationException();
                }

                probe.fireMessage("Opening new safe");
                probe.fireProgress(1);

                return new Safe(this.originalFile, encryptionKey, this.bufferSize);
            }
        } catch (final CancellationException e)
        {
            throw e;
        } catch (final Exception e)
        {
            probe.fireException(e);
            throw e;
        } finally
        {
            probe.fireTerminated();
        }

    }

    /**
     * Compute the hash of the {@link Safe}
     * 
     * @return
     * @throws Exception
     */
    public  synchronized  byte [] computeHash(final TaskProbe probe) throws Exception
    {
        final byte [] hash = computeHash(this.original, getCipher(), this.ivLength, this.encryptionKey, this.bufferSize, probe);
        return hash;
    }

    /**
     * Return a copy of the hash that was in the {@link Safe}'s file
     * 
     * @return
     */
    public byte [] getHash()
    {
        final byte [] destination = new byte[this.hash.length];
        System.arraycopy(this.hash, 0, destination, 0, this.hash.length);
        return destination;
    }

    private Cipher getCipher() throws Exception
    {
        final String encryption = this.publicHeader.get(ENCRYPTION_LABEL);

        if (encryption == null)
            throw new Exception("Public property '" + ENCRYPTION_LABEL + "' must be set");

        return javax.crypto.Cipher.getInstance(encryption);
    }

    @Override
    public  synchronized  void close() throws IOException
    {
        this.original.close();

        final RandomAccessFile temp = getTemp();
        if (temp != null)
        {
            temp.close();
            tempFile.delete();
        }

    }

    /**
     * Get the properties of the {@link Safe}
     * 
     * @return
     */
    public Map<String, String> getPrivateProperties()
    {
        return privateProperties;
    }

    /**
     * Get the header of the {@link Safe}
     * 
     * @return
     */
    public Map<String, String> getPublicHeader()
    {
        return publicHeader;
    }

    /**
     * Get all {@link Block} contained in the {@link Safe}
     * 
     * @return
     */
    public Map<String, Block> getBlocks()
    {
        return this.roBlocks;
    }

    /**
     * Get a {@link Block} from the {@link Safe}
     * 
     * @param path:
     *            path of the {@link Block} to retrieve
     * @return
     */
    public Block getBlock(final String path)
    {

        final String comparablePath = path.toUpperCase(Environment.getLocale());

        return this.roBlocks.get(comparablePath);

    }

    /**
     * Get a {@link Block} from the temporary {@link Safe}
     * 
     * @param path:
     *            path of the {@link Block} to retrieve
     * @return
     */
    public Block getTempBlock(final String path)
    {
        final String comparablePath = path.toUpperCase(Environment.getLocale());

        return this.tempBlocks.get(comparablePath);

    }

    /**
     * Get all {@link Block} contained in the temporary {@link Safe}
     * 
     * @return
     */
    public Map<String, Block> getTempBlocks()
    {
        return tempBlocks;
    }

    /**
     * Get deleted {@link Block}
     * 
     * @return
     */
    public Map<String, Block> getDeletedBlocks()
    {
        return deletedBlocks;
    }

    /**
     * Get root {@link Folder}
     * 
     * @return
     */
    public Folder getRootFolder()
    {
        return root;
    }

    public File getFile()
    {
        return this.originalFile;
    }

    /**
     * Get the temporary safe file
     * 
     * @return
     */
    public File getTempFile()
    {
        return tempFile;
    }

    /**
     * Get the temporary safe file
     * 
     * @return
     */
    public RandomAccessFile getTemp() throws IOException
    {
        return this.temp;
    }

    private static long encrypt(final InputStream data, final Cipher cipher, final RandomAccessFile destination, final int bufferSize, final TaskProbe probe) throws Exception
    {

        final byte [] buffer = new byte[bufferSize];
        final byte [] bufferOut = new byte[bufferSize];

        long total = 0;
        int read;
        while ((read = data.read(buffer)) > -1)
        {

            read = cipher.update(buffer, 0, read, bufferOut);
            if (read == 0)
                // data length is less than cipher block size
                System.arraycopy(buffer, 0, bufferOut, 0, buffer.length);

            total += read;
            destination.write(bufferOut, 0, read);

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }

        }

        read = cipher.doFinal(bufferOut, 0);
        destination.write(bufferOut, 0, read);
        total += read;

        return total;

    }

    private static void decrypt(final RandomAccessFile source, final long length, final Cipher cipher, final OutputStream destination, final int bufferSize) throws Exception
    {

        final byte [] buffer = new byte[bufferSize];
        final byte [] bufferOut = new byte[bufferSize];

        long remaining = length;
        int read;
        while (remaining > 0)
        {
            if (remaining < buffer.length)
                read = source.read(buffer, 0, (int) remaining);
            else
                read = source.read(buffer, 0, buffer.length);

            remaining -= read;

            read = cipher.update(buffer, 0, read, bufferOut);
            destination.write(bufferOut, 0, read);

        }

        read = cipher.doFinal(bufferOut, 0);
        destination.write(bufferOut, 0, read);

    }

    private static long write(final InputStream data, final RandomAccessFile destination, final int bufferSize, final TaskProbe probe) throws Exception
    {

        final byte [] buffer = new byte[bufferSize];

        long total = 0;
        int read;
        while ((read = data.read(buffer)) > -1)
        {
            destination.write(buffer, 0, read);
            total += read;

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }
        }

        return total;

    }

    private static void write(final RandomAccessFile source, final long length, final RandomAccessFile destination, final int bufferSize, final TaskProbe probe) throws Exception
    {

        final byte [] buffer = new byte[bufferSize];

        long remaining = length;
        int read;
        while (remaining > 0)
        {
            if (remaining < buffer.length)
                read = source.read(buffer, 0, (int) remaining);
            else
                read = source.read(buffer, 0, buffer.length);

            destination.write(buffer, 0, read);

            remaining -= read;

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }
        }

    }

    private static SecureRandom getSecureRandom()
    {
        return new SecureRandom();
    }

    /**
     * Read the header of the {@link Safe}
     * 
     * @param file:
     *            safe file to read
     * @param bufferSize:
     *            size of the <code>byte</code> buffer to be used in IO operation
     * @return
     * @throws IOException
     */
    public static Map<String, String> readHeader(final File file, final int bufferSize) throws IOException
    {
        RandomAccessFile raf = null;

        try
        {
            raf = new RandomAccessFile(file, "rw");
            final byte [] buffer = new byte[bufferSize];
            final ByteArrayOutputStream baos = new ByteArrayOutputStream(buffer.length);

            raf.read(buffer, 0, HASHER.getHashLength());// skip hash

            long length = raf.readLong();
            int read;
            while (length > 0)
            {

                if (length < buffer.length)
                    read = raf.read(buffer, 0, (int) length);
                else
                    read = raf.read(buffer);

                baos.write(buffer, 0, read);
                length -= read;

            }
            final String header = new String(baos.toByteArray(), StandardCharsets.UTF_8);
            return GSON.fromJson(header, MAP_STRING_STRING_TYPE);

        } finally
        {
            if (raf != null)
                raf.close();
        }

    }

    /**
     * Compute the hash value of {@link Safe} file
     * 
     * @param safeFile
     * @param cipher
     * @param ivLength
     * @param encryptionKey
     * @param bufferSize
     * @return
     * @throws Exception
     */
    public static byte [] computeHash(final RandomAccessFile safeFile, final Cipher cipher, final int ivLength, final Key encryptionKey, final int bufferSize, TaskProbe probe) throws Exception
    {

        if (probe == null)
            probe = TaskProbe.DULL_PROBE;

        try
        {
            final long previousPosition = safeFile.getFilePointer();

            final byte [] buffer = new byte[bufferSize];
            final byte [] bufferOut = new byte[bufferSize];


            safeFile.seek(HASHER.getHashLength());
            final ByteBuffer byteBuffer = ByteBuffer.allocate((int) (safeFile.length() - safeFile.getFilePointer()));

            long length = safeFile.readLong();
            byteBuffer.putLong(length);

            int read;

            // header
            while (length > 0)// read header
            {

                if (length < buffer.length)
                    read = safeFile.read(buffer, 0, (int) length);
                else
                    read = safeFile.read(buffer);

                byteBuffer.put(buffer, 0, read);
                length -= read;
            }

            byteBuffer.putLong(safeFile.readLong());// header's data length 0

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }

            // properties
            safeFile.read(buffer, 0, ivLength);// read properties iv
            byteBuffer.put(buffer, 0, ivLength);

            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(buffer, ivLength));
            cipher.init(Cipher.DECRYPT_MODE, encryptionKey, iv);

            length = safeFile.readLong();
            byteBuffer.putLong(length);

            while (length > 0)// read properties
            {
                if (length < buffer.length)
                    read = safeFile.read(buffer, 0, (int) length);
                else
                    read = safeFile.read(buffer, 0, buffer.length);

                length -= read;

                read = cipher.update(buffer, 0, read, bufferOut);
                byteBuffer.put(bufferOut, 0, read);

            }

            read = cipher.doFinal(bufferOut, 0);
            byteBuffer.put(bufferOut, 0, read);

            byteBuffer.putLong(safeFile.readLong());// properties data length 0

            if (probe.isCancelRequested())
            {
                probe.fireCanceled();
                throw new CancellationException();
            }

            // blocks
            while (safeFile.getFilePointer() < safeFile.length())
            {
                safeFile.read(buffer, 0, ivLength);// read properties iv
                byteBuffer.put(buffer, 0, ivLength);
                iv = new IvParameterSpec(Arrays.copyOf(buffer, ivLength));
                cipher.init(Cipher.DECRYPT_MODE, encryptionKey, iv);

                // read metadata
                length = safeFile.readLong();
                byteBuffer.putLong(length);

                while (length > 0)
                {
                    if (length < buffer.length)
                        read = safeFile.read(buffer, 0, (int) length);
                    else
                        read = safeFile.read(buffer, 0, buffer.length);

                    length -= read;

                    read = cipher.update(buffer, 0, read, bufferOut);
                    byteBuffer.put(bufferOut, 0, read);

                }

                read = cipher.doFinal(bufferOut, 0);
                byteBuffer.put(bufferOut, 0, read);

                if (probe.isCancelRequested())
                {
                    probe.fireCanceled();
                    throw new CancellationException();
                }

                // read data
                length = safeFile.readLong();
                byteBuffer.putLong(length);

                while (length > 0)
                {
                    if (length < buffer.length)
                        read = safeFile.read(buffer, 0, (int) length);
                    else
                        read = safeFile.read(buffer, 0, buffer.length);

                    length -= read;

                    read = cipher.update(buffer, 0, read, bufferOut);
                    byteBuffer.put(bufferOut, 0, read);

                    if (probe.isCancelRequested())
                    {
                        probe.fireCanceled();
                        throw new CancellationException();
                    }

                }

                read = cipher.doFinal(bufferOut, 0);
                byteBuffer.put(bufferOut, 0, read);

            }

            safeFile.seek(previousPosition);

            return HASHER.hash(byteBuffer.array());

        } catch (final CancellationException e)
        {
            throw e;
        } catch (final Exception e)
        {
            probe.fireException(e);
            throw e;
        } finally
        {
            probe.fireTerminated();
        }
    }

    /**
     * Create a new {@link Safe}
     * 
     * @param file
     * @param key
     * @param publicHeader
     * @param privateProperties
     * @param bufferSize
     * @return
     * @throws Exception
     */
    public static Safe create(final File file, final byte [] key, final Map<String, String> publicHeader, final Map<String, String> privateProperties, final int bufferSize) throws Exception
    {

        final String encryption = publicHeader.get(ENCRYPTION_LABEL);

        if (encryption == null)
            throw new Exception("Public property '" + ENCRYPTION_LABEL + "' must be set");

        if (!publicHeader.containsKey(ENCRYPTION_IV_LENGTH_LABEL))
            throw new Exception("Public property '" + ENCRYPTION_IV_LENGTH_LABEL + "' must be set");

        if (!publicHeader.containsKey(PBKDF2_SALT_LABEL))
            throw new Exception("Public property '" + PBKDF2_SALT_LABEL + "' must be set");

        if (!publicHeader.containsKey(PBKDF2_ITERATION_LABEL))
            throw new Exception("Public property '" + PBKDF2_ITERATION_LABEL + "' must be set");

        Cipher cipher = javax.crypto.Cipher.getInstance(encryption);

        final String keyAlgo = publicHeader.get(KEY_ALGO_LABEL);
        if (keyAlgo == null)
            throw new Exception("Public property '" + KEY_ALGO_LABEL + "' must be set");

        final SecretKeySpec keySpec = new SecretKeySpec(key, keyAlgo);

        if (file.exists())
            throw new IOException("File " + file + " already exist");

        if (!file.createNewFile())
            throw new IOException("Could not create file " + file);

        final RandomAccessFile raf = new RandomAccessFile(file, "rw");

        long total, position, previousPosition;

        cipher.init(Cipher.ENCRYPT_MODE, keySpec, getSecureRandom());

        raf.write(HASHER.getEmptyHash());// global hash

        // header
        position = raf.getFilePointer();
        // no IV in header
        raf.writeLong(0);
        final String header = GSON.toJson(publicHeader);
        total = write(new ByteArrayInputStream(header.getBytes(StandardCharsets.UTF_8)), raf, bufferSize, TaskProbe.DULL_PROBE);
        raf.writeLong(0);// no data in header block

        previousPosition = raf.getFilePointer();
        raf.seek(position);
        raf.writeLong(total);

        raf.seek(previousPosition);

        // properties
        position = raf.getFilePointer();
        raf.write(cipher.getIV());
        final String privatePropsJson = GSON.toJson(privateProperties == null ? new HashMap<>() : privateProperties);
        previousPosition = raf.getFilePointer();
        raf.writeLong(0L);
        total = encrypt(new ByteArrayInputStream(privatePropsJson.getBytes(StandardCharsets.UTF_8)), cipher, raf, bufferSize, TaskProbe.DULL_PROBE);
        raf.writeLong(0);// no data in properties block

        raf.seek(previousPosition);
        raf.writeLong(total);

        // write global hash
        final byte [] hash = computeHash(raf, cipher, cipher.getIV().length, keySpec, bufferSize, null);
        raf.seek(0);
        raf.write(hash);

        raf.close();

        return new Safe(file, keySpec, bufferSize);

    }

}

org/ortis/jsafebox/Utils.java

/*******************************************************************************
 * Copyright 2018 Ortis (cao.ortis.org@gmail.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under the License.
 ******************************************************************************/

package org.ortis.jsafebox;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Utility class
 * 
 * @author Ortis <br>
 *         2018 Apr 26 8:06:47 PM <br>
 */
public class Utils
{

    public final static String SEPARATOR_REGEX = "[/|" + Pattern.quote(java.io.File.separator) + "]";

    private final static String SYSTEM_PATH_DELIMITER_REGEX = Pattern.quote(File.separator) + "|" + Pattern.quote("/") + "|" + Pattern.quote("\");

    public static byte [] passwordToBytes(final char [] chars)
    {
        final CharBuffer charBuffer = CharBuffer.wrap(chars);
        final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
        final byte [] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
        Arrays.fill(charBuffer.array(), 'u0000'); // clear sensitive data
        Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
        return bytes;
    }

    /**
     * Open a {@link Safe}
     * 
     * @param safeFilePath:
     *            system path to the safe file
     * @param password:
     *            the encryption password
     * @param bufferSize:size
     *            of the <code>byte</code> buffer to be used in IO operation
     * @param log
     * @return
     * @throws Exception
     */
    public static Safe open(final String safeFilePath, final char [] password, final int bufferSize, final Logger log) throws Exception
    {
        final File file = new File(safeFilePath);

        if (!file.exists())
            throw new IOException("Safe file " + file + " doest not exist");

        final Map<String, String> header = Safe.readHeader(file, bufferSize);

        final String encyption = header.get(Safe.ENCRYPTION_LABEL);
        if (encyption == null)
            throw new Exception("Could not read property '" + Safe.ENCRYPTION_LABEL + "' from header");

        if (log != null)
            log.fine("Encryption type " + encyption);

        if (!header.containsKey(Safe.KEY_ALGO_LABEL))
            throw new Exception("Could not read property '" + Safe.KEY_ALGO_LABEL + "' from header");

        if (log != null)
            log.fine("Key algorithm " + header.get(Safe.KEY_ALGO_LABEL));


        final byte [] salt = (byte [])Safe.GSON.fromJson(header.get(Safe.PBKDF2_SALT_LABEL), Safe.BYTE_ARRAY_TYPE);

        PBEKeySpec spec = new PBEKeySpec(password, salt, Safe.PBKDF2_ITERATION, 128);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        final byte [] key = skf.generateSecret(spec).getEncoded();

        final SecretKeySpec keySpec = new SecretKeySpec(key, header.get(Safe.KEY_ALGO_LABEL));

        return new Safe(file, keySpec, bufferSize);
    }

    public static List<java.io.File> parseSystemPath(String query, final List<java.io.File> destination) throws IOException
    {
        final String [] tokens = query.split(SYSTEM_PATH_DELIMITER_REGEX);

        Path baseDirectory = null;

        if (tokens[0].equals(".") || tokens[0].equals(".."))
        {
            baseDirectory = new File(tokens[0]).toPath();

            final StringBuilder sb = new StringBuilder();
            for (int i = 1; i < tokens.length; i++)
                if (sb.length() == 0)
                    sb.append(tokens[i]);
                else
                    sb.append(File.separator + tokens[i]);

            query = "**" + File.separator + sb.toString();

        } else
        {

            final String comparableToken = tokens[0].toUpperCase();
            for (final File root : File.listRoots())
                if (root.getAbsolutePath().toUpperCase().equals(comparableToken))
                {
                    // perfect match
                    baseDirectory = root.toPath();
                    break;

                }

            if (baseDirectory == null)
                for (final File root : File.listRoots())
                {
                    String rootPath = root.getAbsolutePath().toUpperCase();
                    rootPath = rootPath.substring(0, rootPath.length() - 1);
                    if (rootPath.equals(comparableToken))
                    {
                        baseDirectory = root.toPath();
                        break;
                    }

                }
        }

        if (baseDirectory == null)
            throw new IOException("Could not locate base directory '" + tokens[0] + "'");

        Path path = baseDirectory;
        for (int i = 1; i < tokens.length; i++)
        {

            try
            {
                path = Paths.get(path.toString(), tokens[i]);
            } catch (final Exception e)
            {
                // here, we have reach a special character and the start point for the search is
                // in path
            }
        }

        final String escapedQuery = query.replace("\", "\\");// PathMatcher does not escape backslash properly. Need to do the escape manually for Windows OS path handling. This might be a bug of Java implentation.
        // Need to check on Oracle bug report database.

        final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + escapedQuery);
        Files.walkFileTree(path, new FileVisitor<Path>()
        {

            @Override
            public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException
            {

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException
            {
                if (pathMatcher.matches(dir))
                {
                    destination.add(dir.toFile());
                    return FileVisitResult.SKIP_SUBTREE;
                }

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException
            {

                if (pathMatcher.matches(file))
                    destination.add(file.toFile());

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException
            {
                return FileVisitResult.CONTINUE;
            }
        });

        return destination;

    }

    /**
     * Return the MIME type of a file
     * 
     * @param file
     * @return
     */
    public static String getMIMEType(final java.io.File file)
    {

        final String name = file.getName().toUpperCase();

        if (name.endsWith(".TXT"))
            return "text/plain";
        else if (name.endsWith(".CSV"))
            return "text/csv";
        else if (name.endsWith(".HTM") || name.endsWith(".HTML"))
            return "text/html";
        else if (name.endsWith(".JPG") || name.endsWith(".JPEG"))
            return "image/jpg";
        else if (name.endsWith(".PNG"))
            return "image/png";
        else if (name.endsWith(".BM") || name.endsWith(".BMP"))
            return "image/bmp";
        else if (name.endsWith(".PDF"))
            return "application/pdf";
        else if (name.endsWith(".AVI"))
            return "video/x-msvideo";
        else if (name.endsWith(".MPEG"))
            return "video/mpeg";
        else if (name.endsWith(".MP4"))
            return "video/mp4";
        else if (name.endsWith(".MKV"))
            return "video/x-matroska";
        else if (name.endsWith(".MP3"))
            return "audio/mpeg";
        else
            return "application/octet-stream";
    }

    /**
     * Format the exception message
     * 
     * @param t
     * @return
     */
    public static String formatException(final Throwable t)
    {
        if (t == null)
            return null;

        final Throwable cause = t.getCause();
        final String msg = cause == null ? null : formatException(cause);
        return formatException(t.getClass(), msg, t.toString(), t.getStackTrace());

    }

    private static String formatException(final Class<?> exceptionClass, final String cause, final String msg, final StackTraceElement [] exceptionStack)
    {
        final StringBuilder builder = new StringBuilder();

        if (msg != null)
            builder.append(msg);

        if (exceptionStack != null)
        {
            builder.append(System.lineSeparator());
            for (int i = 0; i < exceptionStack.length; i++)
            {
                final String stackElement = exceptionStack[i].toString();

                builder.append(stackElement + System.lineSeparator());
            }
        }

        if (cause != null)
            builder.append("Caused by " + cause);

        return builder.toString();
    }

    /**
     * Remove forbidden <code>char</code> from the path and replace them with <code>substitute</code>
     * 
     * @param path:
     *            the path to sanitize
     * @param delimiter:
     *            delimiter of the path
     * @param substitute:
     *            replacement char
     * @return
     */
    public static String sanitize(final String path, final Character delimiter, final Character substitute)
    {
        final String [] tokens = path.split(Pattern.quote(Character.toString(delimiter)));

        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < tokens.length; i++)
        {
            if (i < tokens.length - 1)
                sb.append(sanitizeToken(tokens[i], substitute) + delimiter);
            else
                sb.append(sanitizeToken(tokens[i], substitute));

        }

        return sb.toString();
    }

    public static String sanitizeToken(final String token, final Character substitute)
    {

        final StringBuilder sb = new StringBuilder(token);

        final Character replacement = substitute;

        c: for (int i = 0; i < sb.length(); i++)
        {

            if (sb.charAt(i) == java.io.File.separatorChar || sb.charAt(i) == Folder.DELIMITER)
            {
                if (replacement == null)
                    sb.deleteCharAt(i--);
                else
                    sb.setCharAt(i, replacement);
                continue c;
            }

            for (final char c : Environment.getForbidenChars())
                if (sb.charAt(i) == c)
                {
                    if (replacement == null)
                        sb.deleteCharAt(i--);
                    else
                        sb.setCharAt(i, replacement);
                    continue c;
                }
        }
        return sb.toString();

    }

    public static boolean isHeadless()
    {
        if (GraphicsEnvironment.isHeadless())
            return true;

        try
        {
            GraphicsDevice [] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
            return screenDevices == null || screenDevices.length == 0;
        } catch (HeadlessException e)
        {
            return true;
        }
    }

}


Get this bounty!!!