Please excuse the hackjob! Still new at coding and was utilizing a lot of sys outs to troubleshoot, and this is my first post at StackOverflow. Thank you for the help!
So I've been working on trying to encrypt String objects using the javax.crypto.Cipher API, and I have found some success, but only when it is using the same instance of the Cipher object. However, for the purposes of my project, I am encrypting text (String) to and decrypting text from a text file, and will not be accessing the same Cipher object every time.
I believe the issue is not from converting between the byte arrays and Strings, since the Base64 encoder seems to have taken care of that problem. The output of the byte arrays are identical pre-encoding and post-decoding, so that should isolate it as an issue during the decryption phase. What can be done so that my decryptPW method can use a different Cipher instance (with the same arguments passed) and not trigger a BadPaddingException?
private static String encryptPW(String pw){
byte[] pwBytes = pw.getBytes();
byte[] keyBytes = "0123456789abcdef".getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
try {
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = ciph.doFinal(pwBytes);
pw = Base64.getEncoder().encodeToString(ciph.doFinal(pwBytes));
for (byte b : encryptedBytes){
System.out.print(b);
}
System.out.println();
} catch (Exception e){
e.printStackTrace();
}
return pw;
}
private static String decryptPW(String pw){
byte[] pwBytes = Base64.getDecoder().decode(pw.getBytes());
for (byte b : pwBytes){
System.out.print(b);
}
System.out.println();
byte[] keyBytes = "0123456789abcdef".getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
try {
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, keySpec, ciph.getParameters());
pw = new String(ciph.doFinal(pwBytes));
} catch (Exception e){
e.printStackTrace();
}
return pw;
}
Again, thank you!
As you use CBC mode, you need to save the random Initialization Vector (IV) from the encryption cipher and provide it to the decryption cipher.
You can get it from the encryption cipher after init like:
ciph.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] iv = ciph.getIV();
And provide it to the decrypt cipher with something like:
ciph.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
You need to come up with a method of saving the IV. A common way is to prefix the encrypted data with the IV, so it's available when you need to initialize your decrypt cipher. It don't need to be secret.
Related
We need to keep some flags in the cookies for a spring MVC application
It will be checked and set in an interceptor for every request. Since we need to make the application stateless we don't want to store anything in the session.
My question is how do we encrypt/decrypt the cookie most efficiently? (As less CPU/time as possible).
Currently with AES encryption it takes around 200ms to encrypt and another similar time to decrypt. This is very high overhead considering we need to do it for every request.
Updated question with AES code taking long time
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
Can anyone suggest standard practices for this kind of requirements?
Thanks in advance.
I want to save the password in XML file. But the plaintext is not secure enough. So I want to save it in hashed value or encrypted data. But I don't know how to do this. Since MD5 or SHA-2 is one way hashing. Or if I use salt, it should save the value of salt.
So what can I do? Please help me with this problem.
You can use javax.crypto package to encrypt/decrypt password.
First of all you have to define encryption secret and encryption init vector. For instance:
String secret = "Foo12345Bar12345";
String initVector = "randomInitVector";
Then you can write methods to encrypt/decrypt password.
public static String encrypt(String value, String secret, String initVector) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes());
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.encodeBase64String(encrypted);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static String decrypt(String value, String secret, String initVector) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes());
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
byte[] decrypted = cipher.doFinal(Base64.decodeBase64(str));
return new String(decrypted);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
To run this code you need to add apache dependency to your project
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
In addition, Java 8 has already tools to decode/encode Base64:
java.util.Base64.getDecoder() and java.util.Base64.getEncoder() so you can replace Apache with Java 8 impl
This is a continuation from my question yesterday:
Android Java AES Encryption
I am currently testing AES encryption on Android. In my previous question I am able to encrypt and decrypt a string using Cipher c = Cipher.getInstance("AES");
I was informed in the replies that I should specify the IV, encryption mode and padding to prevent any potential issues in the future since no specification means the program will use the system's default value. So I changed my code to c = Cipher.getInstance("AES/CBC/PKCS5Padding");
But now my code no longer works, and it will return a NullPointerException.
My code:
byte[] a = encryptFIN128AES("pls");
String b = decryptFIN128AES(a);
Log.e("AES_Test", "b = " + b);
/**
* Encrypts a string with AES (128 bit key)
* #param fin
* #return the AES encrypted string
*/
private byte[] encryptFIN128AES(String fin) {
SecretKeySpec sks = null;
try {
sks = new SecretKeySpec(generateKey(PASSPHRASE, SALT.getBytes(StandardCharsets.UTF_8)).getEncoded(), "AES");
} catch (Exception e) {
Log.e("encryptFIN128AES", "AES key generation error");
}
// Encode the original data with AES
byte[] encodedBytes = null;
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, sks);
encodedBytes = c.doFinal(fin.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
Log.e("encryptFIN128AES", "AES encryption error");
}
return encodedBytes;
}
/**
* Decrypts a string with AES (128 bit key)
* #param encodedBytes
* #return the decrypted String
*/
private String decryptFIN128AES(byte[] encodedBytes) {
SecretKeySpec sks = null;
try {
sks = new SecretKeySpec(generateKey(PASSPHRASE, SALT.getBytes(StandardCharsets.UTF_8)).getEncoded(), "AES");
} catch (Exception e) {
Log.e("decryptFIN128AES", "AES key generation error");
}
// Decode the encoded data with AES
byte[] decodedBytes = null;
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, sks);
decodedBytes = c.doFinal(encodedBytes);
} catch (Exception e) {
Log.e("decryptFIN128AES", "AES decryption error");
}
//return Base64.encodeToString(decodedBytes, Base64.DEFAULT);
return new String(decodedBytes, StandardCharsets.UTF_8);
}
/**
* Build private key from a passpharase/PIN (incl. key derivation (Uses PBKDF2))
* #param passphraseOrPin
* #param salt
* #return The generated SecretKey (Used for AES-encryption, key size specified in outputKeyLength)
*/
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 128;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
Output:
E/decryptFIN128AES: AES decryption error
E/AndroidRuntime: FATAL EXCEPTION: Thread-176
Process: testapp.ttyi.nfcapp, PID: 2920
java.lang.NullPointerException: Attempt to get length of null array
at java.lang.String.<init>(String.java:371)
at testapp.ttyi.nfcapp.DisplayQRActivity.decryptFIN128AES(DisplayQRActivity.java:254)
at testapp.ttyi.nfcapp.DisplayQRActivity.access$100(DisplayQRActivity.java:29)
at testapp.ttyi.nfcapp.DisplayQRActivity$1.run(DisplayQRActivity.java:77)
at java.lang.Thread.run(Thread.java:818)
testapp.ttyi.nfcapp.DisplayQRActivity.decryptFIN128AES(DisplayQRActivity.java:254) points to the last line of decryptFIN128AES, which is:
return new String(decodedBytes, StandardCharsets.UTF_8);
I understand that the NullPointerException occurs because something went wrong with the decryption process. Since it must have went into the catch case and thus decodedBytes remains as NULL and thus causes the error when I want to return decodedBytes. So now my question is: Why does this happen and how can I fix this?
Your help is much appreciated.
Many thanks to #Thomas W for your help. I changed the catch to throw and now I can see the actual error has something to do with BadPaddingException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
After some googling I found the solution is the lack of IV argument in my c.init. Previously I was using "AES" in which Java defaults to "AES/ECB/PKCS5Padding" and that works fine without IV.
(Source: Android: Encrypt a string with AES 256bit Encryption with iv and secret key)
But once I changed to "AES/CBC/PKCS5Padding" Java will have issues without a declared IV. Therefore by changing c.init(Cipher.ENCRYPT_MODE, sks); and c.init(Cipher.DECRYPT_MODE, sks); to
c.init(Cipher.ENCRYPT_MODE, sks, new IvParameterSpec(new byte[16])); and
c.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(new byte[16])); fixed the issue.
Now my program can encrypt and decrypt properly.
I'm currently running into problems decrypting my data. The base64 of the encoded string is being stored in the database. So, I'm printing out the encoded string and then trying to run it back through with "DECRYPT" instead of "ENCRYPT". However, I never get a value that the Decrypter method likes, it always gives me an error about parameters or the value not being 16 bytes.
public class crypto {
public static void main(String [] args) {
String s = args[0];
String s1 = args[1];
String ivkey = "thisisasecretkey";
byte[] ivraw = ivkey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(ivraw, "AES");
if (s.equalsIgnoreCase("ENCRYPT")) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.encodeBase64(encrypted)));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.decodeBase64(encrypted)));
} catch (Exception e) {
e.printStackTrace();
}
}
return;
};
}
command:crypto "ENCRYPT" "password"
output: 5eQvSzPG1TE2AybgCmeV6A==
command:crytpo "DECRYPT" "5eQvSzPG1TE2AybgCmeV6A=="
output: java.security.InvalidKeyException: Parameters missing
I'm aware of the security flaws, that's not what I'm asking about and I would prefer answers/comments not get cluttered with best practices.
You should do base 64 decoding, and you should do that before decrypting.
You are not including the initialization vector (IV).
AES in CBC mode has both a 16 byte IV and the 16 byte symmetric key.
String IV = "AAAAAAAAAAAAAAAA"; // generate this randomly
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(IV.getBytes()));
byte[] encrypted = cipher.doFinal(s.getBytes());
Edit: as it turns out, encryption does not require a IV to be provided (as owlstead pointed out), but decryption does. The best bet would be to be explicit and use IV in both encryption and decryption. Change your decryption function to include the IV, and you will run into the other error in your code that owlstead pointed out.
I have function that crypts string:
BASE64Decoder decoder = new BASE64Decoder();
BASE64Encoder encoder = new BASE64Encoder();
public String encryptValueWithBlowfish(String data, String secretKey) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
try {
SecretKeySpec key = new SecretKeySpec(decoder.decodeBuffer(secretKey), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/NoPadding", "BC");
String iv = "\0\0\0\0\0\0\0\0";
IvParameterSpec ivs = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, key, ivs);
MessageDigest sha = MessageDigest.getInstance("SHA-1");
return encoder.encode(sha.digest(cipher.doFinal(decoder.decodeBuffer(data))));
} catch (Exception e) {
lg.info("Failed to encryptValueWithBlowfish: " + e.getMessage());
return "";
}
}
Line cipher.init(Cipher.ENCRYPT_MODE, key, ivs); rises exception "Unsupported keysize or algorithm parameters". This code forks fine in another Linux machine. Parameters passed in both cases are the same. I'm not strong in crypto things. What might be wrong?
What might be wrong?
Well, you're using the default character encoding here: iv.getBytes() - that's never a good start. Perhaps the two different machines have different default encodings.
If you want to create a byte array of all-zeroes and a particular size, why not just use:
IvParameterSpec ivs = new IvParameterSpec(new byte[8]);
Or use 16 if you wanted a 16 byte IV.
It's not clear what decoder is here - but you use it twice and again, if that's using the default character encoding it could vary by machine.