String RSA encryption in Android - android

The situation:
I want an application that encrypts an string using RSA. I have the public key stored in res/raw, and as the key is 1024 bits, the resulting string has to be 128 bytes long. However, the resulting string after encrypting is 124 long, and as a result, the decryption crashes.
The function I am using to recover the public key is:
private PublicKey getPublicKey() throws Exception {
InputStream is = getResources().openRawResource(R.raw.publickey);
DataInputStream dis = new DataInputStream(is);
byte [] keyBytes = new byte [(int) is.available()];
dis.readFully(keyBytes);
dis.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
And the code of the function that I am using to encrypt:
private String rsaEncrypt (String plain) {
byte [] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = getPublicKey();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
P.D.: The code works perfectly in a desktop application, it just crashes in Android.
I really would appreciate any help,
Thank you very much.

String encrypted = new String(encryptedBytes);
is a bug. The output from crypto transforms are binary bytes. You cannot reliably store them as Strings.
Using is.available() is probably also a bug, but I'm not sure in this case.
Finally, it is one of my pet peeves when folks use the default charset versions of new String(...) and String.getBytes(). It is very rarely the right thing to do, especially in that Java claims to be "write once, run everywhere". The default charset is different on different platforms, which will trigger a bug in your code even if you do everything else correct. You should always specify a particular charset. In every case I have ever seen, simply using the UTF-8 Charset (Charset.forName("UTF-8");) will always work and represent data efficiently.

Related

Encrypted data is larger than the input data

I am trying to encrypt data using AES but I dont know why the output is larger than the input.
I used this function to derive the key
public byte[] deriveKey(String p, byte[] s, int i, int l) throws Exception {
PBEKeySpec ks = new PBEKeySpec(p.toCharArray(), s, i, l);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return skf.generateSecret(ks).getEncoded();
}
After that I send the data which is (112 bytes) and the key to the encrypt function but I get encrypted data (154 bytes)
public String encrypt(String s,byte[] d) throws Exception {
// Perform Encryption
SecretKeySpec eks = new SecretKeySpec(d, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
c.init(Cipher.ENCRYPT_MODE, eks, new IvParameterSpec(new byte[16]));
byte[] es = c.doFinal(s.getBytes(StandardCharsets.UTF_8));
}
The plaintext that you want to encrypt is a string (s) which you encode to a byte array right before encryption: s.getBytes(StandardCharsets.UTF_8).
If the plaintext string contains non-ASCII characters (code points 128 and up), those will be encoded as two or more bytes with UTF-8 (see the table in the Wikipedia article). English text will likely consist of the same number of bytes as characters. Other languages might not be so lucky and their encoding from string to binary data will be blown up.
CTR mode is a streaming mode of operation, so the plaintext/ciphertext input will always be the same size as the ciphertext/plaintext output. The problem is of course that a scheme like AES-CTR has three inputs: key, data and an IV/nonce.
Only if you're changing the password/key every time you encrypt, using a static zero-byte IV will be somewhat secure. If you reuse the password/key even once, you'll run into the two-time pad (many-time pad) problem where an attacker who simply observes ciphertexts might deduce the plaintexts just by looking at them (nice example).
If you cannot guarantee the single use of password/key, then you must use a new IV every time you encrypt. No more new IvParameterSpec(new byte[16]), but something like
SecureRandom r = new SecureRandom();
byte[] iv = new byte[16];
r.nextBytes(iv);
Arrays.fill(iv, 12, 16, (byte)0); // zero out the counter part
In many encryption algorithms, padding is inevitable. That's why you see a size increase on cipher. This post might be helpful to you to understand what happens: https://security.stackexchange.com/questions/29993/aes-cbc-padding-when-the-message-length-is-a-multiple-of-the-block-size

Possible faults in AES implementation in Android

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).

How to decrypt a data in Android which was crypted in nodejs

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.

Decryption works with a IV that is String.getBytes(), but fails SecureRandom.generateSeed()

I'm figuring out how to do cross platform (Android & Python) encryption&decryption using AES, and I seem to succeed transferring data if I use a String based IV. But immediately if I switch to using bytes generated with SecureRandom.generateSeed(), it goes awry.
The secret keys are preshared.
Working Android code (try/catch blocks removed to keep it short):
String SecretKey = "0123456789abcdef";
String iv = "fedcba9876543210";
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
SecretKeySpec keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//Initialize the cipher
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
String message = "What's up?";
byte[] encrypted = cipher.doFinal(message.getBytes());
//Send the data
outputStream.write(encrypted);
There is a small transfer header that let's the client know the size of the incoming message, but I thought it's not relevant and I left that out.
The Python code receiving this message looks like:
#Predefined:
unpad = lambda s : s[0:-ord(s[-1])]
encrypted = cs.recv(messagesize) # Receive the encrypted message
iv = encrypted[:16]
key = AES.new('0123456789abcdef', AES.MODE_CBC,IV=iv)
padded_msg = key.decrypt(encrypted[16:])
decrypted = unpad(padded_msg) #Remove padding
print "read [%s]" % decrypted
Result looks like:
read [What's up]
And if I change two lines in Java code:
SecureRandom rnd = new SecureRandom();
IvParameterSpec ivspec = new IvParameterSpec(rnd.generateSeed(16));
Python output becomes:
read [?=H��m��lڈ�1ls]
I wonder what changes with the SecureRandom? I read that by default the String.getBytes() return platform default encoding (for Android 4.0), so I wonder if I have to do some manipulation on Python end to the IV that was generated with SecureRandom..?
The recipient needs to be told what the IV is. Look at the section on CBC in this Wikipedia entry: you can see that an encrypted n-block message consists of n+1 blocks, the additional block being the IV. There is no standard protocol for transmitting it, but every code i've seen does this by prepending the IV to the message, which really is the natural thing to do and thanks to the error-correcting properties of CBC works even when the code gets it slightly wrong. For example, you can find code in the wild that uses a constant IV but prepends the plain text with a random block, which basically does the same thing in a different way. Sometimes you'll find that kind of code even in books, like the InlineIvCBCExample.java in chapter 2 of David Hook's otherwise very good book.
I recommed the following way of doing AES/CBC/PKCS7Padding:
byte[] plaintext = ...;
byte[] key = ...;
// get iv
SecureRandom rnd = new SecureRandom();
byte[] iv = rnd.getBytes(16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// encrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphertext = cipher.doFinal(plaintext);
// copy to result
byte[] result = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, result, 0, iv.length);
System.arraycopy(ciphertext, 0 , result, iv.length, ciphertext.length);

BlackBerry Encryption AES 256 - No Padding

I want to encrypt data in BlackBerry using the AES 256 encryption method. The requirement is to encrypt with No Padding; "AES/ECB/NoPadding". I am passing a 16 byte array and the encrypted data returned is a hex value of length 32. I have tried the following but it is not producing the correct result. The returned value is different from the expected encrypted value; tested in Android. The results between Android and BlackBerry do not tally. I have used the following method:
public static String EncryptData(byte[] keyData, byte[] data) throws Exception {
String encryptedData = "";
AESKey key = new AESKey(keyData);
NoCopyByteArrayOutputStream out = new NoCopyByteArrayOutputStream();
AESEncryptorEngine engine = new AESEncryptorEngine(key);
BlockEncryptor encryptor = new BlockEncryptor(engine, out);
encryptor.write(data, 0, data.length);
int finalLength = out.size();
byte[] cbytes = new byte[finalLength];
System.arraycopy(out.getByteArray(), 0, cbytes, 0, finalLength);
encryptedData = getHexString(cbytes);
return encryptedData;
}
Can anyone please guide?
EDIT: Below is the equivalent Android code:
Dim Kg As KeyGenerator
Dim c As Cipher
c.Initialize("AES/ECB/NoPadding") ' just "DES" actually performs "DES/ECB/PKCS5Padding".
Kg.Initialize("DESede")
Kg.KeyFromBytes(key)
bytes = Kg.KeyToBytes
msg_data = c.Encrypt(msg_data, Kg.key, False)
Return Bconv.HexFromBytes(msg_data)
There's a mistake in your Basic4Android code. You initialize the cipher with AES:
c.Initialize("AES/ECB/NoPadding")
but then initialize the key generator with TripleDES:
Kg.Initialize("DESede")
According to this documentation, just change "DESede" to "AES":
Kg.Initialize("AES")
Also, I wouldn't recommend using AES with ECB and no padding. It's insecure, especially when it's just as easy to use CBC or CTR mode. See this wikipedia article for an example of how unsafe it really is.

Categories

Resources