I need to write Java code for encrypting a string using AES (Rijndael).
There is an already working C# code that does this same encryption for the same purpose.
The C# code:
RijndaelManaged rijndaelCipher = new RijndaelManaged();
rijndaelCipher.KeySize = 256;
rijndaelCipher.BlockSize = 256;
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.PKCS7;
byte[] pwdBytes = System.Text.Encoding.UTF8.GetBytes(password); // password is 32 bytes long
byte[] keyBytes = new byte[32];
System.Array.Copy(pwdBytes, keyBytes, 32);
rijndaelCipher.Key = keyBytes;
rijndaelCipher.IV = keyBytes;
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
The Java code I came up with:
byte[] sessionKey = SENDER_KEY.getBytes(StandardCharsets.UTF_8);
byte[] iv = SENDER_KEY.getBytes(StandardCharsets.UTF_8) ;
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKey, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] cipheredText = cipher.doFinal(stringedXmlForSending.getBytes());
When I execute the Java code I get the exception:
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
Is it true that I cannot write in Java Rijndael encryption which is using 32 bytes keys?
Another difference is that padding in the C# is PKCS7 while in Java there is PKCS5. Will it still work?
The basic Java Cryptographic Extensions only provide AES implementations which is compatible with Rijndael at 128-bit block size. You should use 128-bit BlockSize so that you are compatible with most implementations which implement AES. If you do then the padding is no problem, since PKCS#5 is the same as PKCS#7 padding in AES.
It's important to note that AES is standardized which Rijndael isn't.
I struggled significantly to get this working due to the padding issue. By theory PKCS#5 from java should match PKCS#7 padding from .Net . When I looked deeper into the issue I found that there is an issue with Base64 encoder. Java has different library to encode byte into base64. By default java uses Base64 from java.util and it gives padding issue. When I used the different library to achieve this purpose I could get the desired library.
org.apache.commons.codec.binary.Base64.encodeBase64String (byte[] encryptedtext)
Related
Every time the encryption values changed by using AES, let anyone investigate the below code and let me know the issue
code:
private static final String secretKeys = "58BA833E57A51CBF9BF8BAB696BF9"
public static String encrypt() throws Exception {
byte[] salt = new byte[16];
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec pbeKeySpec = new PBEKeySpec(secretKeys.getChars(),salt,1000, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
byte[] iv = new byte[16];
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] result = cipher.doFinal("welcome".getBytes("UTF-8"));
String s = Base64.getEncoder().encodeToString(result);
return s
}
Output
first time I got the below-encrypted string
CZRIP35M4CnJtuDQ6YpmaQ==
The second time I got the below-encrypted string
/fylTjohAZDsnCaHhiZo3A==
I have three questions:
why the encrypted string not a constant?
how can I set the Blocksize? ( AES.BlockSize = 128;)
How can I set the padding mode? (AES.Padding = PaddingMode.PKCS7;)
For the first question, #Freiheit already answered this.
Long story short, based on the iv (initilization vector) which acts as a salt and will be different for each encryption.
Having that said, encrypting the same plain text will result in different encrypted text, but the decryption (if necessary) will result back into the same plain text.
IV is helpful to make the encryption predictable.
Having stored the same password for 2 different users in a database will have different values, but will be the same password.
With the current cipher configured, you already have 128 block size. You can read more about the different cypher transformation here. You can also find more information of the block sizes for different algorithms here
You just need to change the Cipher.getInstance() to AES/CBC/PKCS7Padding
1) the encrypted text is always different because the Cipher initialization is providing it's own IV since you are not providing one. You need to provide the IV you've "computed" in order to have a consistent output. Remember you never want to use an IV more than once for whatever this code is ultimately intended to do.
2) The keysize can be 128, 192 or 256 but the blocksize is always 128.
3) Java only provides PKCS5, but there is no difference in the implementation for AES. see what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding
As was already pointed out there are several problems with the code provided such as the first lines not actually doing anything and the key and iv both being uninitialized. I would additionally suggest you use SecureRandom to initialize your key and iv. If you plan on using only a single AES key, this can be computed once and placed in the code or configuration file instead of running PBKDF2 every time.
Only adding to the answer provided by #micker, you need to invoke another version of Cipher.init(); one that takes the IV into account:
...
byte[] iv = new byte[16];
IvParameterSpec ivSpec = new IvParameterSpec(iv); // <= Wrap your IV bytes here.
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec); // <= Add IV here.
...
That being said, the implementation suffers from a slew of other issues (key being all zeroes, IV being all zeroes, first 4 line don't do anything for you (as #JBNizet pointed out)). I hope you are only using it to study how Java's encryption mechanics works.
I give up in trying to research for solutions regarding my problem. I've done my part and search about this problem and encountered solutions (like this https://stackoverflow.com/a/21252990/5328303) which was really the same problem as mine but he is using aes-128-ecb.
I cannot get the solution to work for aes-192-ecb mode.
Here's the node.js part (take note I cannot change this part of the code since this is a third party provider and I'm very limited.)
console.log(encrypt("hello world"))
function encrypt(data) {
const aesKey = '4327601417486622'
const algorithm = 'aes-192-ecb'
const cipher = crypto.createCipher(algorithm, aesKey)
const crypted = cipher.update(data, 'utf-8', "hex") + cipher.final("hex")
return crypted
}
// expected: 066c47b162cd5c464ea9805742c1af9b
And here's my Java function:
public static String decrypt(String seed, String encrypted) throws Exception {
byte[] keyb = seed.getBytes("UTF-8");
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(keyb);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
Cipher dcipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
dcipher.init(Cipher.DECRYPT_MODE, skey);
byte[] clearbyte = dcipher.doFinal(toByte(encrypted));
return new String(clearbyte);
}
The java code above works well if I use aes-128-ecb on my node code but it cannot decode when I'm using aes-192-ecb or even aes-256-ecb.
Maybe I just don't quite understand openssl EVP_BytesToKey function since I read that crypto.createCipher() uses it when encrypting. It also said that it is hashing the key with MD5 which I'm currently doing with the java code.
Also I was thinking that the aesKey that I have is only 16 bytes and maybe that's why it won't work with AES-192 and only works with AES-128. I want to understand how openssl/crypto does it when I'm only passing a 16 byte key with the required 24 bytes key for AES-192 since I cannot change the node.js code.
Am I on the right track? Can anyone guide me?
Thank you!
I have research many source code and find difficulty on how to specify block size in AES java? Does java support until 256 bits? I have search out php source code and it support until 256 bits AES encryption
This is the sample source code for AES encryption. Thanks all for helping me to figure out.
http://aesencryption.net/
PHP supports Rijndael with a 256 block size. AES is a subset of Rijndael with key sizes of 128, 192 and 256 bits and a block size of 128 bits. So saying that PHP supports AES with a blocksize of 256 bit is a contradiction (i.e. incorrect).
Java SE (up to and including Java 9) by Oracle only supports AES with a 128 bit block size and all (3) AES key sizes, although you need the Unlimited Crypto files to use 192 and 256 bit encryption.
To use Rijndael with 256 bit block size you could use Bouncy Castle lightweight API, the different block sizes aren't added to the Bouncy Castle provider either:
new RijndaelEngine(256)
For Android you may want to use Spongy Castle instead.
TRY IT:
public static void main(String[] args) {
String key = "1234567890ABCDEF";
try {
byte[] encrypt = encrypt("hello word",key);
System.out.println(new String(encrypt));
String decrypt = decrypt(encrypt, key);
System.out.println(decrypt);
} catch (Exception ex) {
}
}
public static byte[] encrypt(String message, String key1) throws Exception {
SecretKeySpec key = new SecretKeySpec(key1.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "SunJCE");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(message.getBytes());
}
public static String decrypt(byte[] message, String key1) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(key1.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decryptedByte = cipher.doFinal(message);
String decryptedText = new String(decryptedByte);
return decryptedText;
}
I want to encrypt file and store it in SD card. I want to decrypt that encrypted file and store it in SD card again. I have tried to encrypt file by opening it as file stream and encrypt it but it is not working. I want some idea on how to do this.
Use a CipherOutputStream or CipherInputStream with a Cipher and your FileInputStream / FileOutputStream.
I would suggest something like Cipher.getInstance("AES/CBC/PKCS5Padding") for creating the Cipher class. CBC mode is secure and does not have the vulnerabilities of ECB mode for non-random plaintexts. It should be present in any generic cryptographic library, ensuring high compatibility.
Don't forget to use a Initialization Vector (IV) generated by a secure random generator if you want to encrypt multiple files with the same key. You can prefix the plain IV at the start of the ciphertext. It is always exactly one block (16 bytes) in size.
If you want to use a password, please make sure you do use a good key derivation mechanism (look up password based encryption or password based key derivation). PBKDF2 is the most commonly used Password Based Key Derivation scheme and it is present in most Java runtimes, including Android. Note that SHA-1 is a bit outdated hash function, but it should be fine in PBKDF2, and does currently present the most compatible option.
Always specify the character encoding when encoding/decoding strings, or you'll be in trouble when the platform encoding differs from the previous one. In other words, don't use String.getBytes() but use String.getBytes(StandardCharsets.UTF_8).
To make it more secure, please add cryptographic integrity and authenticity by adding a secure checksum (MAC or HMAC) over the ciphertext and IV, preferably using a different key. Without an authentication tag the ciphertext may be changed in such a way that the change cannot be detected.
Be warned that CipherInputStream may not report BadPaddingException, this includes BadPaddingException generated for authenticated ciphers such as GCM. This would make the streams incompatible and insecure for these kind of authenticated ciphers.
I had a similar problem and for encrypt/decrypt i came up with this solution:
public static byte[] generateKey(String password) throws Exception
{
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
sr.setSeed(keyStart);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
public static byte[] encodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(fileData);
return encrypted;
}
public static byte[] decodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(fileData);
return decrypted;
}
To save a encrypted file to sd do:
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "your_folder_on_sd", "file_name");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
byte[] yourKey = generateKey("password");
byte[] filesBytes = encodeFile(yourKey, yourByteArrayContainigDataToEncrypt);
bos.write(fileBytes);
bos.flush();
bos.close();
To decode a file use:
byte[] yourKey = generateKey("password");
byte[] decodedData = decodeFile(yourKey, bytesOfYourFile);
For reading in a file to a byte Array there a different way out there. A Example: http://examples.javacodegeeks.com/core-java/io/fileinputstream/read-file-in-byte-array-with-fileinputstream/
You could use java-aes-crypto or Facebook's Conceal
java-aes-crypto
Quoting from the repo
A simple Android class for encrypting & decrypting strings, aiming to
avoid the classic mistakes that most such classes suffer from.
Facebook's conceal
Quoting from the repo
Conceal provides easy Android APIs for performing fast encryption and
authentication of data
I used following code to encrypt the data. My input is 16 bytes and key is 16 bytes but the output I am getting (encrypted data ) is 32 bytes. Why?
public static byte[] encrypt(byte[] plainText, byte[] key) {
try {
byte[] passwordKey128 = Arrays.copyOfRange(key, 0, 16);
SecretKeySpec secretKey = new SecretKeySpec(passwordKey128, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] cipherText = cipher.doFinal(plainText);
// String encryptedString = Base64.getEncoder().encodeToString(cipherText);
return cipherText;
What can be the reason? Does AES add some data?
You obtain Cipher object through the Cipher.getInstance(transformation) method where the transformation is of the form:
"algorithm/mode/padding" or
"algorithm"
When you do this the implementation searches through the list of crypto providers in the system and determine if any implementation supports this. If you don't specify the mode and padding, its up to the crypto provider to decide what default mode and padding to use.
According to this, For example, the SunJCE defaults to ECB as the default mode, and PKCS5Padding.
As PKCS5Padding always adds at least one byte, it pushes your 16 bytes over the limit of the block and creates the need for two blocks.