Why I ask this question:
I know there have been a lot of questions about AES encryption, even for Android. And there are lots of code snippets if you search the Web. But on every single page, in every Stack Overflow question, I find another implementation with major differences.
So I created this question to find a "best practice". I hope we can collect a list of the most important requirements and set up an implementation that is really secure!
I read about initialization vectors and salts. Not all implementations I found had these features. So do you need it? Does it increase the security a lot? How do you implement it? Should the algorithm raise exceptions if the encrypted data cannot be decrypted? Or is that insecure and it should just return an unreadable string? Can the algorithm use Bcrypt instead of SHA?
What about these two implementations I found? Are they okay? Perfect or some important things missing? What of these is secure?
The algorithm should take a string and a "password" for encryption and then encrypt the string with that password. The output should be a string (hex or base64?) again. Decryption should be possible as well, of course.
What is the perfect AES implementation for Android?
Implementation #1:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedCrypto implements ICrypto {
public static final String PROVIDER = "BC";
public static final int SALT_LENGTH = 20;
public static final int IV_LENGTH = 16;
public static final int PBE_ITERATION_COUNT = 100;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
private static final String HASH_ALGORITHM = "SHA-512";
private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_ALGORITHM = "AES";
public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
try {
byte[] iv = generateIv();
String ivHex = HexEncoder.toHex(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
String encryptedHex = HexEncoder.toHex(encryptedText);
return ivHex + encryptedHex;
} catch (Exception e) {
throw new CryptoException("Unable to encrypt", e);
}
}
public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
try {
Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
String ivHex = encrypted.substring(0, IV_LENGTH * 2);
String encryptedHex = encrypted.substring(IV_LENGTH * 2);
IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
String decrypted = new String(decryptedText, "UTF-8");
return decrypted;
} catch (Exception e) {
throw new CryptoException("Unable to decrypt", e);
}
}
public SecretKey getSecretKey(String password, String salt) throws CryptoException {
try {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
SecretKey tmp = factory.generateSecret(pbeKeySpec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
return secret;
} catch (Exception e) {
throw new CryptoException("Unable to get secret key", e);
}
}
public String getHash(String password, String salt) throws CryptoException {
try {
String input = password + salt;
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
byte[] out = md.digest(input.getBytes("UTF-8"));
return HexEncoder.toHex(out);
} catch (Exception e) {
throw new CryptoException("Unable to get hash", e);
}
}
public String generateSalt() throws CryptoException {
try {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] salt = new byte[SALT_LENGTH];
random.nextBytes(salt);
String saltHex = HexEncoder.toHex(salt);
return saltHex;
} catch (Exception e) {
throw new CryptoException("Unable to generate salt", e);
}
}
private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
}
Source: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html
Implementation #2:
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Usage:
* <pre>
* String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
* ...
* String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
* </pre>
* #author ferenc.hechler
*/
public class SimpleCrypto {
public static String encrypt(String seed, String cleartext) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result);
}
public static String decrypt(String seed, String encrypted) throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2*buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
}
Source: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml
Neither implementation you give in your question is entirely correct, and neither implementation you give should be used as is. In what follows, I will discuss aspects of password-based encryption in Android.
Keys and Hashes
I will start discussing the password-based system with salts. The salt is a randomly generated number. It is not "deduced". Implementation 1 includes a generateSalt() method that generates a cryptographically strong random number. Because the salt is important to security, it should be kept secret once it is generated, though it only needs to be generated once. If this is a Web site, it's relatively easy to keep the salt secret, but for installed applications (for desktop and mobile devices), this will be much more difficult.
The method getHash() returns a hash of the given password and salt, concatenated into a single string. The algorithm used is SHA-512, which returns a 512-bit hash. This method returns a hash that's useful for checking a string's integrity, so it might as well be used by calling getHash() with just a password or just a salt, since it simply concatenates both parameters. Since this method won't be used in the password-based encryption system, I won't be discussing it further.
The method getSecretKey(), derives a key from a char array of the password and a hex-encoded salt, as returned from generateSalt(). The algorithm used is PBKDF1 (I think) from PKCS5 with SHA-256 as the hash function, and returns a 256-bit key. getSecretKey() generates a key by repeatedly generating hashes of the password, salt, and a counter (up to the iteration count given in PBE_ITERATION_COUNT, here 100) in order to increase the time needed to mount a brute-force attack. The salt's length should be at least as long as the key being generated, in this case, at least 256 bits. The iteration count should be set as long as possible without causing unreasonable delay. For more information on salts and iteration counts in key derivation, see section 4 in RFC2898.
The implementation in Java's PBE, however, is flawed if the password contains Unicode characters, that is, those that require more than 8 bits to be represented. As stated in PBEKeySpec, "the PBE mechanism defined in PKCS #5 looks at only the low order 8 bits of each character". To work around this problem, you can try generating a hex string (which will contain only 8-bit characters) of all 16-bit characters in the password before passing it to PBEKeySpec. For example, "ABC" becomes "004100420043". Note also that PBEKeySpec "requests the password as a char array, so it can be overwritten [with clearPassword()] when done". (With respect to "protecting strings in memory", see this question.) I don't see any problems, though, with representing a salt as a hex-encoded string.
Encryption
Once a key is generated, we can use it to encrypt and decrypt text.
In implementation 1, the cipher algorithm used is AES/CBC/PKCS5Padding, that is, AES in the Cipher Block Chaining (CBC) cipher mode, with padding defined in PKCS#5. (Other AES cipher modes include counter mode (CTR), electronic codebook mode (ECB), and Galois counter mode (GCM). Another question on Stack Overflow contains answers that discuss in detail the various AES cipher modes and the recommended ones to use. Be aware, too, that there are several attacks on CBC mode encryption, some of which are mentioned in RFC 7457.)
Note that you should use an encryption mode that also checks the encrypted data for integrity (e.g., authenticated encryption with associated data, AEAD, described in RFC 5116). However, AES/CBC/PKCS5Padding doesn't provide integrity checking, so it alone is not recommended. For AEAD purposes, using a secret that's at least twice as long as a normal encryption key is recommended, to avoid related key attacks: the first half serves as the encryption key, and the second half serves as the key for the integrity check. (That is, in this case, generate a single secret from a password and salt, and split that secret in two.)
Java Implementation
The various functions in implementation 1 use a specific provider, namely "BC", for its algorithms. In general, though, it is not recommended to request specific providers, since not all providers are available on all Java implementations, whether for lack of support, to avoid code duplication, or for other reasons. This advice has especially become important since the release of Android P preview in early 2018, because some functionality from the "BC" provider has been deprecated there — see the article "Cryptography Changes in Android P" in the Android Developers Blog. See also the Introduction to Oracle Providers.
Thus, PROVIDER should not exist and the string -BC should be removed from PBE_ALGORITHM. Implementation 2 is correct in this respect.
It is inappropriate for a method to catch all exceptions, but rather to handle only the exceptions it can. The implementations given in your question can throw a variety of checked exceptions. A method can choose to wrap only those checked exceptions with CryptoException, or specify those checked exceptions in the throws clause. For convenience, wrapping the original exception with CryptoException may be appropriate here, since there are potentially many checked exceptions the classes can throw.
SecureRandom in Android
As detailed in the article "Some SecureRandom Thoughts", in the Android Developers Blog, the implementation of java.security.SecureRandom in Android releases before 2013 has a flaw that reduces the strength of random numbers it delivers. This flaw can be mitigated as described in that article.
#2 should never be used as it uses only "AES" (which means ECB mode encryption on text, a big no-no) for the cipher. I'll just talk about #1.
The first implementation seems to adhere to best practices for encryption. The constants are generally OK, although both the salt size and the number of iterations for performing PBE are on the short side. Futhermore, it seems to be for AES-256 since the PBE key generation uses 256 as a hard coded value (a shame after all those constants). It uses CBC and PKCS5Padding which is at least what you would expect.
Completely missing is any authentication/integrity protection, so an attacker can change the cipher text. This means that padding oracle attacks are possible in a client/server model. It also means that an attacker can try and change the encrypted data. This will likely result in some error somewhere becaues the padding or content is not accepted by the application, but that's not a situation that you want to be in.
Exception handling and input validation could be enhanced, catching Exception is always wrong in my book. Furhtermore, the class implements ICrypt, which I don't know. I do know that having only methods without side effects in a class is a bit weird. Normally, you would make those static. There is no buffering of Cipher instances etc., so every required object gets created ad-nauseum. However, you can safely remove ICrypto from the definition it seems, in that case you could also refactor the code to static methods (or rewrite it to be more object oriented, your choice).
The problem is that any wrapper always makes assumptions about the use case. To say that a wrapper is right or wrong is therefore bunk. This is why I always try to avoid generating wrapper classes. But at least it does not seem explicitly wrong.
You have asked a pretty interesting question. As with all algorithms the cipher key is the "secret sauce", since once that's known to the public, everything else is too. So you look into ways to this document by Google
security
Besides Google In-App Billing also gives thoughts on security which is insightful as well
billing_best_practices
Use BouncyCastle Lightweight API. It provides 256 AES With PBE and Salt.
Here sample code, which can encrypt/decrypt files.
public void encrypt(InputStream fin, OutputStream fout, String password) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
aesCBC.init(true, aesCBCParams);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(true, aesCBCParams);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void decrypt(InputStream fin, OutputStream fout, String password) {
try {
PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
char[] passwordChars = password.toCharArray();
final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
aesCBC.init(false, aesCBCParams);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
aesCipher.init(false, aesCBCParams);
// Read in the decrypted bytes and write the cleartext to out
int numRead = 0;
while ((numRead = fin.read(buf)) >= 0) {
if (numRead == 1024) {
byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
// int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
} else {
byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
final byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
fout.write(plain, 0, plain.length);
}
}
fout.close();
fin.close();
} catch (Exception e) {
e.printStackTrace();
}
}
I found a nice implementation here :
http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html
and
https://github.com/nelenkov/android-pbe
That was also helpful in my quest for a good enough AES Implementation for Android
Related
I have an encrypted messenger app that Android P broke. In P any call to any call to
SecureRandom.getInstance("SHA1PRNG", "Crypto")
will throw NoSuchProviderException.
EDIT If we are not supposed to use SHA1PRNG, then what provider are we supposed to use ??
In my app the following method gets such an exception . . .
public static AESSeedAndMessage doAESEncrypt(String inString)
{
//returnes encrypted message along with UNencrypted seed
AESSeedAndMessage returnStuff = new AESSeedAndMessage();
returnStuff.seed = generateAESSeed(); //get a random seed
byte[] encodedBytes = null;
// Set up secret key spec for 128-bit AES encryption and decryption
SecretKeySpec sks = null;
try
{
SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", new CryptoProvider() );
sr.setSeed(returnStuff.seed.getBytes());
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128, sr);
sks = new SecretKeySpec((kg.generateKey()).getEncoded(), "AES");
// Encode the original data with AES
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, sks);
encodedBytes = c.doFinal(inString.getBytes());
}
catch (Exception e)
{
returnStuff.text = "error";
return returnStuff;
}
returnStuff.text = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
return returnStuff;
}
I find quite a bit of material on this issue but I haven't been able to find out what to do to replace the removal of that crypto provider. What do I need to do in order to make this method work, hopefully, in all versions of Android?
EDIT FYI
I had a problem with this code back in Android N when crypto was eliminated and had to add the following class to make it work in N.
import java.security.Provider;
/**
* Created by gary on 12/30/2017.
*/
//this is because Android N eliminated crypto
public class CryptoProvider extends Provider {
/**
* Creates a Provider and puts parameters
*/
public CryptoProvider() {
super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
put("SecureRandom.SHA1PRNG",
"org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
I want to decrypt and encrypt a string using chacha20
BouncyCastleProvider is using chacha20 technique. So I included it jar. and tried the code but not able to work.
PBE.java
public class PBE extends AppCompatActivity {
private static final String salt = "A long, but constant phrase that will be used each time as the salt.";
private static final int iterations = 2000;
private static final int keyLength = 256;
private static final SecureRandom random = new SecureRandom();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pbe);
try {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
//Security.addProvider(new BouncyCastleProvider());
String passphrase = "The quick brown fox jumped over the lazy brown dog";
String plaintext = "Hello";
byte [] ciphertext = encrypt(passphrase, plaintext);
String recoveredPlaintext = decrypt(passphrase, ciphertext);
TextView decryptedTv = (TextView) findViewById(R.id.tv_decrypt);
decryptedTv.setText(recoveredPlaintext);
System.out.println(recoveredPlaintext);
}catch (Exception e){
e.printStackTrace();
}
}
private static byte [] encrypt(String passphrase, String plaintext) throws Exception {
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");//,new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random);
return cipher.doFinal(plaintext.getBytes());
}
private static String decrypt(String passphrase, byte [] ciphertext) throws Exception {
SecretKey key = generateKey(passphrase);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");// , new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random);
return new String(cipher.doFinal(ciphertext));
}
private static SecretKey generateKey(String passphrase) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
return keyFactory.generateSecret(keySpec);
}
private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
byte [] ivBytes = new byte[cipher.getBlockSize()];
random.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
}
But it is not giving me proper result..
Edit and Updated Code
public class ChaCha20Encryptor implements Encryptor {
private final byte randomIvBytes[] = {0, 1, 2, 3, 4, 5, 6, 7};
static {
Security.addProvider(new BouncyCastleProvider());
}
#Override
public byte[] encrypt(byte[] data, byte[] randomKeyBytes) throws IOException, InvalidKeyException,
InvalidAlgorithmParameterException, InvalidCipherTextException {
ChaChaEngine cipher = new ChaChaEngine();
CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
cipher.init(true, new ParametersWithIV(cp , randomIvBytes));
//cipher.init(true, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));
byte[] result = new byte[data.length];
cipher.processBytes(data, 0, data.length, result, 0);
return result;
}
#Override
public byte[] decrypt(byte[] data, byte[] randomKeyBytes)
throws InvalidKeyException, InvalidAlgorithmParameterException, IOException,
IllegalStateException, InvalidCipherTextException {
ChaChaEngine cipher = new ChaChaEngine();
CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
cipher.init(false, new ParametersWithIV(cp , randomIvBytes));
//cipher.init(false, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));
byte[] result = new byte[data.length];
cipher.processBytes(data, 0, data.length, result, 0);
return result;
}
#Override
public int getKeyLength() {
return 32;
}
#Override
public String toString() {
return "ChaCha20()";
}
private static byte[] getMyKey(byte[] key){
try {
//byte[] key = encodekey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
}
catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
return key;
}
}
Now I have only problem decrypting. It shows an error that key must be 128 or 256 bits. What am I doing wrong.
Update on 24-DEC-2019 (Correction)
Unlike some other modes in AES like CBC, GCM mode does not require the IV to be unpredictable (same as Chacha20-Poly1305). The only requirement is that the IV (for AES) or nonce (for Chacha20-Poly1305) has to be unique for each invocation with a given key. If it repeats once for a given key, security can be compromised. An easy way to achieve this with good probability is to use a random IV or nonce from a strong pseudo random number generator as shown below. Probability of an IV or nonce collision (assuming a strong random source) will be at most 2^-32, which is low enough to deter attackers.
Using a sequence or timestamp as IV or nonce is also possible, but it may not be as trivial as it may sound. For example, if the system does not correctly keep track of the sequences already used as IV in a persistent store, an invocation may repeat an IV after a system reboot. Likewise, there is no perfect clock. Computer clocks readjusts etc.
Also, the key should be rotated after every 2^32 invocations.
SecureRandom.getInstanceStrong() can be used to generate a cryptographically strong random nonce.
Original Answer
Now that ChaCha20 is supported is Java 11. Here is a sample program for encrypting and decrypting using ChaCha20-Poly1305.
The possible reasons for using ChaCha20-Poly1305 (which is a stream cipher based authenticated encryption algorithm) over AES-GCM (which is an authenticated block cipher algorithm) are:
ChaCha20-Poly1305 is almost 3 times faster than AES when the CPU does not provide dedicated AES instructions. Intel processors provide AES-NI instruction set [1]
ChaCha20-Poly1305 does not need the nonce to be unpredictable / random unlike the IV of AES-GCM. Thus the overhead for running pseudo random number generator can be avoided [2]
ChaCha20 is not vulnerable to cache-collision timing attacks unlike AES [1]
package com.sapbasu.javastudy;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.Destroyable;
/**
*
* The possible reasons for using ChaCha20-Poly1305 which is a
* stream cipher based authenticated encryption algorithm
* 1. If the CPU does not provide dedicated AES instructions,
* ChaCha20 is faster than AES
* 2. ChaCha20 is not vulnerable to cache-collision timing
* attacks unlike AES
* 3. Since the nonce is not required to be random. There is
* no overhead for generating cryptographically secured
* pseudo random number
*
*/
public class CryptoChaCha20 {
private static final String ENCRYPT_ALGO = "ChaCha20-Poly1305/None/NoPadding";
private static final int KEY_LEN = 256;
private static final int NONCE_LEN = 12; //bytes
private static final BigInteger NONCE_MIN_VAL = new BigInteger("100000000000000000000000", 16);
private static final BigInteger NONCE_MAX_VAL = new BigInteger("ffffffffffffffffffffffff", 16);
private static BigInteger nonceCounter = NONCE_MIN_VAL;
public static byte[] encrypt(byte[] input, SecretKeySpec key)
throws Exception {
Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");
if (input.length == 0) {
throw new IllegalArgumentException("Length of message cannot be 0");
}
if (key.getEncoded().length * 8 != KEY_LEN) {
throw new IllegalArgumentException("Size of key must be 256 bits");
}
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
byte[] nonce = getNonce();
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] messageCipher = cipher.doFinal(input);
// Prepend the nonce with the message cipher
byte[] cipherText = new byte[messageCipher.length + NONCE_LEN];
System.arraycopy(nonce, 0, cipherText, 0, NONCE_LEN);
System.arraycopy(messageCipher, 0, cipherText, NONCE_LEN,
messageCipher.length);
return cipherText;
}
public static byte[] decrypt(byte[] input, SecretKeySpec key)
throws Exception {
Objects.requireNonNull(input, "Input message cannot be null");
Objects.requireNonNull(key, "key cannot be null");
if (input.length == 0) {
throw new IllegalArgumentException("Input array cannot be empty");
}
byte[] nonce = new byte[NONCE_LEN];
System.arraycopy(input, 0, nonce, 0, NONCE_LEN);
byte[] messageCipher = new byte[input.length - NONCE_LEN];
System.arraycopy(input, NONCE_LEN, messageCipher, 0, input.length - NONCE_LEN);
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
return cipher.doFinal(messageCipher);
}
/**
*
* This method creates the 96 bit nonce. A 96 bit nonce
* is required for ChaCha20-Poly1305. The nonce is not
* a secret. The only requirement being it has to be
* unique for a given key. The following function implements
* a 96 bit counter which when invoked always increments
* the counter by one.
*
* #return
*/
public static byte[] getNonce() {
if (nonceCounter.compareTo(NONCE_MAX_VAL) == -1) {
return nonceCounter.add(BigInteger.ONE).toByteArray();
} else {
nonceCounter = NONCE_MIN_VAL;
return NONCE_MIN_VAL.toByteArray();
}
}
/**
*
* Strings should not be used to hold the clear text message or the key, as
* Strings go in the String pool and they will show up in a heap dump. For the
* same reason, the client calling these encryption or decryption methods
* should clear all the variables or arrays holding the message or the key
* after they are no longer needed. Since Java 8 does not provide an easy
* mechanism to clear the key from {#code SecretKeySpec}, this method uses
* reflection to clear the key
*
* #param key
* The secret key used to do the encryption
* #throws IllegalArgumentException
* #throws IllegalAccessException
* #throws NoSuchFieldException
* #throws SecurityException
*/
#SuppressWarnings("unused")
public static void clearSecret(Destroyable key)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, SecurityException {
Field keyField = key.getClass().getDeclaredField("key");
keyField.setAccessible(true);
byte[] encodedKey = (byte[]) keyField.get(key);
Arrays.fill(encodedKey, Byte.MIN_VALUE);
}
}
And, here is a JUnit test:
package com.sapbasu.javastudy;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.junit.jupiter.api.Test;
public class CryptoChaCha20Test {
private int KEY_LEN = 256; // bits
#Test
public void whenDecryptCalled_givenEncryptedTest_returnsDecryptedBytes()
throws Exception {
char[] input = {'e', 'n', 'c', 'r', 'y', 'p', 't', 'i', 'o', 'n'};
byte[] inputBytes = convertInputToBytes(input);
KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
SecretKey secretKey = keyGen.generateKey();
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
"ChaCha20");
CryptoChaCha20.clearSecret(secretKey);
byte[] encryptedBytes = CryptoChaCha20.encrypt(inputBytes, secretKeySpec);
byte[] decryptedBytes = CryptoChaCha20.decrypt(encryptedBytes, secretKeySpec);
CryptoChaCha20.clearSecret(secretKeySpec);
assertArrayEquals(inputBytes, decryptedBytes);
}
private byte[] convertInputToBytes(char[] input) {
CharBuffer charBuf = CharBuffer.wrap(input);
ByteBuffer byteBuf = Charset.forName(Charset.defaultCharset().name())
.encode(charBuf);
byte[] inputBytes = byteBuf.array();
charBuf.clear();
byteBuf.clear();
return inputBytes;
}
}
The output of a cipher consists of random bits (generally limited by implementations to 8-bit bytes). Random bytes are likely to contain invalid characters in any character set. If you require a String, encode the ciphertext to base 64.
Furthermore, you re-generate the IV on decrypt. IV during encryption/decryption should match.
I'm trying to implement AES encryption ,in Android, which uses a pass phrase to generate the SecretKey. I'm passing the same byte[]
as initialization vector to the ciphers and as salt when generating the SecretKey with PBKDF2.
The passphrase is supplied by the user each time an encryption/decryption is needed.
As of now, I only need to encrypt one value in my database (if that makes any difference).
Questions:
I'm wondering if using the same byte[] as IV and salt weakens the encryption?
Is there a reason to switch from CBC to GCM other then the data integrity functionality GCM provides?
I've read about CBC being prone to BEAST attack, is using a new random IV per message, as demonstrated bellow, mitigates BEAST attack?
Current source code:
public class AesEncryption {
private static final int KEY_SIZE = 16;
private static final int OUTPUT_KEY_LENGTH = 256;
private static final int ITERATIONS = 1000;
private String mPassphraseOrPin;
public AesEncryption(String passphraseOrPin) {
mPassphraseOrPin = passphraseOrPin;
}
public void encrypt(String id, String textToEncrypt) throws Exception {
byte[] iv = getIv();
SecretKey secretKey = generateKey(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(textToEncrypt.getBytes("utf-8"));
byte[] ivCipherText = arrayConcat(iv, cipherText);
String encryptedText = Base64.encodeToString(ivCipherText, Base64.NO_WRAP);
storeEncryptedTextInDb(id, encryptedText);
}
public String decrypt(String id) throws Exception {
String encryptedText = getEncryptedTextFromDb(id);
byte[] ivCipherText = Base64.decode(encryptedText, Base64.NO_WRAP);
byte[] iv = Arrays.copyOfRange(ivCipherText, 0, KEY_SIZE);
byte[] cipherText = Arrays.copyOfRange(ivCipherText, KEY_SIZE, ivCipherText.length);
SecretKey secretKey = generateKey(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
String decrypted = new String(cipher.doFinal(cipherText), "utf-8");
return decrypted;
}
public SecretKey generateKey(byte[] salt) throws Exception {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(mPassphraseOrPin.toCharArray(), salt, ITERATIONS, OUTPUT_KEY_LENGTH);
SecretKey tmp = secretKeyFactory.generateSecret(keySpec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
private byte[] getIv() {
byte[] salt = new byte[KEY_SIZE];
new SecureRandom().nextBytes(salt);
return salt;
}
private byte[] arrayConcat(byte[] one, byte[] two) {
byte[] combined = new byte[one.length + two.length];
for (int i = 0; i < combined.length; ++i) {
combined[i] = i < one.length ? one[i] : two[i - one.length];
}
return combined;
}
}
I'm wondering if using the same byte[] as IV and salt weakens the encryption?
Yes it does.
For the salt: if you don't randomize the salt then an attacker can pre-calculate a table with passwords and password hashes. This is called a rainbow table. Furthermore, if anybody has the same password it would result in the same key. It's strongly recommended to generate a salt per user and - if feasible - a new salt each time the value is re-encrypted.
For the IV: if you re-encrypt starting blocks containing the same plaintext then the ciphertext will repeat blocks. An attacker can use this to extract information from this. Simple example: encrypting "Yes" or "No" twice will clearly be distinguishable from first encrypting "Yes" and then "No". Generally you should generate a random IV and store it with the ciphertext. This is recommended even if the salt (and thus the key) is randomized. It of course depends on your threat model if this makes a difference in the real world.
Is there a reason to switch from CBC to GCM other then the data integrity functionallity GCM provides?
GCM provides integrity and authenticity of the plaintext. Functionally it's just AES in CTR mode with an authentication tag. It depends on your threat model if you need integrity and authenticity of the plaintext (and possibly Additional Authenticated Data or AAD). It won't add any functionality otherwise.
If you're just after keeping your data confidential then you may not need GCM. If you want to protect it against changes made by an attacker then you do need it. In that case however you also need to protect against replay attacks.
I've read about CBC being prone to BEAST attack, is using a new random IV per message, as demonstrated bellow, mitigates BEAST attack?
The BEAST attack is a browser based attack against SSL/TLS. By definition it doesn't apply against database encryption, especially with regards to data at rest. A whole slew of attacks can possibly be raised, but BEAST depends on dynamic data within a TLS connection.
Notes:
Length based attacks are often forgotten as ciphers / cipher modes do not protect against them. They may be applicable none-the-less. GCM leaks slightly more information about the length of the plaintext compared to CBC.
It may also be interesting for an attacker to see if a value is re-encrypted or not.
1000 is not considered a secure iteration count / work factor anymore. You may want to upgrade it (and create a upgrade strategy).
I am seeing a small percentage of production users randomly report this exception related to encrypting/decrypting strings with Xamarin.Android but unfortunately I cannot reproduce it.
What could cause this and/or how could I reproduce the exception so that I can figure out a fix/workaround?
[CryptographicException: Bad PKCS7 padding. Invalid length 147.]
Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0
Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
System.Security.Cryptography.CryptoStream.FlushFinalBlock():0
com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0
EDIT: Here's the code I am using to encrypt/decrypt
private string EncryptWithByteArray(string inPassword, string inByteArray)
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write(inputArray, 0, inputArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
private string DecryptWithByteArray (string strText, string strEncrypt)
{
try
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider ();
Byte[] inputByteArray = Convert.FromBase64String (strText);
MemoryStream ms = new MemoryStream ();
CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write (inputByteArray, 0, inputByteArray.Length);
try {
cs.FlushFinalBlock();
} catch (Exception ex) {
throw(ex);
}
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
EDIT 2:
The encryption key is always the local Device ID. Here's how I am getting this:
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId;
Here's an example of how it's called:
string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
You have not provided much details about your use case but I would say this is happening because you are not using the same cipher settings during the encryption and decryption operations. Symmetric ciphers require you to use exactly the same settings/parameters during the data encryption and also decryption. For example for AES CBC you would need to use exactly the same key, IV, cipher mode and padding on both devices. It is best to set these setting explicitly in the code:
System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged();
aes.Key = new byte[] { ... };
aes.IV = new byte[] { ... };
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
If you are sure you are using the same settings then you should also consider scenario that some data get corrupted or altered during the network transfer.
Edit after some code fragments have been provided:
Decryption method you have provided does not work for me at all so I have put together all your samples and turned them into the code which does the same thing as yours but uses IMO a slightly cleaner approach. For example this code uses more robust "key derivation" (please forgive me cryptoguys) and it has also passed basic code analysis.
You should be able to easily use public methods to do what you need:
string plainData = "This information should be encrypted";
string encryptedData = EncryptStringified(plainData);
string decryptedData = DecryptStringified(encryptedData);
if (plainData != decryptedData)
throw new Exception("Decryption failed");
Implementation and private methods follows:
/// <summary>
/// Encrypts string with the key derived from device ID
/// </summary>
/// <returns>Base64 encoded encrypted data</returns>
/// <param name="stringToEncrypt">String to encrypt</param>
public string EncryptStringified(string stringToEncrypt)
{
if (stringToEncrypt == null)
throw new ArgumentNullException("stringToEncrypt");
byte[] key = DeviceIdToDesKey();
byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt);
byte[] encryptedData = Encrypt(key, plainData);
return Convert.ToBase64String(encryptedData);
}
/// <summary>
/// Decrypts Base64 encoded data with the key derived from device ID
/// </summary>
/// <returns>Decrypted string</returns>
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param>
public string DecryptStringified(string b64DataToDecrypt)
{
if (b64DataToDecrypt == null)
throw new ArgumentNullException("b64DataToDecrypt");
byte[] key = DeviceIdToDesKey();
byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt);
byte[] decryptedData = Decrypt(key, encryptedData);
return Encoding.UTF8.GetString(decryptedData);
}
private byte[] DeviceIdToDesKey()
{
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE";
// Compute hash of device ID so we are sure enough bytes have been gathered for the key
byte[] bytes = null;
using (SHA1 sha1 = SHA1.Create())
bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId));
// Get last 8 bytes from device ID hash as a key
byte[] desKey = new byte[8];
Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length);
return desKey;
}
private byte[] Encrypt(byte[] key, byte[] plainData)
{
if (key == null)
throw new ArgumentNullException("key");
if (plainData == null)
throw new ArgumentNullException("plainData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
// desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream encryptedStream = new MemoryStream())
{
// Write IV at the beginning of memory stream
encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length);
// Perform encryption and append encrypted data to the memory stream
using (ICryptoTransform encryptor = desProvider.CreateEncryptor())
{
byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
return encryptedStream.ToArray();
}
}
}
private byte[] Decrypt(byte[] key, byte[] encryptedData)
{
if (key == null)
throw new ArgumentNullException("key");
if (encryptedData == null)
throw new ArgumentNullException("encryptedData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
if (encryptedData.Length <= desProvider.IV.Length)
throw new CryptographicException("Too short encrypted data has been specified");
// Read IV from the beginning of encrypted data
// Note: New byte array needs to be created because data written to desprovider.IV are ignored
byte[] iv = new byte[desProvider.IV.Length];
Array.Copy(encryptedData, 0, iv, 0, iv.Length);
desProvider.IV = iv;
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
// Remove IV from the beginning of encrypted data and perform decryption
using (ICryptoTransform decryptor = desProvider.CreateDecryptor())
return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length);
}
}
It is really hard to tell what exactly was problem with your code because your decryption method did not work for me at all - most likely because it is using CryptoStream in write mode for decryption which seems a little odd to me.
So much for the code. Now let's get to encryption which is really really weak. It is more just an obfuscation that should protect the data from being accidentally displayed in plain text form (some people use BASE64 encoding for the same thing). The main cause of this is relatively old encryption algorithm and easily predictable encryption key. AFAIK every application running on the same device can read device ID without any privileges. That means any application can decrypt your data. Of course your SQLite database is probably accessible only to your application but that can no longer be true if you remove the SD card or root your phone. To make this a little better you could for example ask user to provide a password and then use it to derive unique encryption key but that is completely different problem. Anyway I am not really sure what you are trying to achieve with this encryption - it may be fully sufficient for your needs even if it can be considered to be weak.
Hope this helps.
I'm reading a lot since some weeks to implement an encrypt/decrypt algoritm for my Android application. I'm implementing a license key that is downloaded from my website and stored in the external storage of my Android device. the application read the content of the file and decrypt it using the server public key (yes i know that i should with client private key but it's ok for my purpose). The problem is that the final string has a lot of black square with question mark inside. i've read a lot of other posts here on stackoverflow, but i think that the "only" problem is that, even if there should be 10 chars in the string, the string is long 255 bytes (with 2048 bit RSA key) and the remaining chars are filled with black "". Why the newPlainText var is not long as "Hello World!" ? Here below my code... Many thanks in advance!
public boolean licenseValid() throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
java.io.File file = new java.io.File(Environment.getExternalStorageDirectory().toString() ,
"/folder/file.lic");
byte[] fileBArray = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < fileBArray.length
&& (numRead=fis.read(fileBArray, offset, fileBArray.length-offset)) >= 0) {
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < fileBArray.length) {
throw new IOException("Could not completely read file "+file.getName());
}
fis.close();
// Decrypt the ciphertext using the public key
PublicKey pubKey = readKeyFromFile();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, pubKey);
byte[] newPlainText = cipher.doFinal(fileBArray);
// THE FOLLOWING TOAST PRINTS MANY <?> AND THAN THE DECRYPTED MESSAGE. THE TOTAL NUMBER OF CHARACTERS IS 255, EVEN IF I CHANGE ENCRYPTED TEXT!
toast(String.valueOf(cipher.doFinal(fileBArray).length));
if (new String(newPlainText, "utf-8").compareTo("Hello World!") == 0)
return true;
else
return false;
}
PublicKey readKeyFromFile() throws IOException {
Resources myResources = getResources();
//public key filename "pub.lic"
InputStream is = myResources.openRawResource(R.raw.pub);
ObjectInputStream oin =
new ObjectInputStream(new BufferedInputStream(is));
try {
BigInteger m = (BigInteger) oin.readObject();
BigInteger e = (BigInteger) oin.readObject();
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
return pubKey;
} catch (Exception e) {
throw new RuntimeException("Spurious serialisation error", e);
} finally {
oin.close();
}
}
If you encrypt with RSA the input and output are always the same length as the key. In your case, that should be 256 bytes (=2048 bits), so first check your code, you are missing a byte.
When the input is shorter, you need to apply a padding, and it looks like your server and client are using a different one. Cipher.getInstance("RSA") will use the platform default, which is probably different for Android and Java SE. You need to specify the padding explicitly in both programs for this to work. Something like this:
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
BTW, you really don't want to distribute the private key with your app, so using the public key is the right thing to do. (Whether your whole encryption scheme is secure is another matter though).