jfs.sync.encryption.AbstractEncryptedStorageAccess.java Source code

Java tutorial

Introduction

Here is the source code for jfs.sync.encryption.AbstractEncryptedStorageAccess.java

Source

/*
 * Copyright (C) 2010-2013, Martin Goellnitz
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301, USA
 */
package jfs.sync.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import jfs.conf.JFSConfig;
import jfs.sync.base.AbstractJFSFileProducerFactory;
import jfs.sync.util.SecurityUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public abstract class AbstractEncryptedStorageAccess {

    private static final Log LOG = LogFactory.getLog(AbstractEncryptedStorageAccess.class);

    /** 111 codes for now - so there's some room left */
    private static final char[] FILE_NAME_CHARACTERS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.', ' ', '_', '-',
        'T', 'S', 'C', 'O', 'P', 'L', 'R', 'M', 'A', 'D', 'E', 'F', 'B', 'V', 'U', 'H', 'W', '<', '>', '*', ':', '/', '|', 'G',
        'I', 'J', 'K', 'N', 'Q', 'X', 'Y', 'Z', '', '', '', '', '', '', '', '+', '=', '{', '}', '[', ']', '$', '\'', '@',
        '!', '&', '%', '~', ',', '#', ';', '(', ')', '', '', '', '', '`', '', '', '', '', '?', '"', '', '', ''};

    private static final char[] DYNAMIC_SPECIAL_CODES = {'<', '>', '?', '*', ':', '"'};

    private static final char[] CODES = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', 'a', 'b', 'c', 'd', 'e',
        'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '', '', '',
        '', '', '', '', '\'', '', '', '@', '!', '&', '%', '~', '#', '(', ')', '[', ']', ',', '.', '{', '}', '', '`', '',
        '', '', '', '', '?', '', '?', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
        '', '', '', '', '', '', '', '', '', '', '', '', ''};

    private final byte[] reverseCodes = new byte[256];

    private final byte[] reverseCharacters = new byte[256];

    private final int longIndex;

    private final Map<String, String> encryptionCache = new HashMap<>();

    private final Map<String, String> decryptionCache = new HashMap<>();

    private static int decoding_mask = 32;

    private static int bits = 6;


    /**
     * Storage Access with encrypted everything - names, contents and optional meta data.
     * When used in six bit mode, most file storage back ends will be able to store the generated files names.
     * While some may need short path names where seven bit default is more helpful.
     *
     * @param shortenPaths extend to seven bit file name character table
     */
    public AbstractEncryptedStorageAccess(boolean shortenPaths) {
        if (CODES.length!=128) {
            throw new RuntimeException("Character missing in 7bit table");
        } // if
        if (shortenPaths) {
            decoding_mask = 64;
            bits = 7;
        } // if
        for (int i = 0; i<CODES.length; i++) {
            char c = CODES[i];
            reverseCodes[c] = (byte) i;
        } // for
        for (byte i = 0; i<FILE_NAME_CHARACTERS.length; i++) {
            reverseCharacters[FILE_NAME_CHARACTERS[i]] = i;
        } // for
        longIndex = reverseCharacters['|'];
    } // AbstractDisguiseStorageAccess()


    public abstract String getSeparator();


    public abstract String getCipherSpec();


    protected String[] getPathAndName(String relativePath) {
        return AbstractJFSFileProducerFactory.getPathAndName(relativePath, getSeparator());
    } // getPathAndName()


    protected String getLastPathElement(String defaultValue, String relativePath) {
        String result = defaultValue;
        int idx = relativePath.lastIndexOf(getSeparator());
        if (idx>=0) {
            idx++;
            result = relativePath.substring(idx);
        } // if
        return result;
    } // getLastPathElement()


    protected String getPassword(String relativePath) {
        String result = "";

        String pwd = JFSConfig.getInstance().getEncryptionPassPhrase();

        // Argh: We forgot to do this on the windows side so now we have to go this way
        relativePath = relativePath.replace('/', '\\');

        int i = 0;
        int j = relativePath.length()-1;
        while ((i<pwd.length())||(j>=0)) {
            if (i<pwd.length()) {
                result += pwd.charAt(i++);
            } // if
            if (j>=0) {
                result += relativePath.charAt(j--);
            } // if
        } // while

        return result;
    } // getPassword()


    protected byte[] getCredentials(String relativePath, String salt) {
        String localPwd = getPassword(relativePath)+salt;
        byte[] localBytes = getEncodedFileName("", localPwd);
        byte[] credentials = new byte[32];
        for (int i = 0; i<32; i++) {
            credentials[i] = localBytes[i];
        } // for
        return credentials;
    } // getCredentials()


    protected abstract byte[] getCredentials(String relativePath);


    private void generateSpecialCodes(String relativePath, List<String> specialCodes, List<Integer> specialLengths) {
        String specialCode = getLastPathElement("", relativePath);
        int specialLength = specialCode.length();

        specialCodes.add(specialCode);
        specialLengths.add(specialLength);
        if (specialLength>2) {
            String scCap = specialCode.substring(1);
            specialCodes.add(scCap);
            specialLengths.add(scCap.length());
            String scSing = specialCode.substring(0, specialLength-1);
            specialCodes.add(scSing);
            specialLengths.add(scSing.length());
        } // if

        if (specialLength!=0) {
            String[] pathAndName = getPathAndName(relativePath);
            String specialCode2 = getLastPathElement("", pathAndName[0]);
            int specialLength2 = specialCode2.length();
            if (specialLength2>2) {
                String scCap = specialCode2.substring(1);
                specialCodes.add(scCap);
                specialLengths.add(scCap.length());
                String scSing = specialCode2.substring(0, specialLength2-1);
                specialCodes.add(scSing);
                specialLengths.add(scSing.length());
            } // if
        } // if
    } // generateSpecialCodes()


    protected String getDecodedFileName(String relativePath, byte[] bytes) {
        List<String> specialCodes = new ArrayList<String>();
        List<Integer> specialLengthes = new ArrayList<Integer>();

        generateSpecialCodes(relativePath, specialCodes, specialLengthes);

        StringBuilder result = new StringBuilder();
        int index = 0;
        int bc = 0;
        int currentBitSize = 6;
        for (int i = 0; i<bytes.length; i++) {
            for (int mask = 128; mask>0; mask = mask>>1) {
                int bit = ((bytes[i]&mask)==0) ? 0 : 1;
                index = (index<<1)+bit;
                bc++;
                if (bc==currentBitSize) {
                    char code = FILE_NAME_CHARACTERS[index];
                    if (code=='|') {
                        currentBitSize = 7;
                    } else {
                        if (code!='/') {
                            String value = ""+code;
                            for (int sci = 0; sci<specialCodes.size(); sci++) {
                                if (code==DYNAMIC_SPECIAL_CODES[sci]) {
                                    value = specialCodes.get(sci);
                                } // if
                            } // for
                            result.append(value);
                        } else {
                            // TODO: this is the element which will later whipe out trailling stuff
                            i = bytes.length; // end first loop
                            mask = 0; // break second loop
                        } // if
                        currentBitSize = 6;
                    } // if
                    bc = 0;
                    index = 0;
                } // if
            } // for
        } // for
        return result.toString();
    } // getDecodedFileName()


    protected byte[] getEncodedFileName(String relativePath, String name) {
        List<String> specialCodes = new ArrayList<String>();
        List<Integer> specialLengths = new ArrayList<Integer>();
        generateSpecialCodes(relativePath, specialCodes, specialLengths);

        List<Byte> resultList = new ArrayList<Byte>();

        int bc = 0;
        byte value = 0;
        for (int i = 0; i<name.length(); i++) {
            char code = name.charAt(i);
            if ((int) code>256) {
                LOG.error("getEncodedFileName() Strange code at "+name.charAt(i)+" ("+relativePath+":"+name+")");
            } // if
            boolean noSpecial = true;
            for (int sci = 0; (sci<specialCodes.size())&&(noSpecial); sci++) {
                Integer sl = specialLengths.get(sci);
                if (sl!=0) {
                    String sc = specialCodes.get(sci);
                    if (name.indexOf(sc, i)==i) {
                        // if (log.isWarnEnabled()) {
                        // log.warn("using special character '"+DYNAMIC_SPECIAL_CODES[sci]+" "+specialCodes.get(sci)+"' in "
                        // +relativePath+getSeparator()+name);
                        // } // if
                        code = DYNAMIC_SPECIAL_CODES[sci];
                        noSpecial = false;
                        i--;
                        i += sl;
                    } // if
                } // if
            } // for
            byte index = reverseCharacters[code];
            // counters[code]++ ;
            if (index>longIndex) {
                // issue prefix character with 6 bits
                for (int mask = 32; mask>0; mask = mask>>1) {
                    int bit = (longIndex&mask)==0 ? 0 : 1;
                    value = (byte) ((value<<1)+bit);
                    bc++;
                    if (bc==8) {
                        resultList.add(value);
                        bc = 0;
                        value = 0;
                    } // if
                } // for
                // issue long 7 bit character
                for (int mask = 64; mask>0; mask = mask>>1) {
                    int bit = (index&mask)==0 ? 0 : 1;
                    // System.out.print(bit);
                    value = (byte) ((value<<1)+bit);
                    bc++;
                    if (bc==8) {
                        resultList.add(value);
                        bc = 0;
                        value = 0;
                    } // if
                } // for
                // System.out.print("|");
            } else {
                // issue short 6 bit character
                for (int mask = 32; mask>0; mask = mask>>1) {
                    int bit = (index&mask)==0 ? 0 : 1;
                    // System.out.print(bit);
                    value = (byte) ((value<<1)+bit);
                    bc++;
                    if (bc==8) {
                        resultList.add(value);
                        bc = 0;
                        value = 0;
                    } // if
                } // for
                // System.out.print("|");
            } // if
        } // for
        if (bc>0) {
            value = (byte) (value<<(8-bc));
            if (bc==2) {
                value = (byte) (value|(reverseCharacters['/']));
            } // if
            if (bc==1) {
                value = (byte) (value|(2*reverseCharacters['/']));
            } // if
            resultList.add(value);
        } // if
        // System.out.println(" - "+bc);

        byte[] result = new byte[resultList.size()];

        int i = 0;
        for (byte b : resultList) {
            result[i++] = b;
        } // for

        return result;
    } // getEncodedFileName()


    protected String getDecryptedFileName(String relativePath, String name) {
        if (decryptionCache.containsKey(relativePath+getSeparator()+name)) {
            return decryptionCache.get(relativePath+getSeparator()+name);
        } // if
        try {
            List<Byte> resultList = new ArrayList<Byte>();

            // System.out.print("D: ");
            int bc = 0;
            byte value = 0;
            for (int i = 0; i<name.length(); i++) {
                char code = name.charAt(i);
                byte index = reverseCodes[code];
                for (int mask = decoding_mask; mask>0; mask = mask>>1) {
                    int bit = (index&mask)==0 ? 0 : 1;
                    // System.out.print(bit);
                    value = (byte) ((value<<1)+bit);
                    bc++;
                    if (bc==8) {
                        // System.out.print(" "+value+" ");
                        resultList.add(value);
                        bc = 0;
                        value = 0;
                    } // if
                } // for
            } // for

            byte[] decodedBytes = new byte[resultList.size()];
            int i = 0;
            for (byte b : resultList) {
                decodedBytes[i++] = b;
            } // for

            // System.out.println("");

            Cipher decrypter = SecurityUtils.getCipher(getCipherSpec(), Cipher.DECRYPT_MODE, getCredentials(relativePath));
            byte[] decryptedBytes = decrypter.doFinal(decodedBytes);
            // name = new String(decryptedBytes, "UTF-8");
            String decryptedName = getDecodedFileName(relativePath, decryptedBytes);
            decryptionCache.put(relativePath+getSeparator()+name, decryptedName);
            name = decryptedName;
        } catch (NoSuchAlgorithmException nsae) {
            LOG.error("getDecryptedFileName() No Such Algorhithm "+nsae.getLocalizedMessage());
        } catch (NoSuchPaddingException nspe) {
            LOG.error("getDecryptedFileName() No Such Padding "+nspe.getLocalizedMessage());
        } catch (InvalidKeyException ike) {
            LOG.error("getDecryptedFileName() Invalid Key "+ike.getLocalizedMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            LOG.error("getDecryptedFileName() ArrayIndexOutOfBoundsException", e);
        } catch (IllegalBlockSizeException e) {
            LOG.error("getDecryptedFileName() IllegalBlockSizeException", e);
        } catch (BadPaddingException e) {
            LOG.error("getDecryptedFileName() BadPaddingException", e);
        } // try/catch
        return name;
    } // getDecryptedFileName()


    protected String getEncryptedFileName(String relativePath, String pathElement) {
        if (encryptionCache.containsKey(relativePath+getSeparator()+pathElement)) {
            return encryptionCache.get(relativePath+getSeparator()+pathElement);
        } // if
        try {
            Cipher encrypter = SecurityUtils.getCipher(getCipherSpec(), Cipher.ENCRYPT_MODE, getCredentials(relativePath));
            byte[] bytes = encrypter.doFinal(getEncodedFileName(relativePath, pathElement));
            StringBuilder result = new StringBuilder();
            int index = 0;
            int bc = 0;
            // System.out.print("E: ");
            for (int i = 0; i<bytes.length; i++) {
                for (int mask = 128; mask>0; mask = mask>>1) {
                    int bit = ((bytes[i]&mask)==0) ? 0 : 1;
                    // System.out.print(bit);
                    index = (index<<1)+bit;
                    bc++;
                    if (bc==bits) {
                        char code = CODES[index];
                        result.append(code);
                        bc = 0;
                        index = 0;
                    } // if
                } // for
                // System.out.print(" "+bytes[i]+" ");
            } // for
            index = index<<(bits-bc);
            char code = CODES[index];
            result.append(code);
            String resultString = result.toString();
            // System.out.println("");
            encryptionCache.put(relativePath+getSeparator()+pathElement, resultString);
            pathElement = resultString;
        } catch (NoSuchAlgorithmException nsae) {
            LOG.error("getEncryptedFileName() No Such Algorhithm "+nsae.getLocalizedMessage());
        } catch (NoSuchPaddingException nspe) {
            LOG.error("getEncryptedFileName() No Such Padding "+nspe.getLocalizedMessage());
        } catch (InvalidKeyException ike) {
            LOG.error("getEncryptedFileName() Invalid Key "+ike.getLocalizedMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            LOG.error("getEncryptedFileName("+pathElement+") ArrayIndexOutOfBoundsException", e);
        } catch (IllegalBlockSizeException e) {
            LOG.error("getEncryptedFileName("+pathElement+") IllegalBlockSizeException", e);
        } catch (BadPaddingException e) {
            LOG.error("getEncryptedFileName("+pathElement+") BadPaddingException", e);
        } // try/catch
        return pathElement;
    } // getEncryptedFileName()


    protected String getFileName(String relativePath) {
        String[] pathElements = relativePath.split("\\"+getSeparator());
        String path = "";
        String originalPath = "";
        for (String pathElement : pathElements) {
            if (StringUtils.isNotEmpty(pathElement)) {
                String encodedString = getEncryptedFileName(originalPath, pathElement);
                String checkBack = getDecryptedFileName(originalPath, encodedString);
                // log.warn("getFile() "+pathElement+" == "+checkBack+"?");
                if (!checkBack.equals(pathElement)) {
                    LOG.fatal("getFileName("+originalPath+") "+pathElement+" != "+checkBack+" in "+relativePath);
                    throw new RuntimeException("getFileName() "+pathElement+" != "+checkBack);
                } // if
                path += getSeparator()+encodedString;
                originalPath += getSeparator()+pathElement;
            } // if
        } // for
        if (LOG.isDebugEnabled()) {
            LOG.debug("getFilename() "+relativePath+" -> "+path);
        } // if
        if (LOG.isWarnEnabled()) {
            if (path.length()>245) {
                LOG.warn("getFileName() long path "+path.length()+" for "+relativePath);
            } // if
        } // if
        return path;
    } // getFileName()


    protected String getMetaDataFileName(String relativePath) {
        StringBuilder result = new StringBuilder(getLastPathElement(JFSConfig.getInstance().getEncryptionPassPhrase(), relativePath));
        result.reverse();
        if (result.length()>8) {
            result.delete(0, result.length()-8);
        } // if
        result.append(".mt");
        return result.toString();
    } // getMetaDataFileName()


    protected String getMetaDataPath(String relativePath) {
        return relativePath+getSeparator()+getMetaDataFileName(relativePath);
    } // getMetaDataPath()

} // AbstractDisguiseStorageAccess