/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker 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 3 of the License, or
(at your option) any later version.
Tiny Travel Tracker 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 Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.android;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.GpsTrailerCrypt;
public class Crypt {
//TODO 3.5: This is a hack to use the same IV for every instance. The data is very short, and pretty
//much all different so this should be ok
private static final int IV_LENGTH = 16;
public static final int SECRET_KEY_ROUNDS = 2048;
private static final String SECRET_KEY_FACTORY_IMPL = "PBKDF2WithHmacSHA1";
private SecretKeySpec skeySpec;
private SecureRandom sr = new SecureRandom();
private byte [] ivData = new byte[IV_LENGTH];
private Cipher encryptCipher;
private Cipher decryptCipher;
//TODO 3.5: Review secret algorithms and such, do we really want to use AES as opposed to something else?
public Crypt(byte [] key)
{
_init(new SecretKeySpec(key, GpsTrailerCrypt.SECRET_KEY_SPEC_ALGORITHM));
}
private void _init(SecretKeySpec skeySpec)
{
this.skeySpec = skeySpec;
try {
encryptCipher = Cipher.getInstance(GpsTrailerCrypt.INTERNAL_SYMMETRIC_ENCRYPTION_ALGORITHM);
//we have to initialize so that getNumOutputBytes... methods work, so we do so with a crap iv
encryptCipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivData));
decryptCipher = Cipher.getInstance(GpsTrailerCrypt.INTERNAL_SYMMETRIC_ENCRYPTION_ALGORITHM);
//we have to initialize so that getNumOutputBytes... methods work, so we do so with a crap iv
decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(ivData));
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
/**
* Encrypts some data
*
* @param offset
* @param length
* @return size of encrypted value
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws ShortBufferException
*/
public synchronized int encryptData(byte [] output, int outputPos, byte [] input, int inputOffset, int inputLength)
{
try {
//write the iv
sr.nextBytes(ivData);
System.arraycopy(ivData, 0, output, outputPos, IV_LENGTH);
encryptCipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivData));
encryptCipher.doFinal(input, inputOffset, inputLength, output, outputPos + IV_LENGTH);
return IV_LENGTH+encryptCipher.getOutputSize(inputLength);
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
/**
* Decrypts some data
*
* @return size of encrypted value
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws ShortBufferException
*/
public synchronized int decryptData(byte [] output, byte [] input)
{
return decryptData(output, input, 0, input.length);
}
public synchronized int decryptData(byte [] output, byte [] input, int inputOffset, int inputLength)
{
try {
decryptCipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(input, inputOffset, IV_LENGTH));
int outputSize = decryptCipher.doFinal(input, inputOffset+IV_LENGTH, inputLength - IV_LENGTH, output);
return outputSize;
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
public static byte[] getRawKey(String password, byte[] salt) {
try {
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_IMPL);
//note, this takes a long time (on purpose) for an actual password. If we're using a fake one, there is no reason to loop so many rounds
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, GpsTrailerCrypt.prefs.isNoPassword ? 1 : SECRET_KEY_ROUNDS,
GpsTrailerCrypt.prefs.aesKeySize);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret.getEncoded();
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
public static String getEncryptDesc()
{
try {
return GpsTrailerCrypt.prefs.aesKeySize+" bit "+Cipher.getInstance(GpsTrailerCrypt.INTERNAL_SYMMETRIC_ENCRYPTION_ALGORITHM).
getAlgorithm();
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
public static String getSecretKeyDesc()
{
try {
return SECRET_KEY_ROUNDS+" round "+SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_IMPL).getAlgorithm();
}
catch(Exception e)
{
throw new IllegalStateException(e);
}
}
public static String getAsymmetricEncryptionDesc()
{
try {
return GpsTrailerCrypt.RSA_KEY_SIZE+" bit "+Cipher.getInstance(GpsTrailerCrypt.INTERNAL_ASYMMETRIC_ENCRYPTION_ALGORITHM).getAlgorithm();
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
public synchronized int getNumOutputBytesForEncryption(int length) {
return encryptCipher.getOutputSize(length) + IV_LENGTH;
}
public synchronized int getNumOutputBytesForDecryption(int encryptedLength) {
return decryptCipher.getOutputSize(encryptedLength - IV_LENGTH);
}
}