I am trying to convert a Java encryption and decryption method to PHP.
It is a AES encryption/decryption method with keystore.jks file. I am finding it difficult to achieve this.
I have tried converting using mcrypt_encrypt method in PHP. But couldn't find a way to do it with keystore.jks file.
It will be great if you guys can comment what does each important line do.
function my_aes_encrypt($key, $data) {
if(16 !== strlen($key)) $key = hash('MD5', $key, true);
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB, str_repeat("\0", 16)));
}
function my_aes_decrypt($str, $key){
$str = base64_decode($str);
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
$block = mcrypt_get_block_size('rijndael_128', 'ecb');
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len-1]);
return substr($str, 0, strlen($str) - $pad);
}
public class MainClass {
public static void main(String[] args) {
try {
String ALIAS = "alias";
String KEYSTORE_PASSWORD = "key_pass";
String KEY_PASSWORD = "k_pass";
KeyStore keyStore = KeyStore.getInstance("JCEKS");
FileInputStream stream = new FileInputStream("path/to/keyFile.jks");
keyStore.load(stream, KEYSTORE_PASSWORD.toCharArray());
Key key = keyStore.getKey(ALIAS, KEY_PASSWORD.toCharArray());
String data = "text to encrypt";
//Encrypt Data
String encryptedData = encryptWithAESKey(data, key.getEncoded());
System.out.println("Encrypted Data : " + encryptedData);
//Decrypt Data
System.out.println("Decrypted Data : " +decryptWithAESKey(encryptedData, key.getEncoded()));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String encryptWithAESKey(String data, byte[] key) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
SecretKey secKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] newData = cipher.doFinal(data.getBytes());
return Base64.encodeBase64String(newData);
}
public static String decryptWithAESKey(String inputData, byte[] key) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("AES");
SecretKey secKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] newData = cipher.doFinal(Base64.decodeBase64(inputData.getBytes()));
return new String(newData);
}
}
php code does nothing - this is only encryption and decryption procedures;
java code encrypts data with encryptWithAESKey() and then decrypt it with decryptWithAESKey(). Key is loaded from file "path/to/keyFile.jks" (fake path, you can change it) Do you have this keys file?
Look at my encoder class:
<?php
class AES {
const M_CBC = 'cbc';
const M_CFB = 'cfb';
const M_ECB = 'ecb';
const M_NOFB = 'nofb';
const M_OFB = 'ofb';
const M_STREAM = 'stream';
protected $key;
protected $cipher;
protected $data;
protected $mode;
protected $IV;
/**
*
* #param type $data
* #param type $key
* #param type $blockSize
* #param type $mode
*/
function __construct($data = null, $key = null, $blockSize = null, $mode = null) {
$this->setData($data);
$this->setKey($key);
$this->setBlockSize($blockSize);
$this->setMode($mode);
$this->setIV("");
}
/**
*
* #param type $data
*/
public function setData($data) {
$this->data = $data;
}
/**
*
* #param type $key
*/
public function setKey($key) {
$this->key = $key;
}
/**
*
* #param type $blockSize
*/
public function setBlockSize($blockSize) {
switch ($blockSize) {
case 128:
$this->cipher = MCRYPT_RIJNDAEL_128;
break;
case 192:
$this->cipher = MCRYPT_RIJNDAEL_192;
break;
case 256:
$this->cipher = MCRYPT_RIJNDAEL_256;
break;
}
}
/**
*
* #param type $mode
*/
public function setMode($mode) {
switch ($mode) {
case AES::M_CBC:
$this->mode = MCRYPT_MODE_CBC;
break;
case AES::M_CFB:
$this->mode = MCRYPT_MODE_CFB;
break;
case AES::M_ECB:
$this->mode = MCRYPT_MODE_ECB;
break;
case AES::M_NOFB:
$this->mode = MCRYPT_MODE_NOFB;
break;
case AES::M_OFB:
$this->mode = MCRYPT_MODE_OFB;
break;
case AES::M_STREAM:
$this->mode = MCRYPT_MODE_STREAM;
break;
default:
$this->mode = MCRYPT_MODE_ECB;
break;
}
}
private function pkcs5_pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
/**
*
* #return boolean
*/
public function validateParams() {
if ($this->data != null && $this->key != null && $this->cipher != null) {
if(strlen($this->key) > 16) $this->setKey(substr($this->key, 0, 16));
if(strlen($this->key) < 16){ $n = 1 + intval(16 / strlen($this->key)); $this->setKey(substr(str_repeat($this->key, $n), 0, 16));}
$size = mcrypt_get_block_size($this->cipher, $this->mode);
$this->data = $this->pkcs5_pad($this->data, $size);
return true;
} else {
return FALSE;
}
}
public function setIV($IV) {
$this->IV = $IV;
}
protected function getIV() {
if ($this->IV == "") {
$this->IV = mcrypt_create_iv(mcrypt_get_iv_size($this->cipher, $this->mode), MCRYPT_RAND);
}
return $this->IV;
}
/**
* #return type
* #throws Exception
*/
public function encrypt() {
if ($this->validateParams()) {
return trim(base64_encode(
mcrypt_encrypt(
$this->cipher, $this->key, $this->data, $this->mode, $this->getIV())));
} else {
throw new Exception('Invlid params!');
}
}
/**
*
* #return type
* #throws Exception
*/
public function decrypt() {
if ($this->validateParams()) {
return trim(mcrypt_decrypt(
$this->cipher, $this->key, base64_decode($this->data), $this->mode, $this->getIV()));
} else {
throw new Exception('Invlid params!');
}
}
}
and usage example:
$crypt = new AES('{"username":"Tiger","amount":20,"source":1,"ttime":1517405737}', "49c50ba2129b7730", 128);
$encdata = $crypt->encrypt();
$crypt = new AES($encdata, "49c50ba2129b7730", 128);
$data = $crypt->decrypt();
Related
I'm working on an Android application that needs to crypt (and then to decrypt) file on the file system. I wrote an android test to test the code that I found on the web and I adapted for my needed. I try with to crypt a simple text and then try to decrypt it. The problem is when I try to decrypt it, some strange character appears at the beginning of the content that I want to crypt/decrypt. For example, I try to crypt/decrypt a string like this:
Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
And I received
X��YK�P���$BProgramming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
The test code is
#Test
public void test() throws IOException, GeneralSecurityException {
String input = "Concentration - Programming Music 0100 (Part 4)";
for (int i=0;i<10;i++) {
input+=input;
}
String password = EncryptSystem.encrypt(new ByteArrayInputStream(input.getBytes(Charset.forName("UTF-8"))), new File(this.context.getFilesDir(), "test.txt"));
InputStream inputStream = EncryptSystem.decrypt(password, new File(this.context.getFilesDir(), "test.txt"));
//creating an InputStreamReader object
InputStreamReader isReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
//Creating a BufferedReader object
BufferedReader reader = new BufferedReader(isReader);
StringBuilder sb = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
sb.append(str);
}
System.out.println(sb.toString());
Assert.assertEquals(input, sb.toString());
}
The class code is:
import android.os.Build;
import android.os.Process;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.*;
public class EncryptSystem {
public static class SecretKeys {
private SecretKey confidentialityKey;
private byte[] iv;
/**
* An aes key derived from a base64 encoded key. This does not generate the
* key. It's not random or a PBE key.
*
* #param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
* #return an AES and HMAC key set suitable for other functions.
*/
public static SecretKeys of(String keysStr) throws InvalidKeyException {
String[] keysArr = keysStr.split(":");
if (keysArr.length != 2) {
throw new IllegalArgumentException("Cannot parse aesKey:iv");
} else {
byte[] confidentialityKey = Base64.decode(keysArr[0], BASE64_FLAGS);
if (confidentialityKey.length != AES_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
}
byte[] iv = Base64.decode(keysArr[1], BASE64_FLAGS);
/* if (iv.length != HMAC_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
}*/
return new SecretKeys(
new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
iv);
}
}
public SecretKeys(SecretKey confidentialityKeyIn, byte[] i) {
setConfidentialityKey(confidentialityKeyIn);
iv = new byte[i.length];
System.arraycopy(i, 0, iv, 0, i.length);
}
public SecretKey getConfidentialityKey() {
return confidentialityKey;
}
public void setConfidentialityKey(SecretKey confidentialityKey) {
this.confidentialityKey = confidentialityKey;
}
#Override
public String toString() {
return Base64.encodeToString(getConfidentialityKey().getEncoded(), BASE64_FLAGS)
+ ":" + Base64.encodeToString(this.iv, BASE64_FLAGS);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SecretKeys that = (SecretKeys) o;
return confidentialityKey.equals(that.confidentialityKey) &&
Arrays.equals(iv, that.iv);
}
#Override
public int hashCode() {
int result = Objects.hash(confidentialityKey);
result = 31 * result + Arrays.hashCode(iv);
return result;
}
public byte[] getIv() {
return this.iv;
}
}
// If the PRNG fix would not succeed for some reason, we normally will throw an exception.
// If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
private static final boolean ALLOW_BROKEN_PRNG = false;
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String CIPHER = "AES";
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 16;
private static final int PBE_ITERATION_COUNT = 10000;
private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
//Made BASE_64_FLAGS public as it's useful to know for compatibility.
public static final int BASE64_FLAGS = Base64.NO_WRAP;
//default for testing
static final AtomicBoolean prngFixed = new AtomicBoolean(false);
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int HMAC_KEY_LENGTH_BITS = 256;
public static SecretKeys generateKey() throws GeneralSecurityException {
fixPrng();
KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
// No need to provide a SecureRandom or set a seed since that will
// happen automatically.
keyGen.init(AES_KEY_LENGTH_BITS);
SecretKey confidentialityKey = keyGen.generateKey();
return new SecretKeys(confidentialityKey, generateIv());
}
private static void fixPrng() {
if (!prngFixed.get()) {
synchronized (PrngFixes.class) {
if (!prngFixed.get()) {
PrngFixes.apply();
prngFixed.set(true);
}
}
}
}
private static byte[] randomBytes(int length) throws GeneralSecurityException {
fixPrng();
SecureRandom random = new SecureRandom();
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static byte[] generateIv() throws GeneralSecurityException {
return randomBytes(IV_LENGTH_BYTES);
}
private static String keyString(SecretKeys keys) {
return keys.toString();
}
public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
fixPrng();
//Get enough random bytes for both the AES key and the HMAC key:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBE_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
// Split the random bytes into two parts:
byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS / 8);
byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS / 8, AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8);
//Generate the AES key
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
return new SecretKeys(confidentialityKey, generateIv());
}
private static byte[] copyOfRange(byte[] from, int start, int end) {
int length = end - start;
byte[] result = new byte[length];
System.arraycopy(from, start, result, 0, length);
return result;
}
public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS));
}
public static String encrypt(InputStream inputStream, File fileToWrite)
throws GeneralSecurityException {
SecretKeys secretKeys = generateKey();
return encrypt(inputStream, secretKeys, fileToWrite);
}
public static InputStream decrypt(String secretKey, File fileToRead) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, FileNotFoundException {
SecretKeys secretKeys = SecretKeys.of(secretKey);
Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
new IvParameterSpec(secretKeys.getIv()));
return new CipherInputStream(new FileInputStream(fileToRead), aesCipherForDecryption);
}
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
byte[] iv = generateIv();
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
/*
* Now we get back the IV that will actually be used. Some Android
* versions do funny stuff w/ the IV, so this is to work around bugs:
*/
/*iv = aesCipherForEncryption.getIV();
//byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
return new CipherTextIvMac(byteCipherText, iv, integrityMac);*/
return secretKeys.toString();
}
private static boolean saveFile(InputStream inputStream, Cipher aesCipherForEncryption, File fileToWrite) {
try {
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
/*long fileSize = body.contentLength();*/
long fileSizeDownloaded = 0;
outputStream = new CipherOutputStream(new FileOutputStream(fileToWrite), aesCipherForEncryption);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
}
outputStream.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
public static final class PrngFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
/**
* Hidden constructor to prevent instantiation.
*/
private PrngFixes() {
}
/**
* Applies all fixes.
*
* #throws SecurityException if a fix is needed but could not be
* applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
* the fix is not needed.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class
.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException("Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "Failed to seed OpenSSL PRNG", e);
} else {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
}
/**
* Installs a Linux PRNG-backed {#code SecureRandom} implementation as
* the default. Does nothing if the implementation is already the
* default or if there is not need to install the implementation.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void installLinuxPRNGSecureRandom() throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
// Insert and check the provider atomically.
// The official Android Java libraries use synchronized methods for
// insertProviderAt, etc., so synchronizing on the class should
// make things more stable, and prevent race conditions with other
// versions of this code.
synchronized (java.security.Security.class) {
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
Security.insertProviderAt(new PrngFixes.LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
return;
} else {
throw new SecurityException("new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
}
SecureRandom rng2 = null;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "SHA1PRNG not available", e);
return;
} else {
new SecurityException("SHA1PRNG not available", e);
}
}
if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
return;
} else {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
}
}
}
}
/**
* {#code Provider} of {#code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need
// to prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", PrngFixes.LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {#link SecureRandomSpi} which passes all requests to the Linux PRNG (
* {#code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
* seed are passed through to the Linux PRNG (/dev/urandom).
* Instances of this class seed themselves by mixing in the current
* time, PID, UID, build fingerprint, and hardware serial number
* (where available) into Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed
* because each instance needs to seed itself if the client does not
* explicitly seed it.
*/
private boolean mSeeded;
#Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not
// writable Log and ignore.
Log.w(PrngFixes.class.getSimpleName(), "Failed to mix seed into "
+ URANDOM_FILE);
} finally {
mSeeded = true;
}
}
#Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
}
}
#Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream
// between DataInputStream and FileInputStream if you need
// higher PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open " + URANDOM_FILE
+ " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* #return serial number or {#code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because of Build.SERIAL is only
// available since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}
}
Any idea about what I'm in wrong? Thank you in advance
Finally, I solve by myself. I post the solution just to help anybody that in the future will look for a similar situation. I mistake to retrieve the iv array in the encrypt method, I was generating another iv vector instead of using the one contained in secretKeys.
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(secretKeys.getIv()));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
return secretKeys.toString();
}
I have a task where I have to brute force a .enc file encrypted with AES-192 in CBC mode.
So the first thing I've done is trying an offline dictionary attack using Java and the Crypto library, the problem is that an average word in the dictionary is 8 bit long so the password must have been salted in some way.
The problem is that the program that I wrote keep outputting different password at every execution and the output is just unreadable text.
here is the main:
public class main {
public static void main(String[] args) {
bruteForceFile();
}
/*
* Tries to brute force the file reading all the possible keys from a dictionary
* */
private static void bruteForceFile() {
System.out.println("--------------------------------------------------------");
System.out.println("Starting brute force/dictionary attack:");
try {
byte[] file = cipherText.cipherText.getInstance().getFileArray();
bruteForceWrapper enc = new AES(OperatingMode.CBC).bruteForceFile(file);
System.out.println("decription succeded, key : " + enc.key + " elapsed time: " + enc.elapsedSeconds +"s");
System.out.println("--------------------------------------------------------");
System.out.println("Decripted message:\n");
System.out.println(new String(enc.data));
}catch(Exception e) {
e.printStackTrace();
}
}
}
and here is the AES class:
/**
*Advanced Encryption Standard as specified by NIST in FIPS 197. Also known as the Rijndael
*algorithm by Joan Daemen and Vincent Rijmen,
*AES is a 128-bit block cipher supporting keys of 128, 192, and 256 bits.
*/
public class AES extends cipher.AbstractCipher {
public AES(OperatingMode opm) {
super(opm);
super.enablePadding();
}
#Override
protected String getMode() {
if(opm == OperatingMode.CBC) {
if(padding)
return "AES/CBC/PKCS5Padding";
return "AES/CBC/NoPadding";
}
else {
if(padding)
return "AES/ECB/PKCS5Padding";
return "AES/ECB/NoPadding";
}
}
#Override
public encriptionWrapper encript(byte[] plainText,AbstractCipherKey key) {
return null;
}
#Override
public encriptionWrapper decript(byte[]cipherText,AbstractCipherKey key) {
StopWatch timer = new StopWatch();
if(super.print) {
System.out.println("------------------------------------------------------------");
System.out.println("Starting " + this.toString() + " decryption" + " in "
+ opm.toString() + " mode."+ " (" + this.getMode() + ")");
}
try {
Cipher dcipher = Cipher.getInstance("AES");
AESCipherKey aes_key = (AESCipherKey)key;
byte[] b_key = aes_key.getPassword().getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
b_key = sha.digest(b_key);
b_key = Arrays.copyOf(b_key, 16);
SecretKeySpec secretKeySpec = new SecretKeySpec(b_key, "AES");
dcipher.init(Cipher.DECRYPT_MODE, secretKeySpec);// decode with base64 to get bytes
byte[] utf8 = dcipher.doFinal(cipherText);
// create new string based on the specified charset
if(super.print) {
System.out.println("Encryption ended. Elapsed Time: " + timer.getSeconds() + "s");
System.out.println("encrypted message length: " + utf8.length);
System.out.println("------------------------------------------------------------");
}
return new encriptionWrapper(utf8,timer.getSeconds());
} catch (Exception e) {
/*if(e.getClass() == BadPaddingException.class) {
System.out.println("attempt failed....");
}*/
if(super.print) {
System.out.println(this.toString() + " decryption failed. \n");
System.out.println("decryption ended. Elapsed Time: " + timer.getSeconds() + "s");
System.out.println("------------------------------------------------------------\n");
}
return null;
}
}
/*
* Try to brute force an encrypted file with AES
*/
public bruteForceWrapper bruteForceFile(byte[] encrypted) {
StopWatch watch = new StopWatch();
Dictionary dic = new Dictionary();
bruteForceWrapper wrapper;
super.print = false;
System.out.print("Decrypting...");
while(1 == 1) {
if(dic.getProvidedWords().size()%10 == 0) {
System.out.print(".");
}
encriptionWrapper enc = decript(encrypted,new AESCipherKey(dic.getWord()));
if(enc != null) {
wrapper = new bruteForceWrapper(enc.data, watch.getSeconds());
break;
}
}
super.print = true;
wrapper.tried_keys = dic.getProvidedWords();
wrapper.key = dic.getProvidedWords().get(dic.getProvidedWords().size() - 1);
return wrapper;
}
#Override
public String toString() {
return "AES";
}
}
Finally here is the AESCipherKey class:
public class AESCipherKey extends AbstractCipherKey{
private String SHA_TEC = "SHA-1";
public AESCipherKey(String key) {
super(key);
}
/**
*Return the desription of the safeness of the key(unsafe is user generated)
*/
#Override
public String getKeySafenessDescription() {
if(isKeySafe) {
return "(safe key)";
}else
return "(unsafe key)";
}
#Override
public boolean validate() {
if(super.isKeySafe)
return true;
if(super.getByteArray().length != 16) {
System.out.println("Invalid AES key: " + super.key);
return false;
}
return true;
}
#Override
public void generateSecureKey() {
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
super.safeKey = keyGen.generateKey();
super.isKeySafe = true;
} catch (NoSuchAlgorithmException e) {
System.out.println("Error generating AES safe key");
e.printStackTrace();
}
}
public String getPassword() {
return super.key;
}
}
So I think that the problem is around here:
byte[] b_key = aes_key.getPassword().getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
b_key = sha.digest(b_key);
b_key = Arrays.copyOf(b_key, 16);
SecretKeySpec secretKeySpec = new SecretKeySpec(b_key, "AES");
but I'm not able to find the error, here some output of the decryption:
--------------------------------------------------------
Starting brute force/dictionary attack:
Decrypting................decription succeded, key : enantiopathia elapsed time: 12.995s
--------------------------------------------------------
Decripted message:
"��t����O����m�V��}s1��i#a7� B<2�B֯�R�E�\!��v���k��WK�m��'hՒ���g�y�$�s�ug���
X��l=qYX�����F%�y)���>��r܅͞��i��L'FG��c6-�}���-�|�L�#�n���Ӧ���)�\�o�2|?7/ə���Lc�����-
�/���*���"sK���*[U�ɮ�����s��ec�P��z�6v�����Ov��1e����w�5����t�{s�%���|��W���'�3�^�H�Td��k1���S���l�8��Žѕ���XZ�X�Eiq��K���|�'�Wi��
E2-�k�Zm��
�͞�+tj��p�o\m���jc\���ؠ_v�F�k;���$\O��JW!�zD3cZ�#���N�T�J!^c��<��+���)[sK�=�Sf���Tm���J>�i�tc���1��`ɱs
,,uO��zt� �Ү>j�6��xe�,�z��l�$jW�����n��g��~M��^�s-����}kDr���`ݶ��4��?��hT�G�߿E�Z�w����&��'��фAz��}�-��r�W�2=����ƛ�i�!��Ⱥu�J�8_d��z���9h�]��yi�A�6D�0H�R����g#��������>rS1�e�供�F����H�E[m�����Syc��糠�)��"��b��0%�¤����
o70T&&�T�06�q�F��X`�V��u{1`&Xkx ��7�����|�v
2_�y��VL6z�xu��95�r�H'g�E�J�(\WY�T������T���kXM�bG�^kppڀ#�h�1�9�[���Ǽ�T<�/Oo�B =�iw����Ef��G�S�c<����������W�
�<�H�N����$�m�-=�;�*��].��v��n���&�V��D����_�{9��+��:����̶F0��|�1�9��p�9�* �Rs�Ͱ�Ckl5ͫ�jGB��!��m�h
/��*г-�z�H�w�)Q����p��!� B�p�H˦eOŹ��������< ��Ǹ��[����uP��q�n�T���Lj����yrЙ-$�i��X����T~�R��4�xό~]��G��e�dÖnI��&b{�=�&��Bi�y���%|���E���H�=�k�~į_�6PӬ��D|~
M ;��BK�'�p����o:8��0]������ً �&�k9��2�0�̟WtFy���t�>?GS��� W.����tG�R��$\V�'�����'�&��a����#�b�9�בȨ�yl�+J�M���rƠ�D�0H��B�w;��8\�!���.%��yc��~�9�X ;hq�)�&E�
�W��?�D�-:��,t�f柟.�-P�f�\˲�=S.�&
���X]�����Z�����������j�A(�]�����m�*U'"6��g��jw��
These are my classes used in order to encrypt and decrypt data between a php based server and an android application.
Sometimes the php decrypt class does not work:
For example when I encrypt "abc" or "zdf" or "091360532561524369510" in android, the php class could not decrypt the encrypted data from the android client side.
Can you please check these classes?
java class:
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ApiCrypter
{
private String iv= "0123456789012345";
private String secretkey= "9876543210987654";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
public ApiCrypter()
{
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(secretkey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception
{
if(text == null || text.length() == 0) {
throw new Exception("Empty string");
}
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(text.getBytes("UTF-8"));
}
catch (Exception e) {
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception
{
if(code == null || code.length() == 0) {
throw new Exception("Empty string");
}
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
}
catch (Exception e) {
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data)
{
if (data==null) {
return null;
}
int len = data.length;
String str = "";
for (int i=0; i<len; i++) {
if ((data[i]&0xFF)<16) {
str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
}
else {
str = str + java.lang.Integer.toHexString(data[i]&0xFF);
}
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
}
else if (str.length() < 2) {
return null;
}
else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
}
PHP class :
<?php
class ApiCrypter
{
private $iv = '0123456789012345';
private $key = '9876543210987654';
public function __construct()
{
}
public function encrypt($str)
{
$str = $this->pkcs5_pad($str);
$iv = $this->iv;
$td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv);
mcrypt_generic_init($td, $this->key, $iv);
$encrypted = mcrypt_generic($td, $str);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return bin2hex($encrypted);
}
public function decrypt($code)
{
$code = $this->hex2bin($code);
$iv = $this->iv;
$td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv);
mcrypt_generic_init($td, $this->key, $iv);
$decrypted = mdecrypt_generic($td, $code);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$ut = utf8_encode(trim($decrypted));
return $this->pkcs5_unpad($ut);
}
protected function hex2bin($hexdata)
{
$bindata = '';
for ($i = 0; $i < strlen($hexdata); $i += 2) {
$bindata .= chr(hexdec(substr($hexdata, $i, 2)));
}
return $bindata;
}
protected function pkcs5_pad ($text)
{
$blocksize = 16;
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
protected function pkcs5_unpad($text)
{
$pad = ord($text{strlen($text)-1});
if ($pad > strlen($text))
{
return false;
}
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad)
{
return false;
}
return substr($text, 0, -1 * $pad);
}
}
?>
A couple of things:
Your IVs should be distinct per message and unpredictable; never hard-coded.
Don't use mcrypt in PHP.
This is the easiest way to solve your problem:
Get libsodium in both languages. If you upgrade your PHP to 7.2 or newer, you should get the Sodium extension automatically (unless your OS vendor is doing something wrong).
Use crypto_secretbox() (or the equivalent API in your language) to encrypt, crypto_secretbox_open() to decrypt.
This is simpler than learning the proper way to fuss about with CBC mode, Initialization Vectors, padding schemes, RNGs, and ciphertext integrity.
i am using shared preferences to store the user name and password in order to achieve onetime user authentication. I am encrypting and storing the data in shared pref file. Again i am decrypting them and validating the values every time the user open the application. It is working fine until the App is running on background.
If the user close the app from background i am again getting the login screen asking to input the user credentials.
Below is the error:
W/System.err: java.security.InvalidKeyException: unknown key type passed to RSA
W/System.err: at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineInit(CipherSpi.java:275)
W/System.err: at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineInit(CipherSpi.java:379)
W/System.err: at javax.crypto.Cipher.init(Cipher.java:661)
W/System.err: at javax.crypto.Cipher.init(Cipher.java:621)
Below is my encryption Algorithm. I am calling the genereteKey() method before comitting the data in to shared pref file and encrypting the data then commiting the data.
private final static String RSA = "RSA";
public static PublicKey uk;
public static PrivateKey rk;
public static void generateKey() throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance(RSA);
gen.initialize(512, new SecureRandom());
KeyPair keyPair = gen.generateKeyPair();
uk = keyPair.getPublic();
rk = keyPair.getPrivate();
}
private static byte[] encrypt(String text, PublicKey pubRSA) throws Exception {
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubRSA);
return cipher.doFinal(text.getBytes());
}
public final static String encrypt(String text) {
try {
return byte2hex(encrypt(text, uk));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public final static String decrypt(String data) {
try {
return new String(decrypt(hex2byte(data.getBytes())));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static byte[] decrypt(byte[] src) throws Exception {
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, rk);
return cipher.doFinal(src);
}
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xFF);
if (stmp.length() == 1) hs += ("0" + stmp);
else
hs += stmp;
}
return hs.toUpperCase();
}
public static byte[] hex2byte(byte[] b) {
if ((b.length % 2) != 0) throw new IllegalArgumentException("hello");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += 2) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
This is actually exactly what I am looking for but in Java:
Encrypted cookies in Chrome
The mentioned "Windows Data Protection API (DPAPI)" I have found here for Java:
http://jdpapi.sourceforge.net/
but its "outdated" or only for 32bit platform.
SQLite connection and result is of course working that's what I got atm:
public void getDecryptedValue() {
try {
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM cookies";
ResultSet rs = stmt.executeQuery(sql);
int cookieCount = 1;
while (rs.next()) {
log.debug("####### Cookie " + cookieCount + " ############");
String host_key = rs.getString("host_key");
log.debug(host_key);
byte[] encrypted_value = rs.getBytes("encrypted_value");
//this ofc outputs "nonsense". here the decryption is needed.
log.debug(encrypted_value.toString());
cookieCount++;
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
I searched now for 2 days found nothing helpful my next approach would be to
decrypt/encrypt triple DES encryption with (Windows username? and password? what is used as seed if you have no password?) but I really don't know how I should do that with the BLOB I get from the SQLite DB or even start.
Edit: I am digging deeper with that atm : http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
I spent a long time on this myself. Here are some classes you can use to decrypt cookies for Chrome on Mac, Windows, and Linux. I didn't implement inserting cookies, but I imagine you have most of everything you need with this code to add that functionality.
It requires some third party libraries (JNI Windows encryption wrappers and SQLite) so I am linking to the Maven project I am using the code in if you need to see the code in context.
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.sun.jna.platform.win32.Crypt32Util;
public class Test {
public static void main(String[] args) throws IOException {
ChromeBrowser chrome = new ChromeBrowser();
Set<Cookie> cookies = chrome.getCookiesForDomain("stackoverflow.com");
for(Cookie cookie : cookies){
System.out.println(cookie.toString());
}
}
public static abstract class Cookie {
protected String name;
protected byte[] encryptedValue;
protected Date expires;
protected String path;
protected String domain;
protected boolean secure;
protected boolean httpOnly;
protected File cookieStore;
public Cookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
this.name = name;
this.encryptedValue = encryptedValue;
this.expires = expires;
this.path = path;
this.domain = domain;
this.secure = secure;
this.httpOnly = httpOnly;
this.cookieStore = cookieStore;
}
public String getName() {
return name;
}
public byte[] getEncryptedValue() {
return encryptedValue;
}
public Date getExpires() {
return expires;
}
public String getPath() {
return path;
}
public String getDomain() {
return domain;
}
public boolean isSecure() {
return secure;
}
public boolean isHttpOnly() {
return httpOnly;
}
public File getCookieStore(){
return cookieStore;
}
public abstract boolean isDecrypted();
}
public static class DecryptedCookie extends Cookie {
private String decryptedValue;
public DecryptedCookie(String name, byte[] encryptedValue, String decryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
this.decryptedValue = decryptedValue;
}
public String getDecryptedValue(){
return decryptedValue;
}
#Override
public boolean isDecrypted() {
return true;
}
#Override
public String toString() {
return "Cookie [name=" + name + ", value=" + decryptedValue + "]";
}
}
public static class EncryptedCookie extends Cookie {
public EncryptedCookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
}
#Override
public boolean isDecrypted() {
return false;
}
#Override
public String toString() {
return "Cookie [name=" + name + " (encrypted)]";
}
}
public static class OS {
public static String getOsArchitecture() {
return System.getProperty("os.arch");
}
public static String getOperatingSystem() {
return System.getProperty("os.name");
}
public static String getOperatingSystemVersion() {
return System.getProperty("os.version");
}
public static String getIP() throws UnknownHostException {
InetAddress ip = InetAddress.getLocalHost();
return ip.getHostAddress();
}
public static String getHostname() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
public static boolean isWindows() {
return (getOperatingSystem().toLowerCase().indexOf("win") >= 0);
}
public static boolean isMac() {
return (getOperatingSystem().toLowerCase().indexOf("mac") >= 0);
}
public static boolean isLinux() {
return (getOperatingSystem().toLowerCase().indexOf("nix") >= 0 || getOperatingSystem().toLowerCase().indexOf("nux") >= 0 || getOperatingSystem().toLowerCase().indexOf("aix") > 0 );
}
public static boolean isSolaris() {
return (getOperatingSystem().toLowerCase().indexOf("sunos") >= 0);
}
}
public static abstract class Browser {
/**
* A file that should be used to make a temporary copy of the browser's cookie store
*/
protected File cookieStoreCopy = new File(".cookies.db");
/**
* Returns all cookies
*/
public Set<Cookie> getCookies() {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, null));
}
return cookies;
}
/**
* Returns cookies for a given domain
*/
public Set<Cookie> getCookiesForDomain(String domain) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, domain));
}
return cookies;
}
/**
* Returns a set of cookie store locations
* #return
*/
protected abstract Set<File> getCookieStores();
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
protected abstract Set<Cookie> processCookies(File cookieStore, String domainFilter);
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
protected abstract DecryptedCookie decrypt(EncryptedCookie encryptedCookie);
}
/**
* An implementation of Chrome cookie decryption logic for Mac, Windows, and Linux installs
*
* References:
* 1) http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
* 2) https://github.com/markushuber/ssnoob
*
* #author Ben Holland
*/
public static class ChromeBrowser extends Browser {
private String chromeKeyringPassword = null;
/**
* Returns a set of cookie store locations
* #return
*/
#Override
protected Set<File> getCookieStores() {
HashSet<File> cookieStores = new HashSet<File>();
// pre Win7
cookieStores.add(new File(System.getProperty("user.home") + "\\Application Data\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Win 7+
cookieStores.add(new File(System.getProperty("user.home") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Mac
cookieStores.add(new File(System.getProperty("user.home") + "/Library/Application Support/Google/Chrome/Default/Cookies"));
// Linux
cookieStores.add(new File(System.getProperty("user.home") + "/.config/chromium/Default/Cookies"));
return cookieStores;
}
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
#Override
protected Set<Cookie> processCookies(File cookieStore, String domainFilter) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
if(cookieStore.exists()){
Connection connection = null;
try {
cookieStoreCopy.delete();
Files.copy(cookieStore.toPath(), cookieStoreCopy.toPath());
// load the sqlite-JDBC driver using the current class loader
Class.forName("org.sqlite.JDBC");
// create a database connection
connection = DriverManager.getConnection("jdbc:sqlite:" + cookieStoreCopy.getAbsolutePath());
Statement statement = connection.createStatement();
statement.setQueryTimeout(30); // set timeout to 30 seconds
ResultSet result = null;
if(domainFilter == null || domainFilter.isEmpty()){
result = statement.executeQuery("select * from cookies");
} else {
result = statement.executeQuery("select * from cookies where host_key like \"%" + domainFilter + "%\"");
}
while (result.next()) {
String name = result.getString("name");
byte[] encryptedBytes = result.getBytes("encrypted_value");
String path = result.getString("path");
String domain = result.getString("host_key");
boolean secure = result.getBoolean("secure");
boolean httpOnly = result.getBoolean("httponly");
Date expires = result.getDate("expires_utc");
EncryptedCookie encryptedCookie = new EncryptedCookie(name,
encryptedBytes,
expires,
path,
domain,
secure,
httpOnly,
cookieStore);
DecryptedCookie decryptedCookie = decrypt(encryptedCookie);
if(decryptedCookie != null){
cookies.add(decryptedCookie);
} else {
cookies.add(encryptedCookie);
}
cookieStoreCopy.delete();
}
} catch (Exception e) {
e.printStackTrace();
// if the error message is "out of memory",
// it probably means no database file is found
} finally {
try {
if (connection != null){
connection.close();
}
} catch (SQLException e) {
// connection close failed
}
}
}
return cookies;
}
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
#Override
protected DecryptedCookie decrypt(EncryptedCookie encryptedCookie) {
byte[] decryptedBytes = null;
if(OS.isWindows()){
try {
decryptedBytes = Crypt32Util.cryptUnprotectData(encryptedCookie.getEncryptedValue());
} catch (Exception e){
decryptedBytes = null;
}
} else if(OS.isLinux()){
try {
byte[] salt = "saltysalt".getBytes();
char[] password = "peanuts".toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
} else if(OS.isMac()){
// access the decryption password from the keyring manager
if(chromeKeyringPassword == null){
try {
chromeKeyringPassword = getMacKeyringPassword("Chrome Safe Storage");
} catch (IOException e) {
decryptedBytes = null;
}
}
try {
byte[] salt = "saltysalt".getBytes();
char[] password = chromeKeyringPassword.toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1003;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
}
if(decryptedBytes == null){
return null;
} else {
return new DecryptedCookie(encryptedCookie.getName(),
encryptedCookie.getEncryptedValue(),
new String(decryptedBytes),
encryptedCookie.getExpires(),
encryptedCookie.getPath(),
encryptedCookie.getDomain(),
encryptedCookie.isSecure(),
encryptedCookie.isHttpOnly(),
encryptedCookie.getCookieStore());
}
}
/**
* Accesses the apple keyring to retrieve the Chrome decryption password
* #param application
* #return
* #throws IOException
*/
private static String getMacKeyringPassword(String application) throws IOException {
Runtime rt = Runtime.getRuntime();
String[] commands = {"security", "find-generic-password","-w", "-s", application};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String result = "";
String s = null;
while ((s = stdInput.readLine()) != null) {
result += s;
}
return result;
}
}
}