import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
class CryptAES {
static final String cipher_type = "AES/CBC/PKCS5Padding";
public static void main(String[] args) {
String key = args[0];
String iv = args[1];
String data = args[2];
byte[] enc = encode(key, iv, data.getBytes());
byte[] dec = decode(key, iv, enc);
for (int i = 0; i < enc.length; i++) {
System.out.printf("%02x", enc[i]);
}
System.out.println();
System.out.println(new String(dec));
}
public static byte[] encode(String skey, String iv, byte[] data) {
return process(Cipher.ENCRYPT_MODE, skey, iv, data);
}
public static byte[] decode(String skey, String iv, byte[] data) {
return process(Cipher.DECRYPT_MODE, skey, iv, data);
}
private static byte[] process(int mode, String skey, String iv, byte[] data) {
SecretKeySpec key = new SecretKeySpec(skey.getBytes(), "AES");
AlgorithmParameterSpec param = new IvParameterSpec(iv.getBytes());
try {
Cipher cipher = Cipher.getInstance(cipher_type);
cipher.init(mode, key, param);
return cipher.doFinal(data);
} catch (Exception e) {
System.err.println(e.getMessage());
throw new RuntimeException(e);
}
}
}
exception in thread main java.lang.arrayindexoutofboundsexception index out of bounds for length 0
exception in thread main java.lang.arrayindexoutofboundsexception index out of bounds for length 0
As #rzwitserloot pointed out, you need to check if there have been arguments passed into the application in the command line.
You should always be checking for null/empty/invalid values when a user can give input. A simple example on how to prevent the original error and throw a more meaningful error is below:
public static void main(String[] args) {
if (null == args || args.length != 3) {
throw new IOException("Please add arguments key, iv and data");
}
String key = args[0];
String iv = args[1];
String data = args[2];
...
}
You may want to be able to not have any inputs, or only input 1/2 out of the 3 arguments. For that you need to check if the argument value exists and then access it.
I am trying to encrypt a json string using the below code:
public static final Charset CHARSET = StandardCharsets.UTF_8;
public static Cipher getDefaultCipherInstance(int mode)
throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException {
byte[] key = Base64.getDecoder().decode("encryptionKey".getBytes(CHARSET));
IvParameterSpec iv = new IvParameterSpec("RandomVector".getBytes(CHARSET));
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(mode, skeySpec, iv);
return cipher;
}
public static String encryptText(String plainText) {
try {
Cipher cipher = getDefaultCipherInstance(Cipher.ENCRYPT_MODE);
byte[] cipherText = cipher.doFinal(plainText.getBytes(CHARSET));
return new String(Base64.getEncoder().encode(cipherText));
} catch (Exception ex) {
LOG.error("Problem encryptingText",ex);
return null;
}
}
public static String decryptText(String cipherText) {
try {
Cipher cipher = getDefaultCipherInstance(Cipher.DECRYPT_MODE);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(decrypted);
} catch (Exception ex) {
LOG.debug("Problem during decrypt text: " + cipherText, ex);
return null;
}
}
It works fine most of the times but sometimes I see weird characters in the decrypted string like "\u001A=`�["Q�\u001D)��ۉ�d":\ , this is corrupting the json and we are not able to deserialize json to object.
Any idea what could be the problem here?
Update::
I added the following code to test encryption/decryption in a concurrent(multi-threaded) environment:
public class EncryptionTest {
#Test
public void test() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(25);
String text = "Hi there Ithanks for cimngng";
for(int i = 0; i < 5; i++) {
System.out.println("Iteration: " + i);
executorService.submit(new EncryptionRunnable(text));
}
Thread.currentThread().join();
}
static class EncryptionRunnable implements Runnable {
private String text;
public EncryptionRunnable(String text) {
this.text = text;
}
#Override
public void run() {
int i = 0;
while(i < 10) {
String encrypted = encryptText(text);
String prefix = Thread.currentThread().getName() + "::" + i + ":: ";
System.out.println(prefix + "encrypted:: " + encrypted);
try {
System.out.println(prefix + "decrypted:: " + decryptText(encrypted));
} catch (Exception e) {
System.out.println(prefix + "decrypted:: ");
e.printStackTrace();
}
i++;
}
}
}
}
I see that all of the outputs were correct but for one of the output, it produced strange characters like this:
pool-1-thread-5::0:: decrypted:: ȼ����S}�q��j� for cimngng
Even the encrypted string is same for every encryption. Can anybody help now? Note: I am using the same cipher instance for encryption and same for decryption.
Here is a snapshot of the output of the above code.
I want to encode my password using an encryption key. but I got a null value, when printing the encoded password. I have attached my code below:
public class FirstJava {
private static final Long ENCRYPTION_KEY = 29190210908917L;
public static String encrypt(String strToEncrypt, byte[] key) {
if (strToEncrypt == null)
return strToEncrypt;
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
final SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes()));
} catch (Exception exception) {
System.out.println("ERROR");
}
return null;
}
public static void main(String[] args) {
String password = "12345678";
byte[] arr = String.valueOf(ENCRYPTION_KEY).getBytes();
String passwordEnc = encrypt(password,arr);
System.out.println("passwordEnc============= "+passwordEnc);
}
}
AES only supports key sizes of 16, 24 or 32 bytes. Your key length is 14, add 2 more digits to your key and it will work.
private static final Long ENCRYPTION_KEY = 2919021090891712L; //16 bytes
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 two instances of my service that does the encryption and decryption running in cloud. The decryption fails sometimes with 'decryption failed' error. I guess this is because each instance has its own Aead instance. How can i solve this issue?
public class Utils {
private static final Logger log = LoggerFactory.getLogger(Utils.class);
private Aead aead;
private static Utils utils;
private Utils() {
try {
AeadConfig.register();
KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
aead = AeadFactory.getPrimitive(keysetHandle);
} catch (GeneralSecurityException e) {
log.error(String.format("Error occured: %s",e.getMessage())).log();
}
}
public static Utils getInstance() {
if(null == utils) {
utils = new Utils();
}
return utils;
}
public String encrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] plainText = text.getBytes("ISO-8859-1");
byte[] additionalData = null;
byte[] cipherText = aead.encrypt(plainText,additionalData);
String output = Base64.getEncoder().encodeToString(cipherText);
return output;
}
public String decrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] cipherText = Base64.getDecoder().decode(text);
byte[] additionalData = null;
byte[] decipheredData = aead.decrypt(cipherText,additionalData);
String output = new String(decipheredData,"ISO-8859-1");
return output;
}
#Test
public void encrypt() throws IOException, GeneralSecurityException {
String encryptedText = cryptographicUtils.encrypt("Hello World");
assertThat(encryptedText, Matchers.notNullValue());
}
#Test
public void decrypt() throws IOException, GeneralSecurityException {
String encryptedText = cryptographicUtils.encrypt("Hello 123456");
String decrypedText = cryptographicUtils.decrypt(encryptedText);
assertThat(decrypedText, Matchers.is("Hello 123456"));
}
I am getting consistent result if only one instance is running...
I will have to use the same keyset to encrypt and decrypt. I am able to resolve the issue by storing the keyset in a physical location and use it to create Aead instance. With this change all instances of my service able to decrypt string successfully
Looks like a thread safety issue. Try making the getInstance synchronized. Also, protect access to private Aead aead
Multiple threads can be altering the state of aead if you're not careful.
Consider a queue to do your work, or synchronize access to what is interacting with aead.