I have some public/private encryption code written. It works fine when the data to be encrypted is short, example: "this is plain text".
private static final String ALGORITHM = "RSA";
public static byte[] encryptWithPrivateKey(byte[] privateKey, byte[] inputData) throws Exception {
PrivateKey key = KeyFactory.getInstance(ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(privateKey));
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(inputData);
return encryptedBytes;
}
But when I try to encrypt a much longer string, I get an error ...
javax.crypto.IllegalBlockSizeException: Data must not be longer than 245 bytes
... according to this StackOverflow answer here ... the solution is to use algorithm "RSA/ECB/PKCS1Padding" instead of "RSA". [UPDATE: This conclusion was incorrect.]
When I changed ALGORITHM = "RSA"; to ALGORITHM = "RSA/ECB/PKCS1Padding";, I get this error ...
"java.security.NoSuchAlgorithmException: RSA/ECB/PKCS1Padding"
How do I fix this "NoSuchAlgorithm" error?
Just FYI, I'm using Spring Tool Suite 4 (4.6.0) and Java 1.8.0_241 that either came with it or was installed by Mac software updates.
Related
Thank you for taking you time to assist me with this!
THIS POST HAS BEEN EDITED FOR LESS INFORMATION SEE THE EDITED PART
Well I have spend ours of research on this matter and I ended up with a working piece of code..
But Encryption is not a place to make mistakes, and I wanted to ask if my code is actualy secure! It's really important for me because I want to implement it to a program so my code is...
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class EncryptFile{
private static final String FILE_IN = "./EncryptFile.java";
private static final String FILE_ENCR = "./EncryptFile_encr.java";
private static final String FILE_DECR = "./EncryptFile_decr.java";
public static void main(String []args){
try
{
Encryption("passwordisnottheactual", Files.readAllBytes(Paths.get(FILE_IN)));
Decryption("passwordisnottheactual");
}catch(Exception e){
System.out.println(e.getMessage());
}
}
private static void Encryption(String Key, byte[] byteArray) throws Exception
{
// Decode the base64 encoded Key
byte[] decodedKey = Base64.getDecoder().decode(Key);
// Rebuild the key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
// Cipher gets AES Algorithm instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize AesCipher with Encryption Mode, Our Key and A ?SecureRandom?
AesCipher.init(Cipher.ENCRYPT_MODE, secretKey, new SecureRandom());
byte[] byteCipherText = AesCipher.doFinal(byteArray);
//Write Bytes To File
Files.write(Paths.get(FILE_ENCR), byteCipherText);
}
private static void Decryption(String Key) throws Exception
{
//Ddecode the base64 encoded string
byte[] decodedKey = Base64.getDecoder().decode(Key);
//Rebuild key using SecretKeySpec
SecretKey secretKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
//Read All The Bytes From The File
byte[] cipherText = Files.readAllBytes(Paths.get(FILE_ENCR));
//Cipher gets AES Algorithm Instance
Cipher AesCipher = Cipher.getInstance("AES");
//Initialize it in Decrypt mode, with our Key, and a ?SecureRandom?
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
byte[] bytePlainText = AesCipher.doFinal(cipherText);
Files.write(Paths.get(FILE_DECR), bytePlainText);
}
}
EDIT
Possible duplicate of Simple Java AES encrypt/decrypt example – JFPicard
Well it could be but these answers Use IVParameterSpec and I wanted to know if
this line of code is actually secure or if it is bad practice:
AesCipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
because I use a new SecureRandom() every time,
and I haven't seen anyone use a SecureRandom object like this.
Encryption key
The password is passes as a string but the Encryption function Base64 decoded it, that is a coding error.
When a password is used the encryption key should be derived from it with the PBKDF2 (aka Rfc2898DeriveBytes) function.
When using key derivation the salt and iteration count needs to be available for decryption, often they are provided in a prefix to the encrypted data.
Encryption mode
No encryption mode is supplied.
Use CBC mode with a random IV.
Just prefix the encrypted data with the IV for use on decryption.
Padding
AES is a block cipher and as such requires the input data size to be a multiple of the block size.
Specify PKCS#7 (née PKCS#5) padding, it will add padding on encryption and remove it on decryption.
On decryption do not return "padding" errors, they can provide a "Padding Oracle" attack.
Explicit
Specify all encryption parameters and sizes.
Do not rely on implementation defaults.
Encryption authentication
Consider if there is a need to know if the data is decrypted correctly.
Versioning
Add a version indicator so that if changes are necessary later there is an compatibility path.
Or consider using RNCryptor which handles all this and more.
Update: (thx Andy for the comment)
If GCM mode is available and interoperability across platforms and libraries is not an issue GCM is arguably a better encryption mode. GCM has authentication and padding build-in making it more robust and an easier secure solution.
I am aware of a question very similar to this (How do I encrypt in Python and decrypt in Java?) but I have a different problem.
My problem is, I am not able to decrypt in Java correctly. Despite using the correct key and IV, I still get garbage characters after decryption. I don't have any compile/run-time errors or exceptions in Java so I believe I am using the right parameters for decryption.
Python Encryption Code -
from Crypto.Cipher import AES
import base64
key = '0123456789012345'
iv = 'RandomInitVector'
raw = 'samplePlainText'
cipher = AES.new(key,AES.MODE_CFB,iv)
encrypted = base64.b64encode(iv + cipher.encrypt(raw))
Java Decryption Code -
private static String KEY = "0123456789012345";
public static String decrypt(String encrypted_encoded_string) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
String plain_text = "";
try{
byte[] encrypted_decoded_bytes = Base64.getDecoder().decode(encrypted_encoded_string);
String encrypted_decoded_string = new String(encrypted_decoded_bytes);
String iv_string = encrypted_decoded_string.substring(0,16); //IV is retrieved correctly.
IvParameterSpec iv = new IvParameterSpec(iv_string.getBytes());
SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
plain_text = new String(cipher.doFinal(encrypted_decoded_bytes));//Returns garbage characters
return plain_text;
} catch (Exception e) {
System.err.println("Caught Exception: " + e.getMessage());
}
return plain_text;
}
Is there anything obvious that I am missing?
The Cipher Feedback (CFB) mode of operation is a family of modes. It is parametrized by the segment size (or register size). PyCrypto has a default segment size of 8 bit and Java (actually OpenJDK) has a default segment size the same as the block size (128 bit for AES).
If you want CFB-128 in pycrypto, you can use AES.new(key, AES.MODE_CFB, iv, segment_size=128). If you want CFB-8 in Java, you can use Cipher.getInstance("AES/CFB8/NoPadding");.
Now that we have that out the way, you have other problems:
Always specify the character set you're using, because it can change between different JVMs: new String(someBytes, "UTF-8") and someString.getBytes("UTF-8"). When you do, be consistent.
Never use a String to store binary data (new String(encrypted_decoded_bytes);). You can copy the bytes directly: IvParameterSpec iv = new IvParameterSpec(Arrays.copyOf(encrypted_decoded_bytes, 16)); and cipher.doFinal(Arrays.copyOfRange(encrypted_decoded_bytes, 16, encrypted_decoded_bytes.length)).
In Java, you're assuming that the IV is written in front of the ciphertext and then encoded together, but in Python, you're never doing anything with the IV. I guess you posted incomplete code.
It is crucial for CFB mode to use a different IV every time if the key stays the same. If you don't change the IV for every encryption, you will create a multi-time pad which enables an attacker to deduce the plaintext even without knowing the key.
I am having some troubles with the following code - I seem to be getting an IllegalBlocksizeException and am unsure what it is that I maybe doing incorrectly here? Would it be possible to get some advice / pointers?
Thanks
public class Encryption
{
private SecretKeyFactory factory;
private SecretKey tmp;
private SecretKey secret;
private Cipher cipher;
private byte[] iv;
private byte[] cipherText;
private final KeySpec spec = new PBEKeySpec("somepassword".toCharArray(), SALT, 65536, 256);
private static final byte[] SALT = {(byte)0xc3, (byte)0x23, (byte)0x71, (byte)0x1c, (byte)0x2e, (byte)0xc2, (byte)0xee, (byte)0x77};
public Encryption()
{
try
{
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public String encrypt(String valueToEncrypt) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
return Base64.encodeBase64String(cipherText);
}
public String decrypt(String encryptedValueToDecrypt) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
return new String(cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes())));
}
public static void main(String[] args ) throws Exception
{
Encryption manager = new Encryption();
String encrypted = manager.encrypt("this is a string which i would like to encrypt");
System.out.println(encrypted);
String decrypted = manager.decrypt(encrypted);
System.out.println(decrypted);
System.out.println(encrypted.equals(decrypted));
}
}
The exception is as follows
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at encrypt.Encryption.decrypt(Encryption.java:52)
at encrypt.Encryption.main(Encryption.java:60)
The only approach to begin with the implementation of cryptographic algorithms, from the functional point of view (please keep in mind that a working code is not necessarily a secure one, and a lot of thought should come in that direction), is incremental: first try raw AES with a fixed key, then add the key generated by PBKDF2 and only later Base64. The latter is just an encoding tool and should be the easiest part of the process.
But let's take a look to the code:
1. The initialization seems fine, if your goal is to generate the key out of a password.
2. During the decryption, this line stands off:
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
valueToEncrypt is a readable string, but you're trying to decrypt it. Since it has only lowercase letters and spaces, it might not trigger an error, but you're trying to base64-decode something that hasn't been base64-encoded. It would make more sense to try:
cipherText = cipher.doFinal(valueToEncrypt.getBytes());
Then the cipherText can be base64-encoded.
For the decryption part, undo the operations in encryption in the reverse order. If you encrypted and then base64-encoded, then base64-decode first and then decrypt.
As a final recommendation: think modular. Encode in one line and encrypt in another, so if you want to remove or add a layer you just have to toggle the comments on one line.
You have reversed the base-64 encoding and decoding operations. Base-64 takes raw bytes and makes them into printable text. You can encode the output of an encryption operation to make it printable. But then you will need to base‑64–decode that text before trying to decrypt it.
This part of your decrypt() method is causing the problem:
cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes()))
That should be:
cipher.doFinal(Base64.decodeBase64(encryptedValueToDecrypt.getBytes()))
Asking for "pointers" is pretty open-ended. The best pointer I can give you: don't write this code yourself. Choose a package that provides a higher-level API, selecting high-security algorithms and applying them according to best practices. You don't know what you are doing, and you won't be able to write secure code. But using a high quality, open source library might help you begin to learn more about encryption.
You probably should base64 decode encryptedValueToDecrypt before you decrypt it.
I have "inherited" a Ruby on Rails app, and I must translate this app from Ruby to Java, and the most important thing, I don't have contact with the creator.
My problem is with the IV vector in AES-256 authentication. Ruby app uses AESCrypt gem to encrypt and decrypt user's password. It works fine, and I have already some thousands of users in DB.
The problem is when I try to do the same in Java (I've already updated JCE to allow 256bit key lenght). The Key and the IV are writen as binary strings in ruby source code (see bellow), and when I try to use it in Java I get a exception which say that the IV lenght must be 16 bytes long (I know that it must be 16 bytes long, but the binary string in Ruby has 32 characters).
Ruby code (works fine):
require 'openssl'
require 'digest/md5'
require 'base64'
module AESCrypt
KEY = "AB1CD237690AF13B6721AD237A"
IV = "por874hyufijdue7w63ysxwet4320o90"
TYPE = "AES-256-CBC"
def AESCrypt.key(key)
key = Digest::MD5.hexdigest(key)
key.slice(0..32)
end
# Encrypts a block of data given an encryption key and an
# initialization vector (iv). Keys, iv's, and the data returned
# are all binary strings. Cipher_type should be "AES-256-CBC",
# "AES-256-ECB", or any of the cipher types supported by OpenSSL.
# Pass nil for the iv if the encryption type doesn't use iv's (like
# ECB).
#:return: => String
#:arg: data => String
#:arg: key => String
#:arg: iv => String
#:arg: cipher_type => String
def AESCrypt.encrypt(data)
return nil if data.nil?
return data if data.blank?
aes = OpenSSL::Cipher::Cipher.new(TYPE)
aes.encrypt
aes.key = AESCrypt.key(KEY)
aes.iv = IV if IV != nil
result = aes.update(data) + aes.final
Base64.encode64(result)
end
end
and this is my Java code (it should do the same, seems that works with a 16 chars/bytes IV):
public static void main(String[] args) throws UnsupportedEncodingException {
String KEY = "AB1CD237690AF13B6721AD237A";
String IV = "por874hyufijdue7w63ysxwet4320o90";
SecretKeySpec key = generateKey(KEY);
String message = "password";
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphedText = cipher.doFinal(message.getBytes());
String encoded = Base64.encodeBase64String(ciphedText);
System.out.println("ENCRYPTED text= " + encoded);
}
public static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] bytes = password.getBytes("UTF-8");
digest.update(bytes, 0, bytes.length);
byte[] key = digest.digest();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
return secretKeySpec;
}
And I'm getting this exception (obviously):
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:516)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:339)
at javax.crypto.Cipher.implInit(Cipher.java:801)
at javax.crypto.Cipher.chooseProvider(Cipher.java:859)
at javax.crypto.Cipher.init(Cipher.java:1370)
at javax.crypto.Cipher.init(Cipher.java:1301)
at com.javi.test.security.Test.main(Test.java:129)
I guess my problem is the way I convert the IV java string in byte[]. I think that openSSL code in ruby is unpacking (or doing something internally) the 32 bytes of the IV to 16 bytes. I have tried a lot of things, but I'm going crazy.
Anyone had the same problem or figure out where could be my problem?
I have posted the encryption code but I hace the same issue with decryption.
Thanks in advance, I'll be very grateful with every answer. :)
First, your IV is not actually iv, IV should be HEX encoded, but you have ASCII string "por874hyufijdue7w63ysxwet4320o90", may be it is some how encoded?
Second, IV.getBytes() will transofr IV's each character to hex encoding like p = 0x70, o = 0x6F, r = 0x72, etc...
It is not a useful answer, but may be hint.
Actually IV must be the same length as block cipher single block length. You have 32 bytes long IV itself, if you make IV.getBytes() IV length should match the cipher block length
I am able to encrypt data however when decrypting it i am getting the following error:
Error
HTTP Status 500 - Request processing failed; nested exception is javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
Here is my Encryption and Decryption code
//secret key 8
private static String strkey ="Blowfish";
UPDATED
//encrypt using blowfish algorithm
public static byte[] encrypt(String Data)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, key);
return (cipher.doFinal(Data.getBytes("UTF8")));
}
//decrypt using blow fish algorithm
public static String decrypt(byte[] encryptedData)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encryptedData);
return new String(decrypted);
}
If you run your encrypt and decrypt methods in a main method, it will work. But if the results of encrypt are put into a url and then the url parameter is decrypted, it will fail.
After encryption, the byte array contains values that are outside the character set of URLS (non-ascii), so this value gets encoded when it is stuffed into a url. And you you receive a corrupted version for decryption.
As an example, when I created a string from an encrypted byte array, it looked like this Ž¹Qêz¦ but if I put it into a URL it turns into Ž%0B¹Qêz¦.
The fix, as suggested in other comments, is to add a encode / decode step. After encryption, the value should be encoded to a format which contains ascii characters. Base 64 is an excellent choice. So you return encrypted and encoded value in the url. When you receive the param, first decode then decrypt, and you'll get the original data.
Here are some notes on the implementation.
Use a library like commons codec. It is my weapon of choice, this class specifically http://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html.
In the class that does encryption and decryption, have a shared instance of Base64. To instantiate it use new Base64(true); this produces url safe strings.
Your encrypt and decrypt method signatures should accept and return strings, not byte arrays.
So the last line of your encrypt would become something like return base64.encodeToString(cipher.doFinal(Data.getBytes("UTF8"))); You can now safely pass the encrypted value in a url
In your decrypt, you first step is to decode. So the first line would become something like byte[] encryptedData = base64.decodeBase64(encrypted);
I just took your code and added some base 64 stuff, the result looks like this:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Test {
private static String strkey ="Blowfish";
private static Base64 base64 = new Base64(true);
//encrypt using blowfish algorithm
public static String encrypt(String Data)throws Exception{
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.ENCRYPT_MODE, key);
return base64.encodeToString(cipher.doFinal(Data.getBytes("UTF8")));
}
//decrypt using blow fish algorithm
public static String decrypt(String encrypted)throws Exception{
byte[] encryptedData = base64.decodeBase64(encrypted);
SecretKeySpec key = new SecretKeySpec(strkey.getBytes("UTF8"), "Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encryptedData);
return new String(decrypted);
}
public static void main(String[] args) throws Exception {
String data = "will this work?";
String encoded = encrypt(data);
System.out.println(encoded);
String decoded = decrypt(encoded);
System.out.println(decoded);
}
}
Hope this answers your questions.
You can't create a String out of random (in this case encrypted) bytes like you're doing in the last line of your encrypt method - you need to create a Base64 encoded string instead (which you then need to decode back to a byte array in the decrypt method). Alternatively, just have your encrypt method return a byte array and have your decrypt method accept a byte array as its parameter.
The problem is with the way you are creating String instances out of the raw encrypted byte[] data. You need to either use binhex encoding like that provided by javax.xml.bind.DatatypeConverter via the parseHexBinary and printHexBinary methods or base 64 using the parseBase64Binary and printBase64Binary methods of the same object.
One other word of advice, never rely on the default mode and padding, always be explicit. Use something like Cipher.getInstance("Blowfish/CBC/PKCS5Padding") depending on what your needs are.