I've been looking at using Conceal to encrypt some files. The code snippet provided states that the input is plain text. Can it be used to encrypt file binary though? Why is it specifically plain text and not general binary?
Here's the snippet provided:
// Creates a new Crypto object with default implementations of
// a key chain as well as native library.
Crypto crypto = new Crypto(
new SharedPrefsBackedKeyChain(context),
new SystemNativeCryptoLibrary());
// Check for whether the crypto functionality is available
// This might fail if Android does not load libaries correctly.
if (!crypto.isAvailable()) {
return;
}
OutputStream fileStream = new BufferedOutputStream(
new FileOutputStream(file));
// Creates an output stream which encrypts the data as
// it is written to it and writes it out to the file.
OutputStream outputStream = crypto.getCipherOutputStream(
fileStream,
entity);
// Write plaintext to it.
outputStream.write(plainText);
outputStream.close();
From the library, the method says:
/**
* A convenience method to encrypt data if the data to be processed is small and can
* be held in memory.
* #param plainTextBytes Bytes of the plain text.
* #param entity Entity to process.
* #return cipherText.
* #throws KeyChainException
* #throws CryptoInitializationException
* #throws IOException
* #throws CryptoInitializationException Thrown if the crypto libraries could not be initialized.
* #throws KeyChainException Thrown if there is trouble managing keys.
*/
public byte[] encrypt(byte[] plainTextBytes, Entity entity)
throws KeyChainException, CryptoInitializationException, IOException {
int cipheredBytesLength = plainTextBytes.length + mCipherHelper.getCipherMetaDataLength();
FixedSizeByteArrayOutputStream outputStream = new FixedSizeByteArrayOutputStream(cipheredBytesLength);
OutputStream cipherStream = mCipherHelper.getCipherOutputStream(outputStream, entity);
cipherStream.write(plainTextBytes);
cipherStream.close();
return outputStream.getBytes();
}
You will need to convert your plain text into byte[] in order to use the library. If you can also convert the binary file to byte[] then you can use conceal for your purposes.
Try this method:
byte[] bytes = File.ReadAllBytes("C:\\Mybinaryfile");
Related
I have an implementation of 'AES' encryption and decryption with 'CBC' mode and 'PKCS5Padding' padding in Kotlin. I noticed that while decrypting cipherInputStream.read(buffer) reads only 512 bytes at a time instead of the full buffer size which is 8192 bytes. Why is that? While encrypting it uses whole buffer.
These are the constants that I am using,
private val TRANSFORMATION = "AES/CBC/PKCS5Padding"
private var SECRET_KEY_FAC_ALGORITHM = "PBKDF2WithHmacSHA1"
private val SECRET_KEY_SPEC_ALGORITHM = "AES"
private val cipher = Cipher.getInstance(TRANSFORMATION)
private val random = SecureRandom()
private val KEY_BITS_LENGTH = 256
private val IV_BYTES_LENGTH = cipher.blockSize
private val SALT_BYTES_LENGTH = KEY_BITS_LENGTH / 8
private val ITERATIONS = 10000
Decryption code
cis = CipherInputStream(input, cipher)
val buffer = ByteArray(8192)
var read = cis.read(buffer)
while (read > -1) {
fos.write(buffer, 0, read)
read = cis.read(buffer)
}
Encryption code
fos.write(iv)
fos.write(salt)
cos = CipherOutputStream(fos, cipher)
val buffer = ByteArray(8192)
var read = input.read(buffer)
while (read > -1) {
cos.write(buffer, 0, read)
read = input.read(buffer)
}
Recently I had a similar issue.
The problem was internal buffer of CipherInputStream class which is defined as follows
private byte[] ibuffer = new byte[512];
What significantly improved decryption speed was increasing this buffer size to 8192. So I've just copy pasted original CipherInputStream class to my own class and modified buffer size.
What is funny is the comment above this ibuffer field.
the size 512 bytes is somewhat randomly chosen */
Hope it helped
I just implemented the class by changing the size of the length of ibuffer. (Copy paste with the changed value only)
import java.io.IOException;
import java.io.InputStream;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NullCipher;
import javax.crypto.ShortBufferException;
public class FasterCipherInputStream extends CipherInputStream {
private static final String TAG = "FasterCipherInputStream";
private static final int BUFFER_SIZE = 20971520;
// the cipher engine to use to process stream data
private final Cipher cipher;
// the underlying input stream
private final InputStream input;
/* the buffer holding data that have been read in from the
underlying stream, but have not been processed by the cipher
engine. the size 512 bytes is somewhat randomly chosen */
private final byte[] ibuffer = new byte[BUFFER_SIZE];
// having reached the end of the underlying input stream
private boolean done = false;
/* the buffer holding data that have been processed by the cipher
engine, but have not been read out */
private byte[] obuffer;
// the offset pointing to the next "new" byte
private int ostart = 0;
// the offset pointing to the last "new" byte
private int ofinish = 0;
// stream status
private boolean closed = false;
/**
* private convenience function.
*
* Entry condition: ostart = ofinish
*
* Exit condition: ostart <= ofinish
*
* return (ofinish-ostart) (we have this many bytes for you)
* return 0 (no data now, but could have more later)
* return -1 (absolutely no more data)
*
* Note: Exceptions are only thrown after the stream is completely read.
* For AEAD ciphers a read() of any length will internally cause the
* whole stream to be read fully and verify the authentication tag before
* returning decrypted data or exceptions.
*/
private int getMoreData() throws IOException {
// Android-changed: The method was creating a new object every time update(byte[], int, int)
// or doFinal() was called resulting in the old object being GCed. With do(byte[], int) and
// update(byte[], int, int, byte[], int), we use already initialized obuffer.
if (done) return -1;
ofinish = 0;
ostart = 0;
int expectedOutputSize = cipher.getOutputSize(ibuffer.length);
if (obuffer == null || expectedOutputSize > obuffer.length) {
obuffer = new byte[expectedOutputSize];
}
int readin = input.read(ibuffer);
if (readin == -1) {
done = true;
try {
// doFinal resets the cipher and it is the final call that is made. If there isn't
// any more byte available, it returns 0. In case of any exception is raised,
// obuffer will get reset and therefore, it is equivalent to no bytes returned.
ofinish = cipher.doFinal(obuffer, 0);
} catch (IllegalBlockSizeException | BadPaddingException e) {
obuffer = null;
throw new IOException(e);
} catch (ShortBufferException e) {
obuffer = null;
throw new IllegalStateException("ShortBufferException is not expected", e);
}
} else {
// update returns number of bytes stored in obuffer.
try {
ofinish = cipher.update(ibuffer, 0, readin, obuffer, 0);
} catch (IllegalStateException e) {
obuffer = null;
throw e;
} catch (ShortBufferException e) {
// Should not reset the value of ofinish as the cipher is still not invalidated.
obuffer = null;
throw new IllegalStateException("ShortBufferException is not expected", e);
}
}
return ofinish;
}
/**
* Constructs a CipherInputStream from an InputStream and a
* Cipher.
* <br>Note: if the specified input stream or cipher is
* null, a NullPointerException may be thrown later when
* they are used.
* #param is the to-be-processed input stream
* #param c an initialized Cipher object
*/
public FasterCipherInputStream(InputStream is, Cipher c) {
super(is);
input = is;
cipher = c;
}
/**
* Constructs a CipherInputStream from an InputStream without
* specifying a Cipher. This has the effect of constructing a
* CipherInputStream using a NullCipher.
* <br>Note: if the specified input stream is null, a
* NullPointerException may be thrown later when it is used.
* #param is the to-be-processed input stream
*/
protected FasterCipherInputStream(InputStream is) {
super(is);
input = is;
cipher = new NullCipher();
}
/**
* Reads the next byte of data from this input stream. The value
* byte is returned as an <code>int</code> in the range
* <code>0</code> to <code>255</code>. If no byte is available
* because the end of the stream has been reached, the value
* <code>-1</code> is returned. This method blocks until input data
* is available, the end of the stream is detected, or an exception
* is thrown.
* <p>
*
* #return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* #exception IOException if an I/O error occurs.
* #since JCE1.2
*/
public int read() throws IOException {
if (ostart >= ofinish) {
// we loop for new data as the spec says we are blocking
int i = 0;
while (i == 0) i = getMoreData();
if (i == -1) return -1;
}
return ((int) obuffer[ostart++] & 0xff);
};
/**
* Reads up to <code>b.length</code> bytes of data from this input
* stream into an array of bytes.
* <p>
* The <code>read</code> method of <code>InputStream</code> calls
* the <code>read</code> method of three arguments with the arguments
* <code>b</code>, <code>0</code>, and <code>b.length</code>.
*
* #param b the buffer into which the data is read.
* #return the total number of bytes read into the buffer, or
* <code>-1</code> is there is no more data because the end of
* the stream has been reached.
* #exception IOException if an I/O error occurs.
* #see java.io.InputStream#read(byte[], int, int)
* #since JCE1.2
*/
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from this input stream
* into an array of bytes. This method blocks until some input is
* available. If the first argument is <code>null,</code> up to
* <code>len</code> bytes are read and discarded.
*
* #param b the buffer into which the data is read.
* #param off the start offset in the destination array
* <code>buf</code>
* #param len the maximum number of bytes read.
* #return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of
* the stream has been reached.
* #exception IOException if an I/O error occurs.
* #see java.io.InputStream#read()
* #since JCE1.2
*/
public int read(byte b[], int off, int len) throws IOException {
if (ostart >= ofinish) {
// we loop for new data as the spec says we are blocking
int i = 0;
while (i == 0) i = getMoreData();
if (i == -1) return -1;
}
if (len <= 0) {
return 0;
}
int available = ofinish - ostart;
if (len < available) available = len;
if (b != null) {
System.arraycopy(obuffer, ostart, b, off, available);
}
ostart = ostart + available;
return available;
}
/**
* Skips <code>n</code> bytes of input from the bytes that can be read
* from this input stream without blocking.
*
* <p>Fewer bytes than requested might be skipped.
* The actual number of bytes skipped is equal to <code>n</code> or
* the result of a call to
* {#link #available() available},
* whichever is smaller.
* If <code>n</code> is less than zero, no bytes are skipped.
*
* <p>The actual number of bytes skipped is returned.
*
* #param n the number of bytes to be skipped.
* #return the actual number of bytes skipped.
* #exception IOException if an I/O error occurs.
* #since JCE1.2
*/
public long skip(long n) throws IOException {
int available = ofinish - ostart;
if (n > available) {
n = available;
}
if (n < 0) {
return 0;
}
ostart += n;
return n;
}
/**
* Returns the number of bytes that can be read from this input
* stream without blocking. The <code>available</code> method of
* <code>InputStream</code> returns <code>0</code>. This method
* <B>should</B> be overridden by subclasses.
*
* #return the number of bytes that can be read from this input stream
* without blocking.
* #exception IOException if an I/O error occurs.
* #since JCE1.2
*/
public int available() throws IOException {
return (ofinish - ostart);
}
/**
* Closes this input stream and releases any system resources
* associated with the stream.
* <p>
* The <code>close</code> method of <code>CipherInputStream</code>
* calls the <code>close</code> method of its underlying input
* stream.
*
* #exception IOException if an I/O error occurs.
* #since JCE1.2
*/
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
input.close();
// Android-removed: Removed a now-inaccurate comment
if (!done) {
try {
cipher.doFinal();
}
catch (BadPaddingException | IllegalBlockSizeException ex) {
// Android-changed: Added throw if bad tag is seen. See b/31590622.
if (ex instanceof AEADBadTagException) {
throw new IOException(ex);
}
}
}
ostart = 0;
ofinish = 0;
}
/**
* Tests if this input stream supports the <code>mark</code>
* and <code>reset</code> methods, which it does not.
*
* #return <code>false</code>, since this class does not support the
* <code>mark</code> and <code>reset</code> methods.
* #see java.io.InputStream#mark(int)
* #see java.io.InputStream#reset()
* #since JCE1.2
*/
public boolean markSupported() {
return false;
}
}
It worked fine for my case while decrypting a file over 30 MB. Hope someone can find some flaws though worked really well for my case.
Edit: Sorry somehow I missed that the above answer says the same. Keeping it for others in case they just need to copy from somewhere. Thanks.
I'm using RSA encrypt text and decrypt text. The public key and the private key are generated with openssl tool.
I encountered an "java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception when decrypting data.
Here is the RSA util class:
package studio.uphie.app;
import android.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* Created by Uphie on 2016/4/11.
*/
public class RSA {
private static String RSA = "RSA";
/**
*
* #param text text to be encrypted
* #param pub_key rsa public key
* #return encrypted data in byte-array form
*/
public static byte[] encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes();
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes(), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* #param text text to be decrypted
* #param pri_key rsa private key
* #return
*/
public static byte[] decryptData(String text, String pri_key) {
try {
byte[] data = text.getBytes();
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes(),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
//"java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block" exception occurs here.
return null;
}
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
}
/**
*
* #param keyBytes
* #return
* #throws NoSuchAlgorithmException
* #throws InvalidKeySpecException
*/
public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,
InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
}
}
And the snippet that encrypts and decrypts data:
//encrypt
byte[] e = RSA.encryptData(text, PUBLIC_KEY);
String result = Base64.encodeToString(e, Base64.DEFAULT);
tv_encrypted.setText(result);
//decrypt
byte[] d = RSA.decryptData(text, PRIVATE_KEY);
String result = Base64.encodeToString(d, Base64.DEFAULT);
tv_decrypted.setText("Decrypted result:\n" + result);
I know the reason may be that the text to be decrypted is too long , but I just encrypt "abc" and then decrypt the encrypted "abc". And how to handle encrypting long text if the text to be encrypted or decrypted should be 11 bytes less than the rsa private key? How can I do to solve it? I'm new to RSA.
Thanks in advance!
You are missing some steps in your code which makes it impossible to check. However, there are a few clues to suggest a problem. Your decryptData method takes a String argument and then calls String.getBytes() to get the data which is then decrypted. However, the result of encryption is a sequence of bytes which is not the encoding of any valid String. Perhaps you meant to base64 decode the input instead of calling getBytes(). In general to perform decryption and decoding you must reverse the steps you performed during encryption and encoding. So, if the plaintext is a byte[] then the steps are:
byte [] → Encrypt → byte [] → Base64 encode → String.
then, in the decrypt direction you start with a Base64 string, you must, in order:
String → Base64 decode → byte [] → decrypt → byte []
Also, another issue which is bad practice and a source of many portability bugs is the use of defaults. You are using defaults in two places and they're both troublesome. First you are using the default no-args String.getBytes() method, and presumably matching that up with the one-arg String (byte []) constructor. This use the platform default character set, but this can differ on different platforms. Therefore always specify a character set. For most applications 'UTF-8' is an ideal choice. Secondly, you are calling Cipher.getInstance('RSA') without specifying padding. Oracle's Java and Android's Java will give you different padding and thus your code will not be portable between the platforms. Always specify the complete padding string. Here the choice is little more difficult if you need portability to older Java implementations. OAEP padding should be your first choice, so Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); is probably the right choice. See this for further discussion.
As for how to encrypt longer texts, see the answer from Henry.
Fianlly I modified my codes like that and they work well:
public static String encryptData(String text, String pub_key) {
try {
byte[] data = text.getBytes("utf-8");
PublicKey publicKey = getPublicKey(Base64.decode(pub_key.getBytes("utf-8"), Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeToString(cipher.doFinal(data),Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String decryptData(String text, String pri_key) {
try {
byte[] data =Base64.decode(text,Base64.DEFAULT);
PrivateKey privateKey = getPrivateKey(Base64.decode(pri_key.getBytes("utf-8"),Base64.DEFAULT));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(data),"utf-8");
} catch (Exception e) {
return null;
}
}
If something seems wrong still you can remind me. Thanks for James and Henry's answer.
Usually, you generate a random secret key for a symmetric cipher (like AES) and use this to encrypt your pay load.
RSA is then only used to encrypt this random key. This does not only solve the length problem but has some other advantages as well:
Symmetric cyphers are usually much faster
If the message is sent to several recievers, only the encrypted key has to be added specifically for each receiver, the main content can be the same.
In android i get always IllegalBlockSizeException, the data are encrypted in nodejs server and looks like (node.js: encrypting data that needs to be decrypted?):
var crypto = require('crypto');
console.log(crypto.getCiphers(), crypto.getHashes());
var algorithm = 'aes128'; // or any other algorithm supported by OpenSSL
var key = 'password';
var cipher = crypto.createCipher(algorithm, key);
var encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
fs.writeFile(file, encrypted, function (err) {
cb(err);
});
android code:
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
call method from file is in input stream (is):
byte [] b = new byte[2000000];
is.read(b, 0, 2000000);
byte[] decryptedData = decrypt(key,"password".getBytes());
result = new String(decryptedData, "UTF8").split("\n");
android code is inspired by : android encryption/decryption with AES where i dont use part of SecretKey with SecureRandom... which is for sure wrong, but i dont use any secure random in node.js part. The problem can be also with coding data in file.
I generaly generate a file in nodejs which is downloaded by app and stored in sdcard i'm not sure if i should be realy care about these data but will be cool have it crypted, isn't it?:-)
Thank you so much for any help or advice;-)
An IllegalBlockSizeException means that your input is not a multiple of the AES block size (16 bytes).
Your use of the decryption method looks completely wrong:
byte [] b = new byte[2000000];
is.read(b, 0, 2000000);
byte[] decryptedData = decrypt(key,"password".getBytes()); // <--- ???!
You are passing an eight byte constant value for your ciphertext. Instead, you should be passing the data you read from your input stream.
I would strongly recommend you research the correct way to read an entire input stream, because this code snippet suggests you are not handling resources correctly. You are also likely to end up with a byte array much larger than your actual data (unless your file is exactly 2000000 bytes long).
Side note: always specify the mode and padding when creating a Cipher object. For instance, if you know your JavaScript code uses CBC-mode and PKCS#7 padding, select:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
This is important, because otherwise you are relying on default values that may differ between platforms.
Question Description
I met a situation recently in which case I need to do a 256-AES cross platform encryption/decryption between iOS and Android with a pre-defined String key like this PreDefinedKey.
The AES implementation is done on iOS with this code, and all I need to do is to change the code on Android so that I can do "cross-platform" encryption/decryption.
NOTE: I'm aware of that the AES code on iOS has a severe security/memory problem, but its currently not my concern :-)
I was able to do the encryption/decryption on either Android and iOS individually. However it seemed the two AES implementations here have a trivial difference which prevented me to do the "cross-platform" encryption/decryption. For example, I put the Android encrypted String to iOS and it can't return a expected result (in this case, it returns null).
Question:
On both iOS and Android platform, I'm sure the algorithm is AES/ECB/PKCS7Padding, with 128-Rijndael algorithm for AES implementation.
Both platform should use 256-bit size key. And with a deeper look inside of the iOS AES code I found that it actually uses zeroes to pad the key to 256-bit.
Here's the zero paddings related code snippet on iOS:
// 'key' should be 32 bytes for AES256, will be null-padded otherwise
char keyPtr[kCCKeySizeAES256+1]; // room for terminator (unused)
bzero(keyPtr, sizeof(keyPtr)); // fill with zeroes (for padding)
And here's the AES parameters in that code (it uses Rijndael-128 Algorithm, 256-bit key size, NULL for Initial Vectors):
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
keyPtr, kCCKeySizeAES256,
NULL /* initialization vector (optional) */,
[self bytes], dataLength, /* input */
buffer, bufferSize, /* output */
&numBytesEncrypted);
But on Android I don't know how to do the similar stuff, so could someone point out the right way for me?
Code I'm Using
On Android platform, I use the code below to do AES implementation:
private static final String AES_SECRET = "PreDefinedKey";
/**
* Method for AES encryption
* #param raw
* #param plain
* #return
* #throws Exception
*/
private static byte[] encrypt(byte[] raw, byte[] plain) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plain);
return encrypted;
}
/**
* AES decryption
* #param encryptMsg
* #return
* #throws Exception
*/
public static String AESDecrypt(String encryptMsg)
throws Exception {
byte[] rawKey = getRawKey(AES_SECRET.getBytes());
//byte[] enc = toByte(encryptMsg);
byte[] enc = Base64.decode(encryptMsg, 0);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
/**
* Method for AES decryption
* #param raw
* #param encrypted
* #return
* #throws Exception
*/
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
//Init for 256bit AES key
kgen.init(256);
SecretKey secret = kgen.generateKey();
//Get secret raw key
byte[] raw = secret.getEncoded();
return seed;
}
In the method of getRawKey(), it uses SHA1PRNG to generate random paddings to make the AES key to 256-bit size which is different from the iOS implementation (it uses zeroes to pad the key to 256-bit).
So, how do I change this method so that I can use my pre-defined string key which is padded with zeroes to 256-bit?
Please let me know if you require more info. Thx!
Find whoever came up with this zero-padded scheme and have them fired. Then have the app reviewed.
As for your question, simply create a byte array of length 32 and copy the key bytes a the beginning, the use it to initialize SecretKeySpec. The KeyGenerator will generate a random key, and the whole 'fixed seed' idea is flawed and doesn't work on latest Android version. Here's some code:
// zeros by default
byte[] rawKey = new byte[32];
// if you don't specify the encoding you might get weird results
byte[] keyBytes = AES_SECRET.getBytes("ASCII");
System.arraycopy(keyBytes, 0, rawKey, 0, keyBytes.length);
SecretKey key = new SecretKeySpec(rawKey, "AES");
Cipher cipher = ...
// rest of your decryption code
The following code is working on all the versions of android except the latest 4.2
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Util class to perform encryption/decryption over strings. <br/>
*/
public final class UtilsEncryption
{
/** The logging TAG */
private static final String TAG = UtilsEncryption.class.getName();
/** */
private static final String KEY = "some_encryption_key";
/**
* Avoid instantiation. <br/>
*/
private UtilsEncryption()
{
}
/** The HEX characters */
private final static String HEX = "0123456789ABCDEF";
/**
* Encrypt a given string. <br/>
*
* #param the string to encrypt
* #return the encrypted string in HEX
*/
public static String encrypt( String cleartext )
{
try
{
byte[] result = process( Cipher.ENCRYPT_MODE, cleartext.getBytes() );
return toHex( result );
}
catch ( Exception e )
{
System.out.println( TAG + ":encrypt:" + e.getMessage() );
}
return null;
}
/**
* Decrypt a HEX encrypted string. <br/>
*
* #param the HEX string to decrypt
* #return the decrypted string
*/
public static String decrypt( String encrypted )
{
try
{
byte[] enc = fromHex( encrypted );
byte[] result = process( Cipher.DECRYPT_MODE, enc );
return new String( result );
}
catch ( Exception e )
{
System.out.println( TAG + ":decrypt:" + e.getMessage() );
}
return null;
}
/**
* Get the raw encryption key. <br/>
*
* #param the seed key
* #return the raw key
* #throws NoSuchAlgorithmException
*/
private static byte[] getRawKey()
throws NoSuchAlgorithmException
{
KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG" );
sr.setSeed( KEY.getBytes() );
kgen.init( 128, sr );
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
/**
* Process the given input with the provided mode. <br/>
*
* #param the cipher mode
* #param the value to process
* #return the processed value as byte[]
* #throws InvalidKeyException
* #throws IllegalBlockSizeException
* #throws BadPaddingException
* #throws NoSuchAlgorithmException
* #throws NoSuchPaddingException
*/
private static byte[] process( int mode, byte[] value )
throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException
{
SecretKeySpec skeySpec = new SecretKeySpec( getRawKey(), "AES" );
Cipher cipher = Cipher.getInstance( "AES" );
cipher.init( mode, skeySpec );
byte[] encrypted = cipher.doFinal( value );
return encrypted;
}
/**
* Decode an HEX encoded string into a byte[]. <br/>
*
* #param the HEX string value
* #return the decoded byte[]
*/
protected static byte[] fromHex( String value )
{
int len = value.length() / 2;
byte[] result = new byte[len];
for ( int i = 0; i < len; i++ )
{
result[i] = Integer.valueOf( value.substring( 2 * i, 2 * i + 2 ), 16 ).byteValue();
}
return result;
}
/**
* Encode a byte[] into an HEX string. <br/>
*
* #param the byte[] value
* #return the HEX encoded string
*/
protected static String toHex( byte[] value )
{
if ( value == null )
{
return "";
}
StringBuffer result = new StringBuffer( 2 * value.length );
for ( int i = 0; i < value.length; i++ )
{
byte b = value[i];
result.append( HEX.charAt( ( b >> 4 ) & 0x0f ) );
result.append( HEX.charAt( b & 0x0f ) );
}
return result.toString();
}
}
Here's a small unit test that i've created to reproduce the error
import junit.framework.TestCase;
public class UtilsEncryptionTest
extends TestCase
{
/** A random string */
private static String ORIGINAL = "some string to test";
/**
* The HEX value corresponds to ORIGINAL. <br/>
* If you change ORIGINAL, calculate the new value on one of this sites:
* <ul>
* <li>http://www.string-functions.com/string-hex.aspx</li>
* <li>http://www.yellowpipe.com/yis/tools/encrypter/index.php</li>
* <li>http://www.convertstring.com/EncodeDecode/HexEncode</li>
* </ul>
*/
private static String HEX = "736F6D6520737472696E6720746F2074657374";
public void testToHex()
{
String hexString = UtilsEncryption.toHex( ORIGINAL.getBytes() );
assertNotNull( "The HEX string should not be null", hexString );
assertTrue( "The HEX string should not be empty", hexString.length() > 0 );
assertEquals( "The HEX string was not encoded correctly", HEX, hexString );
}
public void testFromHex()
{
byte[] stringBytes = UtilsEncryption.fromHex( HEX );
assertNotNull( "The HEX string should not be null", stringBytes );
assertTrue( "The HEX string should not be empty", stringBytes.length > 0 );
assertEquals( "The HEX string was not encoded correctly", ORIGINAL, new String( stringBytes ) );
}
public void testWholeProcess()
{
String encrypted = UtilsEncryption.encrypt( ORIGINAL );
assertNotNull( "The encrypted result should not be null", encrypted );
assertTrue( "The encrypted result should not be empty", encrypted.length() > 0 );
String decrypted = UtilsEncryption.decrypt( encrypted );
assertNotNull( "The decrypted result should not be null", decrypted );
assertTrue( "The decrypted result should not be empty", decrypted.length() > 0 );
assertEquals( "Something went wrong", ORIGINAL, decrypted );
}
}
The line throwing the exception is:
byte[] encrypted = cipher.doFinal( value );
The full stack trace is:
W/<package>.UtilsEncryption:decrypt(16414): pad block corrupted
W/System.err(16414): javax.crypto.BadPaddingException: pad block corrupted
W/System.err(16414): at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
W/System.err(16414): at javax.crypto.Cipher.doFinal(Cipher.java:1111)
W/System.err(16414): at <package>.UtilsEncryption.process(UtilsEncryption.java:117)
W/System.err(16414): at <package>.UtilsEncryption.decrypt(UtilsEncryption.java:69)
W/System.err(16414): at <package>.UtilsEncryptionTest.testWholeProcess(UtilsEncryptionTest.java:74)
W/System.err(16414): at java.lang.reflect.Method.invokeNative(Native Method)
W/System.err(16414): at java.lang.reflect.Method.invoke(Method.java:511)
W/System.err(16414): at junit.framework.TestCase.runTest(TestCase.java:168)
W/System.err(16414): at junit.framework.TestCase.runBare(TestCase.java:134)
W/System.err(16414): at junit.framework.TestResult$1.protect(TestResult.java:115)
W/System.err(16414): at junit.framework.TestResult.runProtected(TestResult.java:133)
D/elapsed ( 588): 14808
W/System.err(16414): at junit.framework.TestResult.run(TestResult.java:118)
W/System.err(16414): at junit.framework.TestCase.run(TestCase.java:124)
W/System.err(16414): at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:190)
W/System.err(16414): at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:175)
W/System.err(16414): at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:555)
W/System.err(16414): at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1661)
Does anybody have a clue of what might be happening ?
Is anybody aware of a breaking change on android 4.2 in any of the referenced classes?
Thanks a lot
From the Android Jellybean page:
Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL
They changed the default provider for SecureRandom to use OpenSSL instead of the previous Crypto provider.
The following code will produce two different outputs on pre-Android 4.2 and Android 4.2:
SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
Log.i(TAG, "rand.getProvider(): " + rand.getProvider().getName());
On pre-4.2 devices:
rand.getProvider: Crypto
On 4.2 devices:
rand.getProvider: AndroidOpenSSL
Fortunately, it's easy to revert to the old behavior:
SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", "Crypto" );
To be sure, it's dangerous to be calling SecureRandom.setSeed at all in light of the Javadocs which state:
Seeding SecureRandom may be insecure
A seed is an array of bytes used to bootstrap random number generation. To produce cryptographically secure random numbers, both the seed and the algorithm must be secure.
By default, instances of this class will generate an initial seed using an internal entropy source, such as /dev/urandom. This seed is unpredictable and appropriate for secure use.
You may alternatively specify the initial seed explicitly with the seeded constructor or by calling setSeed(byte[]) before any random numbers have been generated. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.
However, for writing unit tests, as you are doing, using setSeed may be okay.
As Brigham pointed out that, in Android 4.2, there was a security enhancement, which updated the default implementation of SecureRandom from Crypto to OpenSSL
Cryptography - Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL. Added SSL Socket support
for TLSv1.1 and TLSv1.2 using OpenSSL 1.0.1
bu Brigham's answer is a temporary solution and not recommended, because although it resolves the issue, its still doing the wrong way.
The recommended way (check Nelenkov’s tutorial) is to use proper key derivations PKCS (Public Key Cryptography Standard), which defines two key derivation functions, PBKDF1 and PBKDF2, of which PBKDF2 is more recommended.
This is how you should get the key,
int iterationCount = 1000;
int saltLength = 8; // bytes; 64 bits
int keyLength = 256;
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(seed.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] raw = keyFactory.generateSecret(keySpec).getEncoded();
So what you are trying is to use pseudo random generator as a key derivation function. This is bad for the following reasons:
PRNG are by design non-deterministic and you are relying on it to be deterministic
Relying on a bug and deprecated implementations will break your App some day
PRNG are not designed to be good KDFs
More precisely Google deprecated the use of the Crypto provider in Android N (SDK 24)
Here are some better methods:
Hashed Message Authentication Code (HMAC)-based key derivation function (HKDF)
Using this library:
String userInput = "this is a user input with bad entropy";
HKDF hkdf = HKDF.fromHmacSha256();
//extract the "raw" data to create output with concentrated entropy
byte[] pseudoRandomKey = hkdf.extract(staticSalt32Byte, userInput.getBytes(StandardCharsets.UTF_8));
//create expanded bytes for e.g. AES secret key and IV
byte[] expandedAesKey = hkdf.expand(pseudoRandomKey, "aes-key".getBytes(StandardCharsets.UTF_8), 16);
//Example boilerplate encrypting a simple string with created key/iv
SecretKey key = new SecretKeySpec(expandedAesKey, "AES"); //AES-128 key
PBKDF2 (Password-Based Key Derivation Function 2)
has key stretching which makes it more expensive to brute force the key. Use this for weak key input (like user password):
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
There are more KDFs like BCrypt, scrypt and Argon2