/*
* SecureStorage.java
* Copyright (C) 2013 SINTEF (http://www.sintef.no)
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The MIT License (MIT)
* http://opensource.org/licenses/mit-license.php
*
*/
package eu.aniketos.wp1.ststool.threats.preferences;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* SecureStorage.java
*
* Utility class used to encrypt and decrypt a String without using a password
*
* @author Mauro Poggianella
*
*/
final class SecureStorage {
/**
* The initializations vector for the encryption
*/
private final IvParameterSpec ivSpec;
/**
* The initializations vector for the encryption
*/
private final byte[] STATIC_PSW;
/**
* The password insert sequence <br>
* Note: must be the <b>same length</b> as {@link #PSW_LENGHT}
*/
private final int[] sequenceSteps;
/**
* Length of the generated password <br>
* Note: using DES encryption it must be long exactly 8 bytes
*/
private final static int PSW_LENGHT = 8;
SecureStorage(long seed) {
Random random = new Random(seed);
byte[] ivSpec = new byte[8];
random.nextBytes(ivSpec);
this.ivSpec = new IvParameterSpec(ivSpec);
byte[] staticPsw = new byte[8];
random.nextBytes(staticPsw);
this.STATIC_PSW = staticPsw;
int[] sequenceSteps = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
for (int i = 0; i < sequenceSteps.length; i++) {
int randomPosition = random.nextInt(sequenceSteps.length);
int temp = sequenceSteps[i];
sequenceSteps[i] = sequenceSteps[randomPosition];
sequenceSteps[randomPosition] = temp;
}
this.sequenceSteps = sequenceSteps;
}
public static SecureStorage getDefault() {
return new SecureStorage(1234567890);
}
/**
* Method that return a configured {@link Cipher}.<br>
* It set the {@link #ivSpec initialization vector} and the encriptyion to
* DES
*
* @param psw
* the password used to encrypt<br>
* Must be 8 bytes length.
* @param mode
* The mode that the {@link Cipher} should operate.<br>
* possible values are:<br>
* {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
* @return the configured {@link Cipher}.
*/
private final Cipher getChiper(byte[] psw, int mode) {
try {
Cipher chiper = Cipher.getInstance("DES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(psw, "DES");
chiper.init(mode, key, ivSpec);
return chiper;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Encrypt a String that can be decrypted with
* {@link SecureStorage#decryptString(String)}
*
* @param string
* the string that have to be encrypted;
* @return the encrypted String
*/
public String encryptString(String string, boolean staticPassword) {
byte[] psw = staticPassword ? STATIC_PSW : genPassword();
byte[] encriptedDes = performEncoding(string.getBytes(), psw, true);
byte[] encByte = magicInsert(encriptedDes, psw);
return byteArrayToHexString(encByte);
}
/**
* Encrypt a String that can be decrypted with
* {@link SecureStorage#decryptString(String)}
*
* @param string
* the string that have to be encrypted;
* @return the encrypted String
*/
public String encryptString(String string) {
return encryptString(string, false);
}
/**
* Decrypt a String that must has been encrypted with
* {@link SecureStorage#encryptString(String)}
*
* @param string
* the string that have to be encrypted;
* @return the decrypted String or null if the string passed as input is
* invalid;
*/
public String decryptString(String string) {
try {
byte[] encByte = hexStringToByteArray(string);
byte[][] extracted = magicExtract(encByte);
byte[] output = performEncoding(extracted[0], extracted[1], false);
return new String(output);
} catch (Exception e) {
return null;
}
}
/**
* This method encrypt or decrypt a byte[] input String using a DES
* algorithm and a password
*
* @param input
* the <code>byte[]</code> that need to be encrypted/decrypted
* @param psw
* the <code>byte[]</code> used as password for the operation
* @param encrypt
* - <code>true</code> if the method must encrypt<br>
* <code>false</code> if the method must decrypt<br>
* @return
*/
private byte[] performEncoding(byte[] input, byte[] psw, boolean encrypt) {
try {
int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
Cipher cipher = getChiper(psw, mode);
byte[] converted = new byte[cipher.getOutputSize(input.length)];
int conv_len = cipher.update(input, 0, input.length, converted, 0);
conv_len += cipher.doFinal(converted, conv_len);
byte[] result = new byte[conv_len];
for (int i = 0; i < result.length; i++)
result[i] = converted[i];
return result;
} catch (Exception e) {
// e.printStackTrace();
return null;
}
}
/**
* Insert a <code>byte[]</code> into another <code>byte[]</code> using the
* {@link #sequenceSteps}
*
* @param input
* the <code>byte[]</code> input
* @param arrayToInsert
* the <code>byte[]</code> to insert
* @return a new <code>byte[]</code> containing the 2 merged array
*/
private byte[] magicInsert(byte[] input, byte[] arrayToInsert) {
byte[] result = new byte[input.length];
for (int i = 0; i < result.length; i++)
result[i] = input[i];
for (int i = 0; i < PSW_LENGHT; i++) {
result = insertbyte(result, arrayToInsert[i], sequenceSteps[i]);
}
return result;
}
/**
* Insert a <code>byte</code> into an <code>byte[]</code> at a specific
* position.
*
* @param input
* the <code>byte[]</code> used as input
* @param b
* the <code>byte</code> to insert
* @param pos
* the position where insert the <code>byte</code>
* @return a new array with the inserted <code>byte</code>
*/
private byte[] insertbyte(byte[] input, byte b, int pos) {
byte[] result = new byte[input.length + 1];
if (pos < 0)
pos = 0;
if (pos > input.length)
pos = input.length;
for (int i = 0; i < input.length; i++) {
if (i >= pos) {
result[i + 1] = input[i];
} else {
result[i] = input[i];
}
}
result[pos] = b;
return result;
}
/**
* Extract a <code>byte[]</code> from another <code>byte[]</code> using the
* {@link #sequenceSteps}
*
* @param input
* the <code>byte[]</code> input
* @return a new <code>byte[2][]</code> <br>
* at position<code>[0][]</code> the <code>byte[]</code> containing
* the original array<br>
* at position<code>[1][]</code> the <code>byte[]</code> containing
* the previously inserted array
*/
private byte[][] magicExtract(byte[] input) {
byte[][] result = new byte[2][];
byte[] array = new byte[input.length];
for (int i = 0; i < array.length; i++)
array[i] = input[i];
byte[] psw = new byte[PSW_LENGHT];
for (int i = PSW_LENGHT - 1; i >= 0; i--) {
int pos = sequenceSteps[i];
psw[i] = array[pos];
array = removebyte(array, pos);
}
result[0] = array;
result[1] = psw;
return result;
}
/**
* Remove a <code>byte</code> from an <code>byte[]</code> at a specific
* position.
*
* @param input
* the <code>byte[]</code> used as input
* @param pos
* the position of the <code>byte</code> to remove
* @return a new array with the removed <code>byte</code>
*/
private byte[] removebyte(byte[] array, int pos) {
byte[] result = new byte[array.length - 1];
if (pos < 0)
pos = 0;
for (int i = 0; i < result.length; i++) {
if (i >= pos) {
result[i] = array[i + 1];
} else {
result[i] = array[i];
}
}
return result;
}
/**
* Generate a random password of 8 bytes
*
* @return a a random password of 8 bytes
*/
private static byte[] genPassword() {
byte[] pswB = new byte[PSW_LENGHT];
new Random().nextBytes(pswB);
return pswB;
}
/**
* Convert a string of hexadecimal values in a <code>byte[]</code>.
*
* @param s
* the String containing the hexadecimal values
* @return the converted <code>byte[]</code>
*/
private static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
/**
* Convert a <code>byte[]</code> in a string of hexadecimal values.
*
* @param data
* the <code>byte[]</code> that have to be converted
* @return a String of hexadecimal values representing the input
*/
private static String byteArrayToHexString(byte[] data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; i++) {
sb.append(String.format("%02X", data[i]));
}
return sb.toString();
}
}